inject's pathological case...

  • Thread starter Just Another Victim of the Ambient Morality
  • Start date
J

Just Another Victim of the Ambient Morality

I'm not sure how I feel about inject's pathological case. What do you
all think should happen in the following code?


[2].inject {|a, i| puts i}


I might have thought that the block would never be called and the method
would return 2.
If the block must be called, I might expect "i" to be nil.
What actually happens is that the block is called and "i" is the same
thing as "a". I did not expect this and I'm trying to figure out how this
is either consistent or useful. Can someone clue me in to this behaviour?
Thank you...
 
7

7stud --

Just said:
I'm not sure how I feel about inject's pathological case. What do you
all think should happen in the following code?


[2].inject {|a, i| puts i}


I might have thought that the block would never be called and the
method
would return 2.
If the block must be called, I might expect "i" to be nil.

What actually happens is that the block is called and "i" is the
same
thing as "a". I did not expect this and I'm trying to figure out how
this
is either consistent or useful. Can someone clue me in to this
behaviour?

I'm not seeing that:

1) Two element array:

result = [2, 3].inject do |a, i|
puts "a:#{a}"
puts "i:#{i}"
puts "hello"
puts "--------"
end

puts "result=#{result}"
if result.nil?
puts 'yes'
end

--output:--
a:2
i:3
hello
--------
result=
yes


2) One element array:

result = [2].inject do |a, i|
puts "a:#{a}"
puts "i:#{i}"
puts "hello"
puts "--------"
end

puts "result=#{result}"
if result.nil?
puts 'yes'
end

--output:--
result=2
 
7

7stud --

Oh, yeah:

$ ruby -v
ruby 1.8.2 (2004-12-25) [universal-darwin8.0]


By the way, using inject() is inefficient--not to mention confusing.
You might as well pretend it doesn't exist.
 
D

David A. Black

Hi --

I'm not sure how I feel about inject's pathological case. What do you
all think should happen in the following code?


[2].inject {|a, i| puts i}


I might have thought that the block would never be called and the method
would return 2.
If the block must be called, I might expect "i" to be nil.
What actually happens is that the block is called and "i" is the same
thing as "a". I did not expect this and I'm trying to figure out how this
is either consistent or useful. Can someone clue me in to this behaviour?
Thank you...

Actually the block isn't being called:

irb(main):005:0> [2].inject {|a, i| puts "hi" }
=> 2
irb(main):006:0> [2].inject {}
=> 2

The value of the whole statement is 2, or, more generally, the first
(and only) element in the enumerable. I guess that's all it can do
since it doesn't have enough elements to call the block even once.


David

--
Rails training from David A. Black and Ruby Power and Light:
ADVANCING WITH RAILS April 14-17 New York City
INTRO TO RAILS June 9-12 Berlin
ADVANCING WITH RAILS June 16-19 Berlin
See http://www.rubypal.com for details and updates!
 
7

7stud --

David said:
Actually the block isn't being called:
I guess that's all it can do
since it doesn't have enough elements to call the block even once.

To add to that, on p.456 of pickaxe2 it says that if you don't supply an
argument for the inject call, then the first element in the enumerable
becomes 'a', and it is not included in subsequent iteration.
Apparently, because there are no values to iterate over, the block does
not execute.
 
W

William James

Oh, yeah:

$ ruby -v
ruby 1.8.2 (2004-12-25) [universal-darwin8.0]

By the way, using inject() is inefficient--not to mention confusing.
You might as well pretend it doesn't exist.

There is some truth to that.


def collect_repeats_inject list
return [] if [] == list
list[1..-1].inject([[ list.first ]]){|a,e|
if a[-1][0] == e
a[-1] << e
else
a << [e]
end
a
}.reject{|lst| lst.size < 2 }
end

def collect_repeats list
accum = [ [ list.shift ] ]
list.each{|e|
if accum[-1][0] == e
accum[-1] << e
else
accum << [e]
end }
accum.reject{|lst| lst.size < 2 }
end

p collect_repeats( %w(0 1 1 2 3 3 3 3 4 5 5 6) )
p collect_repeats( [] )
p collect_repeats_inject( %w(0 1 1 2 3 3 3 3 4 5 5 6) )
p collect_repeats_inject( [] )

the_list = %w(0 1 1 2 3 3 3 3 3 3 3 3 4 5 5 6 7 8 8 9 9 9)

t = Time.now
9999.times{ collect_repeats_inject( the_list )}
p Time.now - t
t = Time.now
9999.times{ collect_repeats( the_list )}
p Time.now - t

--- output ---
[["1", "1"], ["3", "3", "3", "3"], ["5", "5"]]
[]
[["1", "1"], ["3", "3", "3", "3"], ["5", "5"]]
[]
2.694
0.16


And the version without inject is shorter and
clearer.
 
W

William James

Oh, yeah:

