I have a very large range to iterate through and find the first element that satisfies specific constraints. That can be done efficiently in Ruby already.
# Runs until memory is exhausted _without_ lazy!
(1..).lazy.select { |i| i > 5 }.first
# => 6
In my use-case however, I want to begin iteration at a random interval of the range and, if no element passes the check when reaching the end of the range, continue from the start of the range (up until the random interval is reached again, if need be). With Combining two different 'ranges' to one in ruby as reference I came to...
letter = ('b'..'y').to_a.sample
[*letter..'z', *'a'...letter].map { |c| c.capitalize }.join
# => "FGHIJKLMNOPQRSTUVWXYZABCDE"
Of course, I don't have the alphabet as range to iterate through, this is just the small-scale example, which fails for my use-case.
* (splat) operator is not lazymap is not lazyWith some more googling and experimentation, I came to the following constructs:
# lazy version of previous alphabet example
[(letter..'z'), ('a'...letter)].lazy.flat_map { |r| r.each.lazy }.map { |c| c.capitalize }.force.join
=> "FGHIJKLMNOPQRSTUVWXYZABCDE"
# Comparable to what I want
start = rand(2**64)
# => 15282219649142738977
[(start..2**64), (0...start)].lazy.flat_map { |r| r.each.lazy }.select { |i| i % 7 == 0 }.first(5)
# => [15282219649142738978, 15282219649142738985, 15282219649142738992, 15282219649142738999, 15282219649142739006]
iter = [(start..2**64), (0...start)].lazy.flat_map { |r| r.each.lazy }.select { |i| i % 7 == 0 }
# => #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: [15282219649142738977..18446744073709551616, 0...15282219649142738977]>:flat_map>:select>
iter.next
# => 15282219649142738978
iter.next
# => 15282219649142738985
That does look overly complicated to me and maybe someone has a better idea?
Thank you for your time,
Xavier.
How to lazy concat Ruby ranges?
You can concatenate enumerators via +. A range is not an enumerator, but you can retrieve one via Range#each, for example:
enum = (-3..0).each + (1..)
The combined enumerator will iterate each of the concatenated enumerators:
enum.take(10)
#=> [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6]
I have a very large range to iterate through and find the first element that satisfies specific constraints
Ruby has a dedicated method Enumerable#find which does exactly this. It iterates the collection and returns the first element for which the block returns a truthy result (without iterating any further), e.g.
enum.find { |i| i > 5 }
#=> 6
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With