Skip to content

Instantly share code, notes, and snippets.

@zobar
Last active August 29, 2015 13:57
Show Gist options
  • Save zobar/9607820 to your computer and use it in GitHub Desktop.
Save zobar/9607820 to your computer and use it in GitHub Desktop.
Look and say, ruby & clojure
(ns look-and-say)
(defn- consecutive [string]
(map first (re-seq #"(.)\1*" string)))
(defn- say [chars]
[(count chars) (first chars)])
(defn- get-next [string]
(apply str (mapcat say (consecutive string))))
(defn look-and-say [string]
(iterate get-next string))
; (def las (look-and-say/look-and-say "1"))
; (take 2 las)
; => ("1" "11")
; (take 2 las)
; => ("1" "11")
module LookAndSay
class << self
def look_and_say(string)
Enumerator.new do |yielder|
current = string
loop do
yielder << current
current = get_next current
end
end
end
private
def consecutive(string)
string.scan(/((.)\2*)/).map &:first
end
def say(chars)
[chars.length, chars[0]]
end
def get_next(string)
consecutive(string).map(&method(:say)).join ''
end
end
end
# las = LookAndSay.look_and_say '1'
# las.take 2
# => ["1", "11"]
# las.take 2
# => ["1", "11"]
@zobar
Copy link
Author

zobar commented Mar 17, 2014

clojure.core/iterate returns a lazy sequence of x, (f x), (f (f x)) etc. A Clojure sequence is rougly equivalent to a Ruby Enumerable.

@zobar
Copy link
Author

zobar commented Mar 17, 2014

Enumerable#take is documented as returning the first n elements; in the case of Enumerator, it appears to return the next n elements instead.

@mark
Copy link

mark commented Mar 17, 2014

Honestly, it depends on what you’re trying to do. When you call #each with a block, it consumes the entire collection, so it’s not good if the collection is infinite, or if you want more control over when successive elements are generated.

But if you want to be most in line with StdLib, then do both: if a block is passed in to your #each, explicitly yield to that block for every element. If no block is passed in, then return an enumerator, and the caller can decide how to handle it. But… I think most of the time people don’t do that, ‘cause they don’t need to.

@mark
Copy link

mark commented Mar 17, 2014

Ah... heh. Well, for my solution, yeah, #take doesn't work perfectly. There's a slightly longer way to write it where it works correctly, but harder to fit into a tweet:

def iterate(meth, arg)
  Enumerator.new do |yielder|
    current = arg
    loop do
      yielder << current
      current = send(meth, current)
    end
  end
end

By reassigning the method parameter, it prevents #take from working correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment