Skip to content

Instantly share code, notes, and snippets.

@zverok
Created December 15, 2019 11:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zverok/d7ca6b966d56ddd313fbf4bbaeab25f6 to your computer and use it in GitHub Desktop.
Save zverok/d7ca6b966d56ddd313fbf4bbaeab25f6 to your computer and use it in GitHub Desktop.
# Just porting examples of .unfold usability from
# http://weblog.raganwald.com/2007/11/really-simple-anamorphisms-in-ruby.html
# http://weblog.raganwald.com/2007/11/really-useful-anamorphisms-in-ruby.html
# ...to Ruby 2.7+ Enumerator.produce (and numbered block args):
# Simple examples: http://weblog.raganwald.com/2007/11/really-simple-anamorphisms-in-ruby.html
# 10.unfold { |n| n-1 unless n == 1 }.inspect => [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
p Enumerator.produce(10) { _1 - 1 }.take_while { _1 >= 1 }
#=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
# 10.class.unfold(&:superclass).inspect => [Fixnum, Integer, Numeric, Object]
p Enumerator.produce(10.class, &:superclass).take_while(&:itself)
#=> [Integer, Numeric, Object, BasicObject]
# 5.unfold(&'_-1 unless _==1').inject(&'*') => 120
Enumerator.produce(5) { _1 - 1 }.take_while { _1 != 1 }.inject(:*)
#=> 120
# More advanced examples: http://weblog.raganwald.com/2007/11/really-useful-anamorphisms-in-ruby.html
# 1.unfold(:to => '==10', :map => '**2', &'_+1')
# => [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
p Enumerator.produce(1) { _1 + 1 }.take_while { _1 <= 10 }.map { _1**2 }
#=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# def zip(*lists)
# lists.unfold(
# :while => '.first',
# :map => '.map(&".first")',
# &'_.reject(&".length < 2").map(&"[1..-1]")')
# end
# zip([:a, :b, :c], [1, 2, 3])
# => [[:a, 1], [:b, 2], [:c, 3]]
def zip(*lists)
Enumerator
.produce(lists) { |list| list.reject { _1.length < 2 }.map { _1[1..-1] } }
.take_while(&:first)
.map { _1.map(&:first) }
end
p zip([:a, :b, :c], [1, 2, 3])
#=> [[:a, 1], [:b, 2], [:c, 3]]
# def flatten(arr)
# arr.unfold(
# :while => '.first',
# :map => lambda { |first|
# first = first.first while first.kind_of?(Array)
# first
# }
# ) { |state|
# state = state.first + state[1..-1] while state.first.kind_of?(Array)
# state[1..-1]
# }
# end
#
# flatten([[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10])
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def flatten(arr)
Enumerator.produce(arr) { |state|
state = state.first + state[1..-1] while state.first.kind_of?(Array)
state[1..-1]
}
.take_while(&:first)
.map { |first|
first = first.first while first.kind_of?(Array)
first
}
end
p flatten([[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10])
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# NB: that flatten above is a direct port. But the thing is, while cycles are ALSO expressable
# with Enumerator.produce, so we can get one step more...
# Just one ugly thing: there is no way to tell take_while to include the object where condition
# matched, so we fall back to slice_after{}.first instead.
def flatten2(arr)
Enumerator.produce(arr) { |state|
Enumerator.produce(state) { _1.first + _1[1..-1] }
.slice_after { !_1.first.kind_of?(Array) }.first.last[1..-1]
}
.take_while(&:first)
.map { |first|
Enumerator.produce(first, &:first).slice_after { !_1.kind_of?(Array) }.first.last
}
end
p flatten2([[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10])
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment