13
\$\begingroup\$

Refer to the help message and title of this post for what the code does.

I'm looking for tips on:

  • Efficiency: While the find-and-replace approach I have works perfectly fine and does so in a relatively quick manner, it feels far slower than it could be.
  • Idiomaticness: As always, I like writing code that abuses every feature it can.
  • Code readability: It's pretty freaking complicated. Even I barely understand what the heck is going on here:

    self.chunk(rows).map { |row| row.shift.zip(*row) }.map { |col| col.chunk(cols) }.flatten(1)
    

    Note that I'm not asking for an explanation. I wrote the code; I understand how it works. I'm asking for tips to improve readability.

  • Help message: Everything about this. I've never been particularly good at writing documentation for other people to use.
require 'pathname'
if ARGV == ['-h'] || ARGV == ['/?']
 file = Pathname.new(__FILE__).relative_path_from(Pathname.new(Dir.getwd))
 puts <<-END.gsub(/^\s*\|/m, '')
 | A simple fractal generator, written in Ruby.
 | 
 | Takes as input the number of rounds to go through, the starting 'map' and a set
 | of 'rules' which determine how to modify the input each round.
 | 
 | Outputs 
 | 
 | SYNTAX:
 | ruby #{file} -h
 | ruby #{file} <round count> <file with initial> <file with rules>
 | <data_source_command> | ruby #{file} <round count> -g
 | 
 | Command-line flags:
 | -------------------
 | 
 | -h OR /?: Display this help message.
 | -g: Retrieve the data from standard input, rather than the command line.
 END
 exit
end
pattern = []
rules = {}
count = Integer(ARGV.shift)
if ARGV.length == 1 && ARGV[0] == '-g'
 pattern << $_.chars until gets == ''
 until gets == ''
 from, to = $_.chomp.split('>').map { |s| s.split(',').map(&:chars) }
 rules[from] = to
 end
else
 pattern = IO.readlines(ARGV[0]).map(&:chomp).map(&:chars)
 rules = Hash[
 IO.readlines(ARGV[1]).map(&:chomp).reject { |line| line.empty? }.map do |line|
 line.split('>').map { |half| half.split(',').map(&:chars) }
 end
 ]
end
class Array
 def dimensions
 width = self[0].size
 jagged = self.any? { |row| row.size != width }
 [self.size, jagged ? nil : width]
 end
 def chunk(chunk_size)
 self.each_with_index.with_object(Array.new(size / chunk_size) { [] }) do |(elt, ind), result|
 result[ind / chunk_size] << elt
 end
 end
 def to_cells(cell_dims)
 rows, cols = cell_dims
 self.chunk(rows).map { |row| row.shift.zip(*row) }.map { |col| col.chunk(cols) }.flatten(1)
 end
 def from_cells(cells_per_row)
 self.chunk(self.size / cells_per_row).each_with_object([]) do |row_of_cells, result|
 result << row_of_cells.each_with_object(Array.new(row_of_cells[0].size) { [] }) do |cell, array|
 cell.each_with_index { |cell_row, index| array[index].concat(cell_row) }
 end
 end.flatten(1)
 end
end
rules.each do |from, to|
 from_size = from.dimensions
 to_size = to.dimensions
 raise ArgumentError, "#{from} is jagged" if from_size.any? { |side| side.nil? }
 raise ArgumentError, "#{to} is jagged" if to_size.any? { |side| side.nil? }
 raise ArgumentError, "#{from} doesn't fit into #{to}" unless to_size.zip(from_size).all? { |(a,b)| a % b == 0 }
end
from_size = rules.keys[0].dimensions
to_size = rules.values[0].dimensions
rules.each do |from, to|
 raise ArgumentError, "Rule sources aren't of equal size" unless from.dimensions == from_size
 raise ArgumentError, "Rule results aren't of equal size" unless to.dimensions == to_size
end
raise ArgumentError, "Rule sources don't fit the input" unless pattern.dimensions.zip(from_size).all? { |(a,b)| a%b==0 }
(1..count).each do |round_num|
 cells_per_row = pattern.dimensions[1] / rules.keys[0].dimensions[1]
 pattern = pattern.to_cells(from_size).map { |cell| rules.fetch(cell, cell) }.from_cells(cells_per_row)
end
pattern.each { |row| puts row.join '' }

And some test data:

ruby build_fractal.rb 3 start.txt rules.txt

start.txt:

#

rules.txt:

#> # ,###, # 
 > , , 

outputs:

 # 
 ### 
 # 
 # # # 
 ######### 
 # # # 
 # 
 ### 
 # 
 # # # 
 ### ### ### 
 # # # 
 # # # # # # # # # 
###########################
 # # # # # # # # # 
 # # # 
 ### ### ### 
 # # # 
 # 
 ### 
 # 
 # # # 
 ######### 
 # # # 
 # 
 ### 
 #

Note that there is trailing whitespace.

asked Aug 6, 2015 at 12:06
\$\endgroup\$
3
  • 3
    \$\begingroup\$ ++ for "As always, I like writing code that abuses every feature it can." \$\endgroup\$ Commented Aug 6, 2015 at 12:50
  • 1
    \$\begingroup\$ @Phrancis Dang, I was hoping it'd be because fractals are freaking cool. Eh, a ++ is a ++ either way. Assuming they're both either prefixes or suffixes, not one of each. \$\endgroup\$ Commented Aug 6, 2015 at 13:26
  • \$\begingroup\$ Wow, this has to be the coolest thing I've seen all week. +++ \$\endgroup\$ Commented Aug 6, 2015 at 14:56

1 Answer 1

3
\$\begingroup\$

I think I found a bug:

pattern << $_.chars until gets == ''

The problem here is that gets cannot return an empty string, so this probably does not behave as you think it should.

Look at this REPL session:

irb(main):003:0> gets
=> "\n"

I think that just adding .chomp after gets should fix this.

answered Oct 31, 2015 at 19:33
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.