Take the last element of a lazy enumerator - ruby

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?

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

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.

Related

Unexpected output when using `select` [duplicate]

This question already has an answer here:
Ruby block and unparenthesized arguments
(1 answer)
Closed 2 years ago.
I'm going through a tutorial on Ruby that explains the neat construction the select method provides. As per that, the three print statements in the following code (filtering out even numbers) should produce an identical output:
numbers = (1..20).to_a
p numbers.select(&:even?)
p numbers.select { |x| x.even? }
p numbers.select do |x| x.even? end
When I run it, though, I get:
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
#<Enumerator: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]:select>
Clearly, the third statement is off, even though it's impossible to tell why. It's just the curly braces replaced with the do-end block so it shouldn't change anything.
I'm on Ruby 2.5 so I guess either the tutorial was running some other version and something has changed? Or maybe there's some subtlety here that I'm not able to put my finger on.
p numbers.select do |x| x.even? end
is called in this way
p(numbers.select) do |x| x.even? end
The block is passed to p, not to select as you'd expect, p just ignores it
In the second case this doesn't happen because the block with {} has higher precedence than the method call. Instead, block with do-end has lower precedence than the method call.
The second case looks like this instead
p(numbers.select { |x| x.even? })

Split array, average sub-arrays, return new array of averages?

Say that I have the array [35, 20, 15, 22, 18, 40, 16, 40]. 8 digits.
What I want is a 4 digit array where each digit is the result of averaging two digits in the first array.
So it'd be [(average of 35 and 20), (average of 15 and 22), (average of 18 and 40), (average of 16 and 40)].
I strongly suspected that there's some clever way to do this using each_slice, map, or inject, but can't quite guess at it.
You're quite right:
[35, 20, 15, 22, 18, 40, 16, 40]
.each_slice(2)
.map {|pair| pair.inject(:+) / pair.length.to_f }
each_slice(2) produces slices of up to 2 elements long, which you can then sum and divide by the length of the slice.
Assuming the array contains an even number of elements, which I believe is intended, here's a FORTRAN IV approach:
arr = [35, 20, 15, 22, 18, 40, 16, 40]
(0...arr.size).step(2).map { |i| (arr[i]+arr[i+1])/2.0 }
#=> [27.5, 18.5, 29.0, 28.0]
and another inspired by FORTH+:
def ex(op, v0, v1)
v1.send op, v0
end
a = arr.dup
stack = []
while a.any?
stack.unshift(a.pop)
stack.unshift(a.pop)
stack.unshift(:+)
stack.unshift(ex(stack.shift, stack.shift, stack.shift))
stack.unshift(2.0)
stack.unshift(:/)
stack.unshift(ex(stack.shift, stack.shift, stack.shift))
end
stack
#=> [27.5, 18.5, 29.0, 28.0]
Hmmm. Why did I think FORTH+1 was so cool? Leo Brodie's book Starting Forth probably had a lot to do with it.
1 Historical note: the Lotus 1-2-3 clone, VP-Planner, was written in FORTH by a friend, Kent Brothers.

efficiently finding overlapping segments from a set of lists

