how about Array#collect_until

T

timr

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.
 
T

timr

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
 
R

Robert Klemme

2010/8/27 timr said:
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]

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
 
S

Steven D. Arnold

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 =3D=3D 0}) {|item| item + 10}

You could define collect_until like this:

class Array
def collect_until(fn, &block)
result =3D []
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
 
F

F. Senault

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
 
T

timr

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}




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
  defcollect_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
--
Actual dialogue from the end of the battle:
Cleric:  "Keep hitting it!"
Paladin: "It's dead."
Fighter: "Yeah, but it's not dead enough."  (David P. Murphy in the SDM)
 
R

Robert Klemme

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

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]
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}

Kind regards

robert
 
C

Christopher Dicely

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.

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]
 
B

botp

module Enumerable
=A0def through
=A0 =A0Enumerator.new do |yielder|
=A0 =A0 =A0self.each do |val|
=A0 =A0 =A0 =A0yielder.yield(val)
=A0 =A0 =A0 =A0break if yield val
=A0 =A0 =A0end
=A0 =A0end
=A0end
end

nice. i'd add that to my lib if you don't mind ;-)
now i can do eg,
[1,2,3].through{|x| x%2=3D=3D0}.with_index.map{|x,i| [x,i,x+i]}
#=3D> [[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
 
T

timr

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.
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.
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}

Kind regards

        robert
 
T

timr

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]

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]
 
C

Christopher Dicely

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,
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,145
Messages
2,570,828
Members
47,374
Latest member
anuragag27

Latest Threads

Top