How about Array#collect_until

I am wondering if anyone has implemented an Array#collect_until method
to break out of a collect loop after an end-point has been achieved
during an Array#collect call.

For instance, you have a long array of items and want to use each item
to calculate something, but should the calculation give a specific
result, then you know you are done–no need for processing the rest of
the array. However, if that specified result is not met, the
processing continues. Something like below:

[1,2,3].collect{|item| item + 10} # => [11,12,13]
[1,2,3].collect_until({|x| x%2 == 0}) {|item| item + 10} # => [11,12]

To get this to work the result from the yield to {|item| item + 10}
block would be run through the {|x| x%2 == 0} block to check if we are
done.

Thanks for any suggestions.

2010/8/27 timr [email protected]:

[1,2,3].collect{|item| item + 10} # => [11,12,13]
[1,2,3].collect_until({|x| x%2 == 0}) {|item| item + 10} # => [11,12]

This would not work - it does not even parse

irb(main):004:0> 09:04:52 ~$ ruby19 -ce ‘[1,2,3].collect_until({|x|
x%2 == 0}) {|item| item + 10}’
-e:1: syntax error, unexpected ‘|’, expecting ‘}’
[1,2,3].collect_until({|x| x%2 == 0}) {|item| item + 10}
^
-e:1: syntax error, unexpected ‘}’, expecting $end
[1,2,3].collect_until({|x| x%2 == 0}) {|item| item + 10}
^
09:04:59 ~$

You could at least have to use ‘lambda’ or ‘proc’:

09:04:59 ~$ ruby19 -ce ‘[1,2,3].collect_until(lambda {|x| x%2 == 0})
{|item| item + 10}’
Syntax OK

To get this to work the result from the yield to {|item| item + 10}
block would be run through the {|x| x%2 == 0} block to check if we are
done.

You can use #inject for this:

irb(main):002:0> [1,2,3].inject([]){|a,x| a << (x+10); break a if x % 2
== 0;a}
=> [11, 12]

It also works if the condition never fires:

irb(main):003:0> [1,3,5].inject([]){|a,x| a << (x+10); break a if x % 2
== 0;a}
=> [11, 13, 15]

Kind regards

robert

Here is an awful hacked solution…perhaps someone come up with a
prettier solution

class Array
def try(something)
contents = self.dup
while contents.length != 0
item = contents.shift
result = yield(item)
if something.call(result)
p result
break
else
p result
end
end
end
end

%w[ find out if that this but not the other mother
mouth].try(Proc.new {|word| word[-2,2] == ‘st’ }){|item| item + “t” }

puts “another attempt”

[1,6,7,5,3,0,10,12,14].try(Proc.new {|num| num > 25 }){|item| item *
4 }

puts “another attempt”

[1,2,3].try(Proc.new {|x| x%2 == 0}) {|item| item + 10}

RESULTS:

“findt”
“outt”
“ift”
“thatt”
“thist”
another attempt
4
24
28
another attempt
11
12

On Aug 27, 2010, at 1:24 AM, Yukihiro M. wrote:

? Ah, collect_until seems inclusive, but I think it shouldn’t.

I think he was thinking of something more like collect_unless, a
combination of filtering and collecting.

steven

Hi,

In message “Re: how about Array#collect_until”
on Fri, 27 Aug 2010 15:32:29 +0900, timr [email protected]
writes:

|[1,2,3].collect{|item| item + 10} # => [11,12,13]
|[1,2,3].collect_until({|x| x%2 == 0}) {|item| item + 10} # => [11,12]

How about using

[1,2,3].take_while{|x| x%2 != 0}.collect{|item| item + 10}

? Ah, collect_until seems inclusive, but I think it shouldn’t.

          matz.

Le 27 août à 08:29, timr a écrit :

[1,2,3].collect{|item| item + 10} # => [11,12,13]
[1,2,3].collect_until({|x| x%2 == 0}) {|item| item + 10} # => [11,12]

Why not something like this :

class Array
def collect_until
n = []
self.each do |e|
r, brk = yield e
n << r
break if brk
end
n
end
end