$ ruby -v
ruby 1.8.2 (2004-12-25) [universal-darwin8.0]

By the way, using inject() is inefficient--not to mention confusing.
You might as well pretend it doesn't exist.

There is some truth to that.

def collect_repeats_inject list
return [] if [] == list
list[1..-1].inject([[ list.first ]]){|a,e|
if a[-1][0] == e
a[-1] << e
else
a << [e]
end
a
}.reject{|lst| lst.size < 2 }
end

def collect_repeats list
return [] if [] == list
accum = [ [ list.first ] ]
list[1..-1].each{|e|
if accum[-1][0] == e
accum[-1] << e
else
accum << [e]
end }
accum.reject{|lst| lst.size < 2 }
end

p collect_repeats( %w(0 1 1 2 3 3 3 3 4 5 5 6) )
p collect_repeats( [] )
p collect_repeats_inject( %w(0 1 1 2 3 3 3 3 4 5 5 6) )
p collect_repeats_inject( [] )

the_list = %w(0 1 1 2 3 3 3 3 3 3 3 3 4 5 5 6 7 8 8 9 9 9)

t = Time.now
9999.times{ collect_repeats_inject( the_list )}
p Time.now - t
t = Time.now
9999.times{ collect_repeats( the_list )}
p Time.now - t

--- output ---
[["1", "1"], ["3", "3", "3", "3"], ["5", "5"]]
[]
[["1", "1"], ["3", "3", "3", "3"], ["5", "5"]]
[]
2.814
1.923

And the version without inject seems clearer.
 
R

Rick DeNatale

By the way, using inject() is inefficient--not to mention confusing.
You might as well pretend it doesn't exist.

Or much better, understand that it's a way to reduce/fold the elements
of an enumerable into a single value.
http://en.wikipedia.org/wiki/Fold_(higher-order_function)

Enumerable#inject is very useful, as long as you don't treat it as
Maslow's hammer and use it in inappropriate ways, such as giving it a
block like in the original posting to this thread.
 
M

Marc Heiler

Understand that it's a way to reduce/fold the elements
of an enumerable into a single value.

Not that this is important, but personally I never liked the name
inject
Though fold(l/r) is not much better either.

A peculiar thing is that the wikipedia page gives "also known variously
as " four different names as alternative/example.
 
R

Robert Dober

Or much better, understand that it's a way to reduce/fold the elements
of an enumerable into a single value.
http://en.wikipedia.org/wiki/Fold_(higher-order_function)

Enumerable#inject is very useful, as long as you don't treat it as
Maslow's hammer and use it in inappropriate ways, such as giving it a
block like in the original posting to this thread.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/
Completely agree with Rick and furthermore you will not be able to
understand Ruby code written by others if you do not have a basic
understanding of inject.

I found this code in Ruby1.9 though

if result.size > 0 and result.inject(false) {|k,s| s or k}

that kind of code explains why inject has a bad reputation.

Cheers
Robert
 
R

Rick DeNatale

Not that this is important, but personally I never liked the name
.inject
Though fold(l/r) is not much better either.

A peculiar thing is that the wikipedia page gives "also known variously
as " four different names as alternative/example.

It's because the same thing has surfaced in several languages using
different terms, and those languages have in turn influenced others.

AFAIK, Matz correct me if I'm wrong, Ruby got the name inject from
Smalltalk, along with collect, select, detect and several other
methods. The other three I mention have alias, map for collect,
find_all for select, and find for detect, but inject is still just
inject in Ruby.
 
D

David A. Black

Hi --

It's because the same thing has surfaced in several languages using
different terms, and those languages have in turn influenced others.

AFAIK, Matz correct me if I'm wrong, Ruby got the name inject from
Smalltalk, along with collect, select, detect and several other
methods. The other three I mention have alias, map for collect,
find_all for select, and find for detect, but inject is still just
inject in Ruby.

It picks up the synonym "reduce" in 1.9 -- which I think is kind of
too bad, since inject in Ruby has such a distinctive personality and
cult following :)


David

--
Rails training from David A. Black and Ruby Power and Light:
ADVANCING WITH RAILS April 14-17 New York City
INTRO TO RAILS June 9-12 Berlin
ADVANCING WITH RAILS June 16-19 Berlin
See http://www.rubypal.com for details and updates!
 
J

James Gray

By the way, using inject() is inefficient--not to mention confusing.
You might as well pretend it doesn't exist.

I strongly disagree with that statement.

So 7stud, you learned Python and Ruby, decided you prefer Python, and
now hang out on the Ruby Talk mailing list bad mouthing our language?
What's the point, if you don't mind my asking?

James Edward Gray II
 
R

Robert Klemme

I found this code in Ruby1.9 though

if result.size > 0 and result.inject(false) {|k,s| s or k}

that kind of code explains why inject has a bad reputation.

That is quite a convoluted way to say

result.any?

