Reputation: 7815
I have a function like this:
(0..Float::INFINITY).lazy.take_while {|n|(n**2+ 1*n+41).prime?}.force[-1]
I'm using this as an optimisation exercise. This works fine, but it has a memory order O(n) as it will create the entire array and then take the last element.
I am trying to get this without building the entire list, hence the lazy enumerator. I can't think of anything other than using a while
loop.
(0..Float::INFINITY).lazy.take_while {|n|(n**2+ 1*n+41).prime?}.last.force
Is there a way to do this in space order O(1) rather than O(n) with enumerators?
EDIT: lazy isn't necessary here for the example to work, but I thought it might be more useful to reduce the space complexity of the function?
Upvotes: 4
Views: 710
Reputation: 20857
Solution
Use inject to hold on to the current value instead of building an array.
(0..Float::INFINITY).lazy.take_while {|n|(n**2+ 1*n+41).prime?}.inject{|acc,n| n }
Note that you must use lazy
otherwise inject
will build an intermediate array.
Verifying
To see what happens if you don't use lazy, run the following after restarting ruby & running the non-lazy version. It will return arrays that "look like" the intermediate array.
ObjectSpace.enum_for(:each_object, Array).each_with_object([]) {|e, acc|
acc << e if e.size == 40 and e.first == 0
}
The non-lazy version will return:
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]]
Re-doing the test with lazy will return an empty array.
Upvotes: 1
Reputation: 110675
If you just don't want to save the entire array:
(0..1.0/0).find {|n| !(n**2+n+41).prime?} - 1
1.0/0
is the same as Float::INFINITY
. I used it in case you hadn't seen it. So far as I know, neither is preferable.
My first thought clearly was overkill:
def do_it
e = (0..1.0/0).to_enum
loop do
n = e.peek
return e.inspect unless (n**2+n+41).prime?
e.next
end
end
do_it
Upvotes: 2