-
-
Save zobar/9607820 to your computer and use it in GitHub Desktop.
(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"] |
Enumerable#take
is documented as returning the first n elements; in the case of Enumerator
, it appears to return the next n elements instead.
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.
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.
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.