Kind regards

robert
 
J

Just Another Victim of the Ambient Morality

David A. Black said:
Hi --

I'm not sure how I feel about inject's pathological case. What do you
all think should happen in the following code?


[2].inject {|a, i| puts i}


I might have thought that the block would never be called and the
method
would return 2.
If the block must be called, I might expect "i" to be nil.
What actually happens is that the block is called and "i" is the same
thing as "a". I did not expect this and I'm trying to figure out how
this
is either consistent or useful. Can someone clue me in to this
behaviour?
Thank you...

Actually the block isn't being called:

irb(main):005:0> [2].inject {|a, i| puts "hi" }
=> 2
irb(main):006:0> [2].inject {}
=> 2

The value of the whole statement is 2, or, more generally, the first
(and only) element in the enumerable. I guess that's all it can do
since it doesn't have enough elements to call the block even once.

D'oh!
I was confused by IRB's interface, for no good reason. I'll put the
crack pipe down, now. It's doing exactly what I thought it should do...
 
T

Thomas Wieczorek

Oh, yeah:

$ ruby -v
ruby 1.8.2 (2004-12-25) [universal-darwin8.0]


By the way, using inject() is inefficient--not to mention confusing.
You might as well pretend it doesn't exist.

I absolutely can't agree with that. map and inject are two beautiful
functions which should be known for the average Ruby programmer imo.
Instead of using some loops, you can easily use map to apply a
function on each member. I like them.
 
J

Julian Leviston

They indispensible. Once you understand the other iterators, inject
becomes natural.

Try doing some form of aggregation without using inject. Haha fun.

# example program to illustrate inject
num_ary = [10,37,27,398,273,28]

# summing without inject:
without_inject_sum = 0
num_ary.each{|num| without_inject_sum += num}

# summing with inject
with_inject_sum = num_ary.inject{|sum, num| sum + num}

Julian

Oh, yeah:

$ ruby -v
ruby 1.8.2 (2004-12-25) [universal-darwin8.0]


By the way, using inject() is inefficient--not to mention confusing.
You might as well pretend it doesn't exist.

I absolutely can't agree with that. map and inject are two beautiful
functions which should be known for the average Ruby programmer imo.
Instead of using some loops, you can easily use map to apply a
function on each member. I like them.
 
J

Julian Leviston

...or you could do it like this:

def collect_repeats_inject(list)
return list unless list.respond_to?:)inject)
collected_hash = list.inject({}) do |collected_repeats, item|
( collected_hash[item] ||= [] ) << item
collected_hash
end
collected_hash.values
end

Oh, yeah:

$ ruby -v
ruby 1.8.2 (2004-12-25) [universal-darwin8.0]

By the way, using inject() is inefficient--not to mention confusing.
You might as well pretend it doesn't exist.

There is some truth to that.


def collect_repeats_inject list
return [] if [] == list
list[1..-1].inject([[ list.first ]]){|a,e|
if a[-1][0] == e
a[-1] << e
else
a << [e]
end
a
}.reject{|lst| lst.size < 2 }
end

def collect_repeats list
accum = [ [ list.shift ] ]
list.each{|e|
if accum[-1][0] == e
accum[-1] << e
else
accum << [e]
end }
accum.reject{|lst| lst.size < 2 }
end

p collect_repeats( %w(0 1 1 2 3 3 3 3 4 5 5 6) )
p collect_repeats( [] )
p collect_repeats_inject( %w(0 1 1 2 3 3 3 3 4 5 5 6) )
p collect_repeats_inject( [] )

the_list = %w(0 1 1 2 3 3 3 3 3 3 3 3 4 5 5 6 7 8 8 9 9 9)

t = Time.now
9999.times{ collect_repeats_inject( the_list )}
p Time.now - t
t = Time.now
9999.times{ collect_repeats( the_list )}
p Time.now - t

--- output ---
[["1", "1"], ["3", "3", "3", "3"], ["5", "5"]]
[]
[["1", "1"], ["3", "3", "3", "3"], ["5", "5"]]
[]
2.694
0.16


And the version without inject is shorter and
clearer.
 
T

thufir

2) One element array:
[...]

How do you print the value for "a" and "i"?

thufir@arrakis:~/ruby$
thufir@arrakis:~/ruby$ ruby inject_one_element.rb
result=2
thufir@arrakis:~/ruby$
thufir@arrakis:~/ruby$ cat inject_one_element.rb
result = [2].inject do |a, i|
puts "a:#{a}"
puts "i:#{i}"
puts "hello"
puts "--------"
end



puts "result=#{result}"
if result.nil?
puts 'yes'
end
thufir@arrakis:~/ruby$



thanks,

Thufir
 
T

thufir

By the way, using inject() is inefficient--not to mention confusing. You
might as well pretend it doesn't exist.


What's the "normal" way, not using inject?



-Thufir
 

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

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top