Массив ruby ​​с повторяющимися значениями в хэш - PullRequest
3 голосов
/ 28 марта 2011

Я новичок в ruby, и мне трудно разобраться, как преобразовать массив массивов в хэш хеша массива.

Например, скажем, у меня есть:

[ [38, "s", "hum"], 
  [38, "t", "foo"], 
  [38, "t", "bar"], 
  [45, "s", "hum"], 
  [45, "t", "ram"], 
  [52, "s", "hum"], 
  [52, "t", "cat"], 
  [52, "t", "dog"]
]

В итоге я хочу:

{38 => {"s" => ["hum"],
        "t" => ["foo", "bar"]
       },
 45 => {"s" => ["hum"],
        "t" => ["ram"]
       },
 52 => {"s" => ["hum"],
        "t" => ["cat", "dog"]
       }
 }

Я пробовал group_by и Hash, но ни один из них не дает мне то, что я ищу.

Ответы [ 4 ]

2 голосов
/ 28 марта 2011

Может быть, есть более краткий способ сделать это, но я решил просто пойти прямым путем:

input = [ [38, "s", "hum"],
  [38, "t", "foo"],
  [38, "t", "bar"],
  [45, "s", "hum"],
  [45, "t", "ram"],
  [52, "s", "hum"],
  [52, "t", "cat"],
  [52, "t", "dog"]
]

output = {}

# I'll talk through the first iteration in the comments.

input.each do |outer_key, inner_key, value|
  # Set output[38] to a new hash, since output[38] isn't set yet.
  # If it were already set, this line would do nothing, so
  # output[38] would keep its previous data.
  output[outer_key] ||= {}

  # Set output[38]["s"] to a new array, since output[38]["s"] isn't set yet.
  # If it were already set, this line would do nothing, so
  # output[38]["s"] would keep its previous data.
  output[outer_key][inner_key] ||= []

  # Add "hum" to the array at output[38]["s"].
  output[outer_key][inner_key] << value
end

Итак, часть, которую вы на самом деле используете, все приведено в порядок:

output = {}

input.each do |outer_key, inner_key, value|
  output[outer_key] ||= {}
  output[outer_key][inner_key] ||= []
  output[outer_key][inner_key] << value
end
1 голос
/ 28 марта 2011

В таких случаях inject (он же reduce в 1.9) является отличным инструментом:

input.inject({}) do |acc, (a, b, c)|
  acc[a] ||= {}
  acc[a][b] ||= []
  acc[a][b] << c
  acc
end

Он вызывает блок один раз для каждого элемента в input, пропуская аккумулятор ипредмет.Первый раз он передает аргумент как аккумулятор, а последующие вызовы возвращают значение последнего вызова как аккумулятор.

0 голосов
/ 31 марта 2011

Пример, приведенный в вопросе, имеет длину три для каждого массива элементов, но метод ниже использует рекурсию и может использоваться для произвольной длины.

a = [ [38, "s", "hum", 1], 
    [38, "t", "foo", 2],
    [38, "t", "bar", 3], 
    [45, "s", "hum", 1], 
    [45, "t", "ram", 1], 
    [52, "s", "hum", 3], 
    [52, "t", "cat", 3], 
    [52, "t", "dog", 2]
]

class Array
  def rep
    group_by{|k, _| k}.
    each_value{|v| v.map!{|_, *args| args}}.
    tap{|h| h.each{|k, v| h[k] = (v.first.length > 1 ? v.rep : v.flatten(1))}}
  end
end

p a.rep
0 голосов
/ 28 марта 2011

Это может считаться ужасным или элегантным, в зависимости от ваших чувств:

input.inject(Hash.new {|h1,k1| h1[k1] = Hash.new {|h2,k2| h2[k2] = Array.new}}) {|hash,elem| hash[elem[0]][elem[1]].push(elem[2]); hash}
=> {38=>{"s"=>["hum"], "t"=>["foo", "bar"]}, 45=>{"s"=>["hum"], "t"=>["ram"]}, 52=>{"s"=>["hum"], "t"=>["cat", "dog"]}}

В идеале более читаемая версия:

input.inject(Hash.new(Hash.new(Array.new))) {|hash,elem| hash[elem[0]][elem[1]].push(elem[2]); hash}

То есть, начинать с пустого хеша со значением по умолчанию, равным пустому хешу, со значением по умолчанию, равным пустому массиву. Затем переберите ввод, сохраняя элементы в соответствующих местах.

Проблема с последним синтаксисом состоит в том, что Hash.new (Hash.new (Array.new)) приведет к тому, что все хеши и массивы будут иметь одинаковое расположение в памяти, и, таким образом, значения будут перезаписаны. Прежний синтаксис каждый раз создает новый объект и, таким образом, дает желаемый результат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...