Want elegant code version

I converted a Ruby method into Crystal, but it’s soooo inelegant compared to the Ruby version. Can some suggest a more Crystal elegant way to do it.

Ruby code

def zeckendorf
  return to_enum(__method__) unless block_given?
  x = 0
  loop do
    bin = x.to_s(2)
    yield bin unless bin.include?("11") 
    x += 1
  end
end
 
zeckendorf.take(21).each_with_index{|x,i| puts "%3d: %8s"% [i, x]}

My functional, but inelegant, Crystal translatioin.

def zeckendorf
  #return to_enum(__method__) unless block?
  x = 0
  i = 0  # added
  loop do
    bin = x.to_s(2)
    #yield bin unless bin.includes?("11")
    yield bin, i += 1 unless bin.includes?("11")
    x += 1
  end
end

#zeckendorf.take(21).each_with_index{|x,i| puts "%3d: %8s"% [i, x]}
zeckendorf{ |num, i| puts "%3d: %8s"% [i, num]; (break if i > 20) }

Here’s how the stdlib does this kind of thing. A method that yields and another that returns an iterator:

def zeckendorf
  x = 0
  loop do
    bin = x.to_s(2)
    yield bin unless bin.includes?("11")
    x += 1
  end
end

class ZeckendorfIterator
  include Iterator(String)

  def initialize
    @x = 0
  end

  def next
    bin = @x.to_s(2)
    @x += 1
    while bin.includes?("11")
      bin = @x.to_s(2)
      @x += 1
    end
    bin
  end
end

def zeckendorf
  ZeckendorfIterator.new
end

zeckendorf.first(21).each_with_index{|x,i| puts "%3d: %8s"% [i, x]}
1 Like

Thank you @Exilor this is exactly what I was looking for.
Now I just have to remember what to do going forward. :thinking:

class ZeckendorfIterator
  include Iterator(String)

  def initialize
    @x = 0
  end

  def next
    bin = @x.to_s(2)
    @x += 1
    while bin.includes?("11")
      bin = @x.to_s(2)
      @x += 1
    end
    bin
  end
end

def zeckendorf(n)
  ZeckendorfIterator.new.first(n)
end

zeckendorf(21).each_with_index{|x,i| puts "%3d: %8s"% [i, x]}

You don’t necessarily need a custom iterator for this.

0.step(by: 1).map(&.to_s(2)).select{|bin| !bin.includes?("11")}.first(21)
2 Likes

Alternatives:

# shorter:
0.step(by: 1).map(&.to_s(2)).reject(&.includes?("11")).first(21)
# faster:
0.step(by: 1).compact_map{|x| bin = x.to_s(2); bin unless bin.includes?("11")}.first(21)
3 Likes

So tasteful, I love this kind of code. It’s something that always drew me into ruby, the way it gives off those magical lisp-esque vibes.

How do you generate output?

You can chain the same each_with_index call as in your example.

Yeh, I figured it out after asking the question.

0.step(by: 1).map(&.to_s(2)).reject(&.includes?("11")).first(21).each_with_index{|x,i| puts "%3d: %8s"% [i, x]}

or better

def zeckendorf(n)
  0.step.map(&.to_s(2)).reject(&.includes?("11")).first(n)
end

zeckendorf(21).each_with_index{ |x,i| puts "%3d: %8s"% [i, x] }