Matriz para Hash Ruby

192

Ok, então aqui está o negócio, eu venho pesquisando há séculos para encontrar uma solução para isso e, embora existam muitos por aí, eles não parecem fazer o trabalho que estou procurando.

Basicamente, eu tenho uma matriz estruturada como esta

["item 1", "item 2", "item 3", "item 4"] 

Eu quero converter isso em um Hash para que fique assim

{ "item 1" => "item 2", "item 3" => "item 4" }

ou seja, os itens que estão nos índices 'pares' são as chaves e os itens nos índices 'ímpares' são os valores.

Alguma idéia de como fazer isso de forma limpa? Suponho que um método de força bruta seria apenas extrair todos os índices pares em uma matriz separada e depois fazer um loop em torno deles para adicionar os valores.

djhworld
fonte

Respostas:

357
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

É isso aí. O *é chamado de operador splat .

Uma ressalva por @Mike Lewis (nos comentários): "Tenha muito cuidado com isso. Ruby expande splats na pilha. Se você fizer isso com um grande conjunto de dados, espere explodir sua pilha".

Portanto, na maioria dos casos de uso geral, esse método é ótimo, mas use um método diferente se você quiser fazer a conversão em muitos dados. Por exemplo, @ Łukasz Niemier (também nos comentários) oferece este método para grandes conjuntos de dados:

h = Hash[a.each_slice(2).to_a]
Ben Lee
fonte
10
@ testador, o *é chamado de operador splat . Ele pega uma matriz e a converte em uma lista literal de itens. Então *[1,2,3,4]=> 1, 2, 3, 4. Neste exemplo, o acima é equivalente a fazer Hash["item 1", "item 2", "item 3", "item 4"]. E Hashpossui um []método que aceita uma lista de argumentos (criando chaves de índices pares e valores de índices ímpares), mas Hash[]não aceita uma matriz, por isso dividimos a matriz usando *.
Ben Lee
15
Tenha muito cuidado com isso. Ruby expande splats na pilha. Se você fizer isso com um grande conjunto de dados, espere explodir sua pilha.
Mike Lewis
9
Em tabelas de big data, você pode usar Hash[a.each_slice(2).to_a].
Hauleth 22/07/2013
4
O que significa "explodir sua pilha"?
Kevin
6
@ Kevin, a pilha usa uma pequena área de memória que o programa aloca e reserva para determinadas operações específicas. Geralmente, é usado para manter uma pilha dos métodos chamados até agora. Essa é a origem do termo rastreamento de pilha e também é por isso que um método infinitamente recursivo pode causar um estouro de pilha . O método nesta resposta também usa a pilha, mas como a pilha é apenas uma pequena área de memória, se você tentar esse método com uma matriz grande, ela preencherá a pilha e causará um erro (um erro nas mesmas linhas de um estouro de pilha).
Ben Lee
103

O Ruby 2.1.0 introduziu um to_hmétodo em Array que faz o que você precisa se sua matriz original consistir em matrizes de pares de valores-chave: http://www.ruby-doc.org/core-2.1.0/Array.html#method -i-to_h .

[[:foo, :bar], [1, 2]].to_h
# => {:foo => :bar, 1 => 2}
Jochem Schulenklopper
fonte
1
Lindo! Muito melhor do que algumas das outras soluções aqui.
Dennis
3
para versões ruby ​​anteriores à 2.1.0, você pode usar o método Hash :: [] para obter resultados semelhantes, desde que possua pares de uma matriz aninhada. so a = [[: foo,: 1], [bar, 2]] --- Hash [a] => {: foo => 1
,:
@AfDev, de fato, obrigado. Você está correto (ao ignorar os erros menores: barprecisa ser um símbolo, e o símbolo :2deve ser um número inteiro. Portanto, sua expressão corrigida é a = [[:foo, 1], [:bar, 2]]).
Jochem Schulenklopper
28

Basta usar Hash.[]com os valores na matriz. Por exemplo:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}
Mandril
fonte
1
o que significa [* arr]?
Alan Coromano 17/07/2013
1
@ Marius: *arrconverte arrem uma lista de argumentos, então isso está chamando o []método de Hash com o conteúdo de arr como argumentos.
Chuck
26

