Search code examples
arraysrubyruby-hash

Read multiple hash at a time and update the values in Ruby


Consider the variables:

ctr = ['cobol',nil,nil,'test',nil,'cobol', nil]

h1 = {
0=>{"ABC"=>"10000100126N", "CDE"=>"2013-08-30-}", "TPP"=>"11400000206633458812N", "APD"=> "01531915972", "PRODUCTID"=>"113n", "OPP"=>"201509n", "CTC"=>"C"}, 
1=>{"ABC"=>"00000039540A", "CDE"=>"0182.22X", "TPP"=>"1234.565N", "APD"=>"12345600", "PRODUCTID"=>"ACHN", "OPP"=>"00000000000119964.1256", "CTC"=>"00000000000211920"}
}

h2 = {'{' => '+0', 'A' => '+1', 'B' => '+2', '}' => '-0', 'N' => '-5'}

The task is to read the ctr data and where the value is cobol, we need to apply logic for those values in h1 hash only.

we need to parse the hash h1 and if the last char in hash's value matches with one of the key in hash h2 then replace that value with the corresponding value and prepend symbol to the string.

For example: when we scan hash h1, for value "10000100126N", as the last char is N and it exists in h2, then the output should be '-100001001265' where 5 is appended and - is prepended. [Not that the ctr for this is 'cobol']

But if we look at the second value "CDE"=>"2013-08-30-}", since for this key-value pair, the ctr value is not cobol, wee do nothing with the string.

This is what i have done so far:

h1.each do |k,h|
    h.update(h) do |*, v|
        # puts v
        h2.each do |q,p|
            if (v[-1] == q)
                v.sub!(v[-1], p[-1])
                 v.sub!(/(.*?)/, p[0] +'\1')
            end
        end
        v
    end
end

This code is updating the string as per the requirement, but its running for all the values in h1, i need to run the code only for the corresponding index where the value in the array ctr is 'cobol'


Solution

  • First of all a warning when you are matching Hash positions with Array indexes. In your example ['cobol',nil,nil,'test',nil,'cobol', nil] correspond with the keys ["ABC", "CDE", "TPP", "APD", "PRODUCTID", "OPP", "CTC"] from the inner Hash of h1. Keep in mind that a Hash is not index based but key based. This means, in theory the order of the hash is not maintained. A better way of doing it is by defining a Hash, like so: {"ABC"=>"cobol", "CDE"=>nil, "TPP"=>nil, "APD"=>"test", "PRODUCTID"=>nil, "OPP"=>"cobol", "CTC"=>nil}.

    With this warning out of the way, let's get to the answer.

    What you are looking for is the Enumerable#zip function, to combine every value with its corresponding value in ctr.

    [:a, :b, :c].zip([1, 2, 3])
    #=> [[:a, 1], [:b, 2], [:c, 3]]
    

    First we need to loop through your hash, you're using Hash#each. Since this is a transformation Enumerable#map is a better fit. The map functions results in a array with transformed values. The resulting array can be transformed back into a Hash with the correct structure.

    [[:a, 1], [:b, 2], [:c, 3]].to_h
    #=> {:a => 1, :b => 2, :c => 3}
    

    Here's the solution I came up with. It's not the most clean, but it works.

    check_logic = lambda do |type, value|
      return value unless type == 'cobol'
      return value unless h2.has_key?(value[-1])
      "#{h2[value[-1]][0]}#{value[0...-1]}#{h2[value[-1]][-1]}"
    end
    
    
    result = h1.map { |k1, v1| [k1, v1.zip(ctr).map { |(k2, v2), type| [k2, check_logic.call(type, v2)] }.to_h] }.to_h
    #=> {0=>{"ABC"=>"-100001001265", "CDE"=>"2013-08-30-}", "TPP"=>"11400000206633458812N", "APD"=>"01531915972", "PRODUCTID"=>"113n", "OPP"=>"201509n", "CTC"=>"C"}, 1=>{"ABC"=>"+000000395401", "CDE"=>"0182.22X", "TPP"=>"1234.565N", "APD"=>"12345600", "PRODUCTID"=>"ACHN", "OPP"=>"00000000000119964.1256", "CTC"=>"00000000000211920"}}
    

    As you can see I'm using zip to combine each value of the Hash with the ctr Array. I'm also using mass assignment (don't know the correct term). A simple example of this is:

    (v1, v2, v3) = [1, 2, 3]
    

    Resulting in v1 having value 1, v2 having value 2. In the second map there are 2 params, the first is an Array, containing the key and value of the inner Hash, the second is the value from the combined ctr Array. By using mass assignment I can give the key and value their own variable name.

    Since the logic is a bit to much for a one liner I moved it to a lambda, but this could also be a function (when passing h2 as param).