3

I want to create a hash from the result of a database query such that its keys are the column values except the last one, and the value are the last column value (or a default value). For example, if I have rows:

1 2 3 1
1 2 4 9
1 3 2 nil

and a default value of 111, I should get:

{
  1 => 
       {
          2 => { 3 => 1, 4 => 9},
          3 => { 2 => 111}
       }
}

I want to make the method generic enough to handle an arbitrary number of columns, so the signature could be:

to_lookup(rows, default_value, value_column, *columns)

How would I go about that?

Update: forgot a comma in the output.

Johnny
  • 7,073
  • 9
  • 46
  • 72
  • 1
    What exactly is the input? – sawa Nov 17 '15 at 17:01
  • 2
    Cf. http://stackoverflow.com/questions/8404769. – sawa Nov 17 '15 at 17:12
  • The input is the result of a database query, but you can think of it as an array of arrays where each row is an array. If you copy/paste the expected output into an irb session I believe it won't throw an exception. – Johnny Nov 17 '15 at 17:12
  • 1
    Interesting question, but you should edit to provide a second example to show whether rows are to be grouped or consecutive rows are to be grouped. You could do that with `arr = [[1, 1, 2, 1], [3]]`, for example. – Cary Swoveland Nov 17 '15 at 18:28
  • 1
    In future, when you give an example, always have valid Ruby objects as inputs and assign each to a variable (e.g., `arr = [[1, 2, 3, 1], [1, 2, 4, 9], [1, 3, 2, nil]]`. You may have thought displaying a picture of the array made it clearer, but we cannot cut and paste that. Of course you can always write it as I have but with each element ("row") of `arr` on a separate line. By assigning objects to variables readers can refer to those variables in comments and answers without having to define them. Lastly, remove extraneous bits, here replacing `nil` with a default. – Cary Swoveland Nov 17 '15 at 18:33

2 Answers2

3

[Edit: after reading @cthulhu's answer, I think I may have misinterpreted the question. I assumed that consecutive rows were to be grouped, rather than all rows to be grouped. I will leave my answer for the former interpretation.]

I believe this is what you are looking for:

def hashify(arr)
  return arr.first.first if arr.first.size == 1  
  arr.slice_when { |f,s| f.first != s.first }.
      each_with_object({}) do |a,h|
        key, *rest = a.transpose
        h[key.first] = hashify(rest.transpose)
      end
end

hashify [[1, 2, 3, 1], [1, 2, 4, 9], [1, 3, 2, nil]]
  #=> {1=>{2=>{3=>1, 4=>9}, 3=>{2=>nil}}} 

hashify [[1, 2, 3, 1], [1, 2, 4, 9], [2, 3, 2, nil]]
  #=> {1=>{2=>{3=>1, 4=>9}}, 2=>{3=>{2=>nil}}} 

Replacing nil with the default can be done before or after the construction of the hash.

Enumerable#slice_when was bestowed upon us in v2.2. For earlier versions, you could replace:

arr.slice_when { |f,s| f.first != s.first }

with

arr.chunk { |row| row.first }.map(&:last)
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 1
    Soon, you will be able to use `chunk_while` instead of `slice_when` so that you don't have to negate the block. – sawa Nov 17 '15 at 19:08
  • @sawa, did you mean `slice_while`? I've been meaning to suggest that [Array#difference](http://stackoverflow.com/questions/24987054/how-to-select-unique-elements) be added as a core method. Where would I do that? – Cary Swoveland Nov 17 '15 at 19:24
2

I simplified things by removing the ability to pass a default, I also simplified the signature method to have only one parameter.

RSpec.describe "#to_lookup" do

  def to_lookup(rows)
    return rows.first.first if rows.flatten.size == 1
    h = {}
    rows.group_by { |e| e.first }.each_entry do |k, v|
      v.each &:shift
     h[k] = to_lookup(v)
    end
    h
  end


  let :input do
    [
      [1, 2, 3, 1],
      [1, 2, 4, 9],
      [1, 3, 2, 111],
    ]
  end

  let :output do
    {
      1 => {
          2 => {3 => 1, 4 => 9},
          3 => {2 => 111}
      }
    }
  end

  it { expect(to_lookup(input)).to eq(output) }

end

BTW I wonder what output do you want for following input:

 1 2 3 1
 1 2 3 2

EDIT: working code snippet: http://rubysandbox.com/#/snippet/566aefa80195f1000c000000

cthulhu
  • 3,749
  • 1
  • 21
  • 25