Ou, se você tiver uma matriz de [key, value]matrizes, poderá:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }
Erik Escobedo
fonte
2
Sua resposta não está relacionada à pergunta e, no seu caso, ainda é muito mais fácil usar o mesmo.Hash[*arr]
Yossi
2
Não. Voltaria { [1, 2] => [3, 4] }. E como o título da pergunta diz "Array to Hash" e o método { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]]interno "Hash to Array": Na verdade, foi assim que acabei aqui de qualquer maneira.
Erik Escobedo
1
Desculpe, adicionei um asterisco de reposição. Hash[arr]fará o trabalho para você.
Yossi
9
Melhor solução IMHO: Hash [* array.flatten (1)]
convidado
2
Yossi: Desculpe por ressuscitar os mortos, mas há um problema maior com sua resposta, e esse é o uso do #injectmétodo. Com #merge!, #each_with_objectdeveria ter sido usado. Se #injecté insistido, em #mergevez de #merge!deveria ter sido usado.
Boris Stitnicky
12

Isto é o que eu estava procurando ao pesquisar isso:

[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}

Karl Glaser
fonte
Você não deseja usar merge, ele constrói e descarta uma nova iteração de hash por loop e é muito lento. Se você tem uma variedade de hashes, tente [{a:1},{b:2}].reduce({}, :merge!)- ele mescla tudo no mesmo (novo) hash.
Obrigado, é isso que eu também queria! :)
Thanasis Petsas
Você também pode fazer.reduce(&:merge!)
Ben Lee
1
[{a: 1}, {b: 2}].reduce(&:merge!)avalia para{:a=>1, :b=>2}
Ben Lee
Isso funciona porque injetar / reduzir possui um recurso no qual você pode omitir o argumento; nesse caso, ele usa o primeiro argumento da matriz para operar como o argumento de entrada e o restante da matriz como a matriz. Combine isso com o símbolo para o processo e você obterá essa construção concisa. Em outras palavras, [{a: 1}, {b: 2}].reduce(&:merge!)é o mesmo [{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) }que [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }.
Ben Lee
10

Enumeratorinclui Enumerable. Desde 2.1, Enumerabletambém tem um método #to_h. Por isso, podemos escrever:

a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}

Porque #each_slicesem o bloco nos dá Enumerator, e conforme a explicação acima, podemos chamar o #to_hmétodo no Enumeratorobjeto.

Arup Rakshit
fonte
7

Você pode tentar assim, para matriz única

irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
  => ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
  => {"item 1"=>"item 2", "item 3"=>"item 4"}

para matriz de matriz

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}
Jenorish
fonte
6
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

ou, se você odeia Hash[ ... ]:

a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end

ou, se você é um fã preguiçoso de programação funcional interrompida:

h = a.lazy.each_slice( 2 ).tap { |a|
  break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"
Boris Stitnicky
fonte
Se você não odeia totalmente Hash[ ... ], mas quero usá-lo como um método encadeada (como você pode fazer com to_h) você pode combinar sugestões Boris e de escrita:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
b-studios
Para tornar a semântica do código acima mais clara: Isso criará um "lazy hash" h , inicialmente vazio , e extrairá elementos da matriz original quando necessário. Somente então eles serão realmente armazenados em h!
Daniel Werner
1

Todas as respostas assumem que a matriz inicial é única. O OP não especificou como lidar com matrizes com entradas duplicadas, o que resulta em chaves duplicadas.

Vamos olhar para:

a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]

Você perderá o item 1 => item 2par, pois é substituído por item 1 => item 5:

Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

Todos os métodos, incluindo o reduce(&:merge!)resultado na mesma remoção.

Pode ser que isso seja exatamente o que você espera, no entanto. Mas em outros casos, você provavelmente deseja obter um resultado com um Arrayvalor for:

{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

A maneira ingênua seria criar uma variável auxiliar, um hash que tenha um valor padrão e preenchê-lo em um loop:

result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Pode ser possível usar assoce reducefazer acima em uma linha, mas isso se torna muito mais difícil de raciocinar e ler.

berkes
fonte