rubyarrayst9

Consolidate nested arrays and erase the subarrays that have been consolidated?


I'm trying to take a bunch of number-word pairs and group the words according to common numbers. I can match the numbers, merge the subarrays that share the number, and erase the first of those subarrays. But when I try to delete the second, I get this error:

"in block in <main>': undefined method[]' for nil:NilClass (NoMethodError)"

The guilty line -- ary.delete_at(i+1) -- has been commented out. Secondary problem: MRBTree is not taking the nested arrays as input...

ary = [[2.28, "cat"], [2.28, "bat"], [2.327, "bear"], [2.68, "ant"], [2.68, "anu"]]
i = 0

for i in 0 ... ary.size - 1
    if ary[i][0] == ary[i+1][0]
        b = (ary[i]+ary[i+1]).uniq
        ary.delete_at(i)
                # ary.delete_at(i+1)
        c = [b.first], b.pop(b.length - 1)
        h = Hash[*c]
        ary.push(*h)
        # mrbtree = MultiRBTree[c]
    end
end

puts ary.inspect

output:

# => [
# =>   [2.28, "bat"], 
# =>   [2.327, "bear"], 
# =>   [2.68, "anu"], 
# =>   [
# =>     [2.28], ["cat", "bat"]
# =>   ], 
# =>   [
# =>     [2.68], ["ant", "anu"]
# =>   ]
# => ]

Any help appreciated!


Solution

  • Your attempt is failing because you are modifying the array (which has impact on a.size) in the loop. The loop end condition is not adjusted automagically. You are accessing things you have deleted before.

    If your array is not too big, this will do:

    p Hash[ary.group_by(&:first).map { | k, v | [k, v.map(&:last)] }]
    # => {2.28=>["cat", "bat"], 2.327=>["bear"], 2.68=>["ant", "anu"]}
    

    It works this way:

    ary.group_by(&:first)   # group the 2-elem arrays by the number, creating a hash 
                            #     like {2.28=>[[2.28, "cat"], [2.28, "bat"]], ...}
    .map  { | k, v | ... }  # change the array of key-value pairs to
    [k, v.map(&:last)]      # pairs where the value-array contains just the strings
    Hash[ ... ]             # make the whole thing a hash again
    

    Creating an intermediate array and transferring it back to a hash is some overhead. If this turns out to be an issue, something like this might be better:

    h = Hash.new { | a, k | a[k] = [] }   # a hash with [] as default value
    p ary.inject(h) { | a, (k, v) | a[k] << v; a }