Used like this :

[1,2,3].collect_until { |x| [ x + 10 , (x % 2 == 0) ] }
=> [11, 12]

Not very pretty, but it does the job.

Fred

You can’t pass an anonymous function using the syntax you’re using. You
can pass it as a lambda, something like:

[1,2,3].collect_until(lambda {|x| x%2 == 0}) {|item| item + 10}

You could define collect_until like this:

class Array
def collect_until(fn, &block)
result = []
self.each do |x|
if !fn.call(x)
result << (yield x)
else
break
end
end
result
end
end

I think the actual result would be only [11], since the collection would
proceed UNTIL the value mod 2 was zero, which would happen at 2.
Therefore the processing would stop then.

steven

Thanks everyone for the great ideas. I ended up slightly modifying
Senault’s suggestion (see below), though I learned from all the
various approaches. In my particular case I do need an inclusive
collect (long story, but using this to do some DNA fuzzy expression
matching that smartly knows when it has found a good solution and
breaks rather than finishing all the other possible calculations, and
I need to see the solution). And though the inject is a great
suggestion, I would prefer keeping the code blocks for determining
endpoint grammatically separated from the processing block, rather
than embedded.
Arigato Ruby Community,
Tim

#collects items processed through block until a solution satisfies the
function test (lambda{})
class Array
def collect_until(fn, &block)
results = []
self.each do |x|
result = (yield x)
results << result
break if fn.call(result)
end
results
end
end
p %w[ find out if that this but not the other mother
mouth].collect_until(lambda {|word| word[-2,2] == ‘st’ }){|item| item

  • “t” }

puts “another attempt”

p [1,6,7,5,3,0,10,12,14].collect_until(Proc.new {|num| num > 25 }){|
item| item * 4 }

puts “another attempt”
p [1,2,3].collect_until(Proc.new {|x| x%2 == 0}) {|item| item + 10}

On Thu, Aug 26, 2010 at 11:32 PM, timr [email protected] wrote:

[1,2,3].collect{|item| item + 10} # => [11,12,13]
[1,2,3].collect_until({|x| x%2 == 0}) {|item| item + 10} # => [11,12]

To get this to work the result from the yield to {|item| item + 10}
block would be run through the {|x| x%2 == 0} block to check if we are
done.

Thanks for any suggestions.

I know you already got something that works, but I think this is a
problem where enumerators provide an elegant solution:

module Enumerable
def through
Enumerator.new do |yielder|
self.each do |val|
yielder.yield(val)
break if yield val
end
end
end
end

then:

[1,2,3].through {|x| x % 2 == 0}.collect {|item| item+10} => [11,12]

On 27.08.2010 21:08, timr wrote:

Arigato Ruby Community,
Tim

#collects items processed through block until a solution satisfies the
function test (lambda{})
class Array

I would rather place this code in Enumerable. Then all other classes
mixing in this module will benefit.

def collect_until(fn,&block)
results = []
self.each do |x|

Why does everybody write “self.each” when “each” is sufficient?

   result = (yield x)
   results<<  result
   break if fn.call(result)

As far as I can see this is not in line with your original requirement
because in your original posting you evaluated the termination condition
on the original value while this code does it on the result of the
mapping calculation.

If you change that you don’t need to store “result”. You can also use
“return” instead of “break” here for a more immediate exit:

return results if fc[x]

p [1,6,7,5,3,0,10,12,14].collect_until(Proc.new {|num| num> 25 }){|
item| item * 4 }

puts “another attempt”
p [1,2,3].collect_until(Proc.new {|x| x%2 == 0}) {|item| item + 10}

Kind regards

robert

I would rather place this code in Enumerable. Then all other classes
mixing in this module will benefit.

Good idea.

def collect_until(fn,&block)
results = []
self.each do |x|

Why does everybody write “self.each” when “each” is sufficient?

self.sorry, er, I mean sorry. No you’re right. It is overly explicit.

   result = (yield x)
   results<<  result
   break if fn.call(result)

