Find the first non-nil element in an array

L

lianliming

Hi all,

Is there any build-in method of class Array which can be used to find
the first non-nil element in an array? I can't find any so I am trying
to achieve it by my own approach. I don't think my approach is smart
enough, so I would like to know better one to do so. Here is what I am
doing:

a = [nil, "hello", "world"]
b = a
b.compact!
first_non_nil_elem = b.first

I don't call "compact!" directly on a because it will change a's
structure, I want to reserve nil elements in a.

Any suggestions are really appreciated!
 
S

Stefano Crocco

Alle marted=EC 11 dicembre 2007, lianliming ha scritto:
Hi all,

Is there any build-in method of class Array which can be used to find
the first non-nil element in an array? I can't find any so I am trying
to achieve it by my own approach. I don't think my approach is smart
enough, so I would like to know better one to do so. Here is what I am
doing:

a =3D [nil, "hello", "world"]
b =3D a
b.compact!
first_non_nil_elem =3D b.first

I don't call "compact!" directly on a because it will change a's
structure, I want to reserve nil elements in a.

Any suggestions are really appreciated!

You can use Enumerable#find, which will return the first item in the array =
for=20
which the block returns true:

a =3D [nil, "hello", "world"]
puts a.find{|i| !i.nil?}
=3D> "hello"

By the way, you can use compact, instead of compact! to avoid using the=20
temporary variable b:

a =3D [nil, "hello", "world"]
first_non_nil_elem =3D a.compact.first

While compact! removes the nil elements from the receiver, compact returns =
a=20
new array with the nil element removed.

I hope this helps

Stefano
 
L

lianliming

Really helpful, thx!
Is there any build-in method of class Array which can be used to find
the first non-nil element in an array? I can't find any so I am trying
to achieve it by my own approach. I don't think my approach is smart
enough, so I would like to know better one to do so. Here is what I am
doing:
a = [nil, "hello", "world"]
b = a
b.compact!
first_non_nil_elem = b.first
I don't call "compact!" directly on a because it will change a's
structure, I want to reserve nil elements in a.
Any suggestions are really appreciated!

You can use Enumerable#find, which will return the first item in the array for
which the block returns true:

a = [nil, "hello", "world"]
puts a.find{|i| !i.nil?}
=> "hello"

By the way, you can use compact, instead of compact! to avoid using the
temporary variable b:

a = [nil, "hello", "world"]
first_non_nil_elem = a.compact.first

While compact! removes the nil elements from the receiver, compact returns a
new array with the nil element removed.

I hope this helps

Stefano
 
P

Phrogz

Alle martedì 11 dicembre 2007, lianliming ha scritto:


Is there any build-in method of class Array which can be used to find
the first non-nil element in an array? I can't find any so I am trying
to achieve it by my own approach. I don't think my approach is smart
enough, so I would like to know better one to do so. Here is what I am
doing:
a = [nil, "hello", "world"]
b = a
b.compact!
first_non_nil_elem = b.first
I don't call "compact!" directly on a because it will change a's
structure, I want to reserve nil elements in a.
Any suggestions are really appreciated!

You can use Enumerable#find, which will return the first item in the arrayfor
which the block returns true:

a = [nil, "hello", "world"]
puts a.find{|i| !i.nil?}
=> "hello"

By the way, you can use compact, instead of compact! to avoid using the
temporary variable b:

a = [nil, "hello", "world"]
first_non_nil_elem = a.compact.first

While compact! removes the nil elements from the receiver, compact returnsa
new array with the nil element removed.

Moreover, there was no reason to assign to the 'b' variable in the
first place. Doing so doesn't duplicate the array, but just creates
another reference to it. The compact! method still modified the same
array:

irb(main):006:0> a = [ nil, "foo", "bar" ]
=> [nil, "foo", "bar"]

irb(main):007:0> b = a
=> [nil, "foo", "bar"]

irb(main):008:0> b.compact!
=> ["foo", "bar"]

irb(main):009:0> a
=> ["foo", "bar"]

So if the intent was, as I assume, to preserve the original array with
the nil values, that's even more reason to use #compact instead of
#compact!
 
C

clouder

I had some time wrapping my head around this one messing with irb

I was wondering why b.compact! would change the value of a

As i continually tried doing
a =3D [1,2,3]
b =3D a
b =3D [1,3,4]
and was getting a =3D> [1,2,3] and b =3D> [1,3,4]

After trying with strings and gsub and such it finally dinged that b=3Da
has assigned 'b' to the object stored in 'a', so running a method on
it was effecting the object stored in both 'a' and 'b'. Where as when
I was doing b =3D [1,3,4] it was creating a new object all together so
'a' was never effected. Is this correct? If so, sorry for making a
dumb post if everyone else git this :X I thought I'd post it anyways
to possibly help some poor soul like me struggling over why this might
not work as expected.

Alle marted=EC 11 dicembre 2007, lianliming ha scritto:
Hi all,
Is there any build-in method of class Array which can be used to find
the first non-nil element in an array? I can't find any so I am trying=
to achieve it by my own approach. I don't think my approach is smart
enough, so I would like to know better one to do so. Here is what I am=
doing:
a =3D [nil, "hello", "world"]
b =3D a
b.compact!
first_non_nil_elem =3D b.first
I don't call "compact!" directly on a because it will change a's
structure, I want to reserve nil elements in a.
Any suggestions are really appreciated!
You can use Enumerable#find, which will return the first item in the arr= ay for
which the block returns true:
a =3D [nil, "hello", "world"]
puts a.find{|i| !i.nil?}
=3D> "hello"
By the way, you can use compact, instead of compact! to avoid using the
temporary variable b:
a =3D [nil, "hello", "world"]
first_non_nil_elem =3D a.compact.first
While compact! removes the nil elements from the receiver, compact retur= ns a
new array with the nil element removed.

Moreover, there was no reason to assign to the 'b' variable in the
first place. Doing so doesn't duplicate the array, but just creates
another reference to it. The compact! method still modified the same
array:

irb(main):006:0> a =3D [ nil, "foo", "bar" ]
=3D> [nil, "foo", "bar"]

irb(main):007:0> b =3D a
=3D> [nil, "foo", "bar"]

irb(main):008:0> b.compact!
=3D> ["foo", "bar"]

irb(main):009:0> a
=3D> ["foo", "bar"]

So if the intent was, as I assume, to preserve the original array with
the nil values, that's even more reason to use #compact instead of
#compact!
 
C

Clifford Heath

clouder said:
I had some time wrapping my head around this one messing with irb

I was wondering why b.compact! would change the value of a

I can't work out why you want to build a new (compacted) array in
order to discard it and use the first element. Detect is much more
suitable:

[nil,nil,3, nil, 9, nil].detect{|e| e}
=> 3

Beware of false; you might want to say !e.nil?

Clifford Heath.
 
P

Phrogz

I had some time wrapping my head around this one messing with irb

I was wondering why b.compact! would change the value of a

As i continually tried doing
a = [1,2,3]
b = a
b = [1,3,4]
and was getting a => [1,2,3] and b => [1,3,4]

After trying with strings and gsub and such it finally dinged that b=a
has assigned 'b' to the object stored in 'a', so running a method on
it was effecting the object stored in both 'a' and 'b'. Where as when
I was doing b = [1,3,4] it was creating a new object all together so
'a' was never effected. Is this correct?

You've got it! Assigning to a variable is like moving a sticky note
with the name of that variable onto an object floating in space.
Reading the 'value' of a variable is like finding the sticky note with
that variable on it and instead grabbing the object underneath.

So, when you write "a = [1,2,3]", you're creating a new Array object
floating in space, and then slapping a sticky note on it.

When you then write "b = a", you're finding that Array floating in
space, and slapping another sticky note next to the one that says "a".

If you then say "b.compact!", you are telling the Array that 'b'
refers to to compact itself, which, of course, is the same Array as
'a' is refering to.

If instead you say "b = [1,3,4]" then you're taking the 'b' sticky
note off of the first Array, and sticking it on the new array that you
just created.
If so, sorry for making a
dumb post if everyone else git this :X I thought I'd post it anyways
to possibly help some poor soul like me struggling over why this might
not work as expected.

Don't apologize - references confuse a lot of new users, who often
think of a variable as a 'shoebox' into which objects are stuffed,
instead of lightweight references. There have been a few in-depth
threads about this in the past, but it still bears repeating to help
all readers who might not have read those discussions.
 
B

Brian Adkins

I can't work out why you want to build a new (compacted) array in
order to discard it and use the first element. Detect is much more
suitable:

Depending on the nature of the array (in particular, the number of
initial nils), speed might be a reason to use compact instead of
detect. Although it seems counterintuitive, creating a new compacted
array just to grab the first element and throw it away can be faster
than using detect.

With the compact approach, there is one call to a method that is
compiled C code. With the detect approach, there are (possibly)
multiple iterations of interpreted Ruby.

I'd say compact is perfectly suitable for this purpose - it's more
concise, usually faster and logical.

require 'benchmark'
include Benchmark

ITER = 10000

def bench size, num_nils
xs = Array.new(size, 'a')
xs.fill(nil, 0, num_nils)

bm(5) do |bench|
bench.report('compact') do
ITER.times { xs.compact[0] }
end
bench.report('detect') do
ITER.times { xs.detect {|e| !e.nil? } }
end
end
end

[10,100,1000].each do |size|
[0,1,9].each do |nils|
bench(size, nils)
end
end
 
P

Phrogz

I can't work out why you want to build a new (compacted) array in
order to discard it and use the first element. Detect is much more
suitable:

Depending on the nature of the array (in particular, the number of
initial nils), speed might be a reason to use compact instead of
detect. Although it seems counterintuitive, creating a new compacted
array just to grab the first element and throw it away can be faster
than using detect.

With the compact approach, there is one call to a method that is
compiled C code. With the detect approach, there are (possibly)
multiple iterations of interpreted Ruby.

I'd say compact is perfectly suitable for this purpose - it's more
concise, usually faster and logical.

require 'benchmark'
include Benchmark

ITER = 10000

def bench size, num_nils
xs = Array.new(size, 'a')
xs.fill(nil, 0, num_nils)

bm(5) do |bench|
bench.report('compact') do
ITER.times { xs.compact[0] }
end
bench.report('detect') do
ITER.times { xs.detect {|e| !e.nil? } }
end
end
end

[10,100,1000].each do |size|
[0,1,9].each do |nils|
bench(size, nils)
end
end

For the curious, here are the results of Brian's test on my computer
(with a small change to simplify the results and make things run long
enough to be noticeable):

Rehearsal --------------------------------------------------
compact 10 0 0.320000 0.000000 0.320000 ( 0.349166)
detect 10 0 0.610000 0.010000 0.620000 ( 0.604446)
compact 10 1 0.340000 0.000000 0.340000 ( 0.342348)
detect 10 1 0.820000 0.000000 0.820000 ( 0.827957)
compact 10 9 0.340000 0.000000 0.340000 ( 0.346089)
detect 10 9 2.540000 0.020000 2.560000 ( 2.554578)
compact 100 0 0.410000 0.000000 0.410000 ( 0.413763)
detect 100 0 0.590000 0.010000 0.600000 ( 0.605422)
compact 100 1 0.440000 0.000000 0.440000 ( 0.436081)
detect 100 1 0.820000 0.000000 0.820000 ( 0.828730)
compact 100 9 0.450000 0.010000 0.460000 ( 0.449880)
detect 100 9 2.540000 0.010000 2.550000 ( 2.559824)
compact 1000 0 2.100000 0.020000 2.120000 ( 2.118428)
detect 1000 0 0.600000 0.000000 0.600000 ( 0.603317)
compact 1000 1 1.900000 0.010000 1.910000 ( 1.966759)
detect 1000 1 0.850000 0.010000 0.860000 ( 1.014724)
compact 1000 9 2.070000 0.020000 2.090000 ( 2.431632)
detect 1000 9 2.600000 0.020000 2.620000 ( 3.047283)
---------------------------------------- total: 20.480000sec

user system total real
compact 10 0 0.320000 0.000000 0.320000 ( 0.323211)
detect 10 0 0.590000 0.000000 0.590000 ( 0.594916)
compact 10 1 0.350000 0.000000 0.350000 ( 0.343759)
detect 10 1 0.820000 0.010000 0.830000 ( 0.822784)
compact 10 9 0.340000 0.000000 0.340000 ( 0.344985)
detect 10 9 2.540000 0.010000 2.550000 ( 2.552693)
compact 100 0 0.400000 0.010000 0.410000 ( 0.409807)
detect 100 0 0.590000 0.000000 0.590000 ( 0.596081)
compact 100 1 0.430000 0.000000 0.430000 ( 0.437479)
detect 100 1 0.820000 0.010000 0.830000 ( 0.826087)
compact 100 9 0.450000 0.000000 0.450000 ( 0.451402)
detect 100 9 2.530000 0.020000 2.550000 ( 2.550478)
compact 1000 0 2.110000 0.010000 2.120000 ( 2.142801)
detect 1000 0 0.600000 0.010000 0.610000 ( 0.597523)
compact 1000 1 1.890000 0.010000 1.900000 ( 1.903376)
detect 1000 1 0.820000 0.000000 0.820000 ( 0.831492)
compact 1000 9 1.950000 0.010000 1.960000 ( 1.980168)
detect 1000 9 2.540000 0.000000 2.540000 ( 2.537653)


Here's the actual test code I ran:
require 'benchmark'
ITER = 500_000

def bench size, num_nils, bm
xs = Array.new(size, 'a')
xs.fill(nil, 0, num_nils)
bm.report("compact #{size} #{num_nils}") do
ITER.times { xs.compact[0] }
end
bm.report("detect #{size} #{num_nils}") do
ITER.times { xs.detect {|e| !e.nil? } }
end
end

Benchmark.bmbm do |bm|
[10,100,1000].each do |size|
[0,1,9].each do |nils|
bench(size, nils, bm)
end
end
end
 

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,996
Messages
2,570,238
Members
46,826
Latest member
robinsontor

Latest Threads

Top