Suppose I have the following lists:
[1, 2, 3, 20, 23, 24, 25, 32, 31, 30, 29]
[1, 2, 3, 20, 23, 28, 29]
[1, 2, 3, 20, 21, 22]
[1, 2, 3, 14, 15, 16]
[16, 17, 18]
[16, 17, 18, 19, 20]
Order matters here. These are the nodes resulting from a depth-first search in a weighted graph. What I want to do is break down the lists into unique paths (where a path has at least 2 elements). So, the above lists would return the following:
[1, 2, 3]
[20, 23]
[24, 25, 32, 31, 30, 29]
[28, 29]
[20, 21, 22]
[14, 15, 16]
[16, 17, 18]
[19, 20]
The general idea I have right now is this:
Look through all pairs of lists to create a set of lists of overlapping segments at the beginning of the lists. For example, in the above example, this would be the output:
[1, 2, 3, 20, 23]
[1, 2, 3, 20]
[1, 2, 3]
[16, 17, 18]
The next output would be this:
[1, 2, 3]
[16, 17, 18]
Once I have the lists from step 2, I look through each input list and chop off the front if it matches one of the lists from step 2. The new lists look like this:
[20, 23, 24, 25, 32, 31, 30, 29]
[20, 23, 28, 29]
[20, 21, 22]
[14, 15, 16]
[19, 20]
I then go back and apply step 1 to the truncated lists from step 3. When step 1 doesn't output any overlapping lists, I'm done.
Step 2 is the tricky part here. What's silly is it's actually equivalent to solving the original problem, although on smaller lists.
What's the most efficient way to solve this problem? Looking at all pairs obviously requires O(N^2) time, and step 2 seems wasteful since I need to run the same procedure to solve these smaller lists. I'm trying to figure out if there's a smarter way to do this, and I'm stuck.
Seems like the solution is to modify a Trie to serve the purpose. Trie compression gives clues, but the kind of compression that is needed here won't yield any performance benefits.
The first list you add becomes it's own node (rather than k nodes). If there is any overlap, nodes split but never get smaller than holding two elements of the array.
A simple example of the graph structure looks like this:
insert (1,2,3,4,5)
graph: (1,2,3,4,5)->None
insert (1,2,3)
graph: (1,2,3)->(4,5), (4,5)->None
insert (3,2,3)
graph: (1,2,3)->(4,5), (4,5)->None, (3,32)->None
segments
output: (1,2,3), (4,5), (3,32)
The child nodes should also be added as an actual Trie, at least when there are enough of them to avoid a linear search when adding/removing from the data structure and potentially increasing the runtime by a factor of N. If that is implemented, then the data structure has the same big O performance as a Trie with a somewhat higher hidden constants. Meaning that it takes O(L*N), where L is the average size of the list and N is the number of lists. Obtaining the segments is linear in the number of segments.
The final data structure, basically a directed graph, for your example would looks like below, with the start node at the bottom.
Note that this data structure can be built as you run the DFS rather than afterwords.
I ended up solving this by thinking about the problem slightly differently. Instead of thinking about sequences of nodes (where an edge is implicit between each successive pair of nodes), I'm thinking about sequences of edges. I basically use the algorithm I posted originally. Step 2 is simply an iterative step where I repeatedly identify prefixes until there are no more prefixes left to identify. This is pretty quick, and dealing with edges instead of nodes really simplified everything.
Thanks for everyone's help!

How to remove outer array from a nested array?

If I have the following arr = [13,12,31,31] Now say I want to push in another set of numbers like 12,13,54,32
So I can do arr << [12,13,54,32] but now I have [13,12,31,31,[12,13,54,32]]
So how can I remove the outside array? arr = arr.pop works sometimes but I'm guessing that a better way exists. Please enlighten.
Don't use <<, use +
arr = [13,12,31,31]
arr += [12,13,54,32]
# arr => [13,12,31,31,12,13,54,32]
You should use Array#flatten
[[13,12,31,31,12,13,54,32]].flatten # => [13, 12, 31, 31, 12, 13, 54, 32]
You have a couple options. You could join your arrays using the + operator and not have to deal with the outer array. If you have an outer array and want to flatten it, simply call flatten on the array. As matt mentioned in the comments above, you can also use concat.
# Creates a new array
[13,12,31,31] + [12,13,54,32]
=> [13, 12, 31, 31, 12, 13, 54, 32]
# Creates a new array, unless you use flatten!
[13, 12, 31, 31, [12, 13, 54, 32]].flatten
=> [13, 12, 31, 31, 12, 13, 54, 32]
# Modifies the original array
[13,12,31,31].concat([12,13,54,32])
=> [13, 12, 31, 31, 12, 13, 54, 32]

Is it possible to put an "if statement" inside a "N.times{}" bracket in Ruby?

IN RUBY,
for example, if you wanted to print the first 25 integers, and put ", " between the 1st and last integers in the output (to clean it up a little bit), could you write something like the code below? (I deliberately didn't use while/for loops.)
N = 25
N.times{|i| print("#{i}")
if i > 0 and i < 25
print ", "
}
If this has already been answered, please redirect me to the old post? Thanks :)
Yes. Though the easiest way to answer these questions is to try them in irb. There are some small problems with your counting logic.
25.times do |i|
print "#{i}"
if i >= 0 and i < 24
print ","
end
end
I realize this is just an example, but it's worth noting that what you would actually want to use for this is Array#join:
irb(main):001:0> [*0..25].join(", ")
=> "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"
Josh Y.'s join example is nicer, but here's your method reduced to a single line.
N.times {|i| print"#{i}#{', ' if i>=0 and i<N-1}"}

Categories

Resources