Search code examples
ruby-on-railsrubyarrays

push elements at specific indices in ruby array


I have an array of arrays being returned from a db and want to make sure they are always the same length. When they are not I want to push zeroes to make them the proper length but also maintain the integrity of the indices.

Most times it will look like

array = [["apple", "blueberries", "banana", "kiwi"], ["20.15", "13.50", "22.10", "10.50"], ["10", "12", "26", "34"]]

I'll eventually be calling each_with_index and will need them to match up ie. apple,20.15,10 in a table.

Every so often, one or two elements could be missing from an array and I'd like to push zeroes into their place.

array2 = [["apple", "blueberries", "banana", "kiwi"], ["20.15", "22.10"], ["10", "12", "26", "34"]]

I've tried something like

array2.each {|f| f.push(0) until f.length === 4}
=>[["apple", "blueberries", "banana", "kiwi"], ["20.15", "13.50", 0, 0], ["10", "12", "26", "34"]]

but the zeroes are going to be pushed to the end of the array - Ideally the output would be

=>[["apple", "blueberries", "banana", "kiwi"], ["20.15", 0, "13.50", 0], ["10", "12", "26", "34"]]

Solution

  • I hate to break it to you, but programming is not an easy task for kids, as non-programmer adults sometimes imagine. Your case calls for creation of a separate class, perhaps a subclass of Array. Like this:

    class GreenGrocer < Array
    end
    

    Now we could imagine redefining GreenGrocer.new public class method to make sure that each GreenGrocer instance has missing elements patched with zeroes, like this:

    class GreenGrocer # reopening the class we defined earlier
      class << self
        def new array
          produce, float_array, int_array = array.reduce( &:zip ).map( &:flatten ).transpose
          super [ produce, float_array.map { |e| e or 0.0 }, int_array.map { |e| e or 0 } ]
        end
    
        def [] *elements; new elements end
      end
    end
    
    # so we can call
    
    g = GreenGrocer.new( array2 )
    
    # or
    
    g = GreenGrocer[ *array2 ]
    

    But why bother? It is enough to make a #to_table method that converts your incomplete data structure to a table as you require, filling the missing data points with zero in the process. So we define:

    class LazierGreenGrocer < Array
      def to_table
        reduce( &:zip ).map( &:flatten ).map { |e| e.map { |e| e || 0 } }
      end
    end
    
    lazier_g = LazierGreenGrocer.new( array2 ) # not eager to do work upon initialization
    lazier_g.to_table # does the required work as #to_table is called
    

    This is how I'd go about it. Since I do not know what exact kind of table do you want to output, you may have to modify my #to_table method accordingly.