Project

General

Profile

Actions

Feature #14781

closed

Enumerator.generate

Feature #14781: Enumerator.generate

Added by zverok (Victor Shepelev) over 7 years ago. Updated almost 5 years ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:87222]

Description

This is alternative proposal to Object#enumerate (#14423), which was considered by many as a good idea, but with unsure naming and too radical (Object extension). This one is less radical, and, at the same time, more powerful.

Synopsys:

  • Enumerator.generate(initial, &block): produces infinite sequence where each next element is calculated by applying block to previous; initial is first sequence element;
  • Enumerator.generate(&block): the same; first element of sequence is a result of calling the block with no args.

This method allows to produce enumerators replacing a lot of common while and loop cycles in the same way #each replaces for.

Examples:

With initial value

# Infinite sequence p Enumerator.generate(1, &:succ).take(5) # => [1, 2, 3, 4, 5] # Easy Fibonacci p Enumerator.generate([0, 1]) { |f0, f1| [f1, f0 + f1] }.take(10).map(&:first) #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] require 'date' # Find next Tuesday p Enumerator.generate(Date.today, &:succ).detect { |d| d.wday == 2 } # => #<Date: 2018-05-22 ((2458261j,0s,0n),+0s,2299161j)> # Tree navigation # --------------- require 'nokogiri' require 'open-uri' # Find some element on page, then make list of all parents p Nokogiri::HTML(open('https://www.ruby-lang.org/en/')) .at('a:contains("Ruby 2.2.10 Released")') .yield_self { |a| Enumerator.generate(a, &:parent) } .take_while { |node| node.respond_to?(:parent) } .map(&:name) # => ["a", "h3", "div", "div", "div", "div", "div", "div", "body", "html"] # Pagination # ---------- require 'octokit' Octokit.stargazers('rails/rails') # ^ this method returned just an array, but have set `.last_response` to full response, with data # and pagination. So now we can do this: p Enumerator.generate(Octokit.last_response) { |response| response.rels[:next].get # pagination: `get` fetches next Response } .first(3) # take just 3 pages of stargazers .flat_map(&:data) # `data` is parsed response content (stargazers themselves) .map { |h| h[:login] } # => ["wycats", "brynary", "macournoyer", "topfunky", "tomtt", "jamesgolick", ... 

Without initial value

# Random search target = 7 p Enumerator.generate { rand(10) }.take_while { |i| i != target }.to_a # => [0, 6, 3, 5,....] # External while condition require 'strscan' scanner = StringScanner.new('7+38/6') p Enumerator.generate { scanner.scan(%r{\d+|[-+*/]}) }.slice_after { scanner.eos? }.first # => ["7", "+", "38", "/", "6"] # Potential message loop system: Enumerator.generate { Message.receive }.take_while { |msg| msg != :exit } 

Reference implementation: https://github.com/zverok/enumerator_generate

I want to thank all peers that participated in the discussion here, on Twitter and Reddit.


Files

enumerator_from.rb (3.16 KB) enumerator_from.rb knu (Akinori MUSHA), 10/18/2018 02:12 PM
enumerator-generate.patch (4.44 KB) enumerator-generate.patch zverok (Victor Shepelev), 08/11/2019 10:29 AM
0001-Implement-Enumerator.produce-Feature-14781.patch (4.47 KB) 0001-Implement-Enumerator.produce-Feature-14781.patch knu (Akinori MUSHA), 08/29/2019 11:06 AM

Related issues 2 (2 open0 closed)

Actions

Also available in: PDF Atom