As far as I can see this is not in line with your original requirement
because in your original posting you evaluated the termination condition
on the original value while this code does it on the result of the
mapping calculation.

From the original posting:
[1,2,3].collect_until({|x| x%2 == 0}) {|item| item + 10} # => [11,12]
To get this to work the result from the yield to {|item| item + 10}
block would be run through the {|x| x%2 == 0} block to check if we
are
done.

So, I was shooting for a method that would send to block, take the
result and check it against fn and quit if condition was met. With the
call made as [].collect_until(fn,&block). I am trying to save
processing from sending any more items than are required to a
calculation intensive block.

The other tests may help clarify the expected behavior. In each case
the method should result in a collection that DOES NOT INCLUDE A
RESULT FOR EVERY ITEM in the original set since the condition is met
part way through and break is executed. Therefore, it provides the
most a collection of calculations upto and including the solution when
an end condition was met.

end

end
end

then:

[1,2,3].through {|x| x % 2 == 0}.collect {|item| item+10} => [11,12]

Thanks for another useful answer.

I need to read the doc for Enumerator class since I don’t really know
how to use it, but your solution appears to run each item into the
first block, until it finds an item producing a true result and then
everything up to that point is sent on to a collect statement for
processing in the second block. I am sure this is going to be useful
in many cases, but it is different from what I was intending for the
collect_until function. You will see how our two solutions differ if
you look at the test cases and the results below.

%w[ find out if that this but not the other mother
mouth].collect_until(lambda {|word| word[-2,2] == ‘st’ }){|item| item

  • “t” } # => [“findt”, “outt”, “ift”, “thatt”, “thist”]

[1,6,7,5,3,0,10,12,14].collect_until(Proc.new {|num| num > 25 }){|
item| item * 4 } # => [4, 24, 28]

[1,2,3].collect_until(Proc.new {|x| x%2 == 0}) {|item| item + 10} # =>
[11, 12]

Versus:
%w[ find out if that this but not the other mother mouth].through {|
word| word[-2,2] == ‘st’ }.collect{|item| item + “t” } # => [“findt”,
“outt”, “ift”, “thatt”, “thist”, “butt”, “nott”, “thet”, “othert”,
“mothert”, “moutht”]

[1,6,7,5,3,0,10,12,14].through {|num| num > 25 }.collect{|item| item *
4 } # => [4, 24, 28, 20, 12, 0, 40, 48, 56]

[1,2,3].through {|x| x%2 == 0}.collect{|item| item + 10} # => [11, 12]

On Fri, Aug 27, 2010 at 9:40 PM, timr [email protected] wrote:

The other tests may help clarify the expected behavior. In each case
the method should result in a collection that DOES NOT INCLUDE A
RESULT FOR EVERY ITEM in the original set since the condition is met
part way through and break is executed. Therefore, it provides the
most a collection of calculations upto and including the solution when
an end condition was met.

Okay… I misunderstood the intent. So you can’t use the
Enumerable#through method I proposed upthread quite as simply, because
you have to reverse the order of the filtering and transforming
operations, and the existing transforming operation for enumerables
(Enumerable#map) produces an array, not an Enumerator, and
Kernel#enum_for won’t pass a block to map (or any other method) to
create an Enumerator that uses the block.

But its easy to create a general-purpose map-like enumerator method.

module Enumerable
def transform
Enumerator.new do |yielder|
self.each { |val| yielder << yield(val) }
end
end
end

Then you can just chain this Enumerable#transform in front of the
Enumerable#through I proposed upthread,

On Sat, Aug 28, 2010 at 4:15 AM, Christopher D. [email protected]
wrote:

module Enumerable
def through
Enumerator.new do |yielder|
self.each do |val|
yielder.yield(val)
break if yield val
end
end
end
end

nice. i’d add that to my lib if you don’t mind :wink:
now i can do eg,
[1,2,3].through{|x| x%2==0}.with_index.map{|x,i| [x,i,x+i]}
#=> [[1, 0, 1], [2, 1, 3]]

i wonder why ruby’s take_while defaulted to array when in blocked
form…

thanks and best regards -botp

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs