Find the first non-nil element in an array

Discussion in 'Ruby' started by lianliming, Dec 11, 2007.

  1. lianliming

    lianliming Guest

    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!
     
    lianliming, Dec 11, 2007
    #1
    1. Advertising

  2. 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
     
    Stefano Crocco, Dec 11, 2007
    #2
    1. Advertising

  3. lianliming

    lianliming Guest

    Really helpful, thx!

    > > 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!

    >
    > 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
     
    lianliming, Dec 11, 2007
    #3
  4. lianliming

    Phrogz Guest

    On Dec 11, 2:28 am, Stefano Crocco <> wrote:
    > Alle martedì 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 = [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!
     
    Phrogz, Dec 11, 2007
    #4
  5. lianliming

    clouder Guest

    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.

    On Dec 11, 7:45 am, Phrogz <> wrote:
    > On Dec 11, 2:28 am, Stefano Crocco <> wrote:
    >
    >
    >
    > > 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!
     
    clouder, Dec 11, 2007
    #5
  6. clouder wrote:
    > 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.
     
    Clifford Heath, Dec 11, 2007
    #6
  7. lianliming

    Phrogz Guest

    On Dec 11, 1:09 pm, clouder <> wrote:
    > 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.
     
    Phrogz, Dec 11, 2007
    #7
  8. lianliming

    Brian Adkins Guest

    On Dec 11, 5:06 pm, Clifford Heath <> wrote:
    > 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

    --
    Brian Adkins
    http://www.lojic.com
    http://lojic.com/blog/
     
    Brian Adkins, Dec 12, 2007
    #8
  9. lianliming

    Phrogz Guest

    On Dec 12, 7:11 am, Brian Adkins <> wrote:
    > On Dec 11, 5:06 pm, Clifford Heath <> wrote:
    >
    > > 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
     
    Phrogz, Dec 12, 2007
    #9
  10. lianliming

    Lee Jarvis Guest

    Lee Jarvis, Dec 13, 2007
    #10
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Brian Candler

    puts nil generates "nil\n"

    Brian Candler, Nov 6, 2004, in forum: Ruby
    Replies:
    1
    Views:
    121
  2. John Carter
    Replies:
    64
    Views:
    689
    Klaus Stein
    May 19, 2005
  3. ako...

    a == nil or a.nil?

    ako..., Nov 22, 2005, in forum: Ruby
    Replies:
    6
    Views:
    161
    Douglas Livingstone
    Nov 23, 2005
  4. Christoffer Sawicki
    Replies:
    5
    Views:
    280
    Christoffer Sawicki
    Sep 2, 2006
  5. Eero Saynatkari

    nil.to_s != "nil"

    Eero Saynatkari, Oct 27, 2006, in forum: Ruby
    Replies:
    11
    Views:
    249
    Gregory Brown
    Oct 28, 2006
Loading...

Share This Page