Qual é a melhor maneira de converter uma matriz em um hash em Ruby


123

Em Ruby, dada uma matriz em uma das seguintes formas ...

[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]

... qual é a melhor maneira de converter isso em um hash na forma de ...

{apple => 1, banana => 2}

Respostas:


91

NOTA : Para uma solução concisa e eficiente, consulte a resposta de Marc-André Lafortune abaixo.

Essa resposta foi originalmente oferecida como uma alternativa às abordagens usando o achatamento, que foram as mais votadas no momento da redação. Eu deveria ter esclarecido que não pretendia apresentar este exemplo como uma prática recomendada ou uma abordagem eficiente. A resposta original segue.


Aviso! Soluções usando achatamento não preservarão chaves ou valores da matriz!

Com base na resposta popular de John Topley, vamos tentar:

a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]

Isso gera um erro:

ArgumentError: odd number of arguments for Hash
        from (irb):10:in `[]'
        from (irb):10

O construtor esperava uma matriz de comprimento uniforme (por exemplo, ['k1', 'v1,' k2 ',' v2 ']). O pior é que uma matriz diferente, achatada até um comprimento uniforme, nos daria silenciosamente um Hash com valores incorretos.

Se você deseja usar chaves ou valores da matriz, pode usar o mapa :

h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"

Isso preserva a chave da matriz:

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}

15
É o mesmo que o Hash [a3], pois a3 == a3.map {| k, v | [k, v]} é verdadeiro, na verdade é o equivalente a a3.dup.
Cluster

2
Em vez de usar o mapa, por que não apenas especificar a profundidade do nivelamento? Por exemplo: em h3 = Hash[*a3.flatten(1)]vez de h3 = Hash[*a3.flatten]qual seria um erro.
Jeff McCune

3
Esta resposta não é eficiente. Também está desatualizado. Veja minha resposta.
Marc-André Lafortune

1
Sim, acho que o de Marc-André to_hé melhor.
B Seven

1
@ Marc-André Lafortune obrigado, atualizei minha resposta para direcionar os usuários aos seus.
Guisado

145

Basta usar Hash[*array_variable.flatten]

Por exemplo:

a1 = ['apple', 1, 'banana', 2]
h1 = Hash[*a1.flatten(1)]
puts "h1: #{h1.inspect}"

a2 = [['apple', 1], ['banana', 2]]
h2 = Hash[*a2.flatten(1)]
puts "h2: #{h2.inspect}"

O uso Array#flatten(1)limita a recursão para que Arraychaves e valores funcionem conforme o esperado.


4
Oh, a eloqüência! É por isso que eu amo Ruby
iGbanam

11
AVISO: as respostas que usam o achatamento causarão problemas se você desejar chaves ou valores da matriz.
Guisado

Publiquei uma solução alternativa abaixo que evitará problemas com valores ou chaves de matriz.
Guisado

5
É melhor não tentar fazer uma solução abrangente para isso. Se suas chaves e valores estiverem emparelhados como em [[chave1, valor1], [chave2, valor2]], basta passar para Hash [] sem engordar. Hash [a2] == Hash [* a2.flatten]. Se a matriz já estiver nivelada como em, [chave1, valor1, chave2, valor2], basta prefixar o var com *, Hash [* a1]
Cluster

8
FWIW, se você realmente deseja (mais de) uma versão de tamanho único, também pode usar Hash[*ary.flatten(1)], o que preservará as chaves e os valores da matriz. É o recursivo flattenque está destruindo aqueles, o que é fácil de evitar.
Brymck

79

A melhor maneira é usar Array#to_h:

[ [:apple,1],[:banana,2] ].to_h  #=> {apple: 1, banana: 2}

Observe que to_htambém aceita um bloco:

[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] } 
  # => {apple: "I like apples", banana: "I like bananas"}

Nota : to_haceita um bloco no Ruby 2.6.0+; para rubis precoces, você pode usar minha backportsgema erequire 'backports/2.6.0/enumerable/to_h'

to_h sem um bloco foi introduzido no Ruby 2.1.0.

Antes do Ruby 2.1, era possível usar o menos legível Hash[]:

array = [ [:apple,1],[:banana,2] ]
Hash[ array ]  #= > {:apple => 1, :banana => 2}

Por fim, desconfie de qualquer solução em uso flatten, isso pode criar problemas com valores que são matrizes em si.


4
Obrigado pela simplicidade do novo método .to_h!
codificação addicted

3
Gosto mais do to_hmétodo do que as respostas acima, pois expressa a intenção de converter depois de operar na matriz.
B 7 de

1
@BSeven Nem Array#to_hnem Enumerable#to_hestá em núcleo Ruby 1.9.
Iron Savior

E se eu tiver uma matriz como [[apple, 1], [banana, 2], [apple, 3], [banana, 4]]e desejar a saída como {"apple" =>[1,3], "banana"=>[2,4]}?
Nishant 22/09

@NishantKumar, essa é uma pergunta diferente.
Marc-André Lafortune


9

Editar: vi as respostas postadas enquanto eu escrevia, Hash [a.flatten] parece o caminho a percorrer. Deve ter perdido essa parte da documentação quando estava pensando na resposta. Pensei que as soluções que eu escrevi podem ser usadas como alternativas, se necessário.

A segunda forma é mais simples:

a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }

a = array, h = hash, r = hash de valor de retorno (aquele em que acumulamos), i = item no array

A maneira mais pura de pensar em fazer a primeira forma é algo como isto:

a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }

2
+1 para a linha a.inject({})única que permite atribuições de valor mais flexíveis.
Chris Bloom

Também é possível descartar o h = {}do segundo exemplo usando o injetar, terminando coma.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
lindes

Você poderia fazera.each_slice(2).to_h
Conor O'Brien

6

Você também pode simplesmente converter uma matriz 2D em hash usando:

1.9.3p362 :005 > a= [[1,2],[3,4]]

 => [[1, 2], [3, 4]]

1.9.3p362 :006 > h = Hash[a]

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

4

Resumo & TL; DR:

Essa resposta espera ser um resumo abrangente de informações de outras respostas.

A versão muito curta, dados os dados da pergunta, mais alguns extras:

flat_array   = [  apple, 1,   banana, 2  ] # count=4
nested_array = [ [apple, 1], [banana, 2] ] # count=2 of count=2 k,v arrays
incomplete_f = [  apple, 1,   banana     ] # count=3 - missing last value
incomplete_n = [ [apple, 1], [banana   ] ] # count=2 of either k or k,v arrays


# there's one option for flat_array:
h1  = Hash[*flat_array]                     # => {apple=>1, banana=>2}

# two options for nested_array:
h2a = nested_array.to_h # since ruby 2.1.0    => {apple=>1, banana=>2}
h2b = Hash[nested_array]                    # => {apple=>1, banana=>2}

# ok if *only* the last value is missing:
h3  = Hash[incomplete_f.each_slice(2).to_a] # => {apple=>1, banana=>nil}
# always ok for k without v in nested array:
h4  = Hash[incomplete_n] # or .to_h           => {apple=>1, banana=>nil}

# as one might expect:
h1 == h2a # => true
h1 == h2b # => true
h1 == h3  # => false
h3 == h4  # => true

Discussão e detalhes a seguir.


Configuração: variáveis

Para mostrar os dados que usaremos antecipadamente, criamos algumas variáveis ​​para representar várias possibilidades para os dados. Eles se encaixam nas seguintes categorias:

Com base no que estava diretamente na pergunta, como a1e a2:

(Nota: Eu presumo que applee bananaforam feitos para representar variáveis Como já foi feito, eu vou estar usando cordas a partir daqui, para que de entrada e os resultados podem igualar..)

a1 = [  'apple', 1 ,  'banana', 2  ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input

Chaves com vários valores e / ou valores, como a3:

Em algumas outras respostas, outra possibilidade foi apresentada (que eu expiro aqui) - chaves e / ou valores podem ser matrizes por conta própria:

a3 = [ [ 'apple',                   1   ],
       [ 'banana',                  2   ],
       [ ['orange','seedless'],     3   ],
       [ 'pear',                 [4, 5] ],
     ]

Matriz desequilibrada, como a4:

Para uma boa medida, pensei em adicionar um para um caso em que possamos ter uma entrada incompleta:

a4 = [ [ 'apple',                   1],
       [ 'banana',                  2],
       [ ['orange','seedless'],     3],
       [ 'durian'                    ], # a spiky fruit pricks us: no value!
     ]

Agora, para trabalhar:

Começando com uma variedade inicialmente-flat, a1:

Alguns sugeriram o uso #to_h(que apareceu no Ruby 2.1.0 e pode ser portado para versões anteriores). Para uma matriz inicialmente plana, isso não funciona:

a1.to_h   # => TypeError: wrong element type String at 0 (expected array)

O uso Hash::[]combinado com o operador splat faz:

Hash[*a1] # => {"apple"=>1, "banana"=>2}

Então essa é a solução para o caso simples representado por a1.

Com uma disposição de chave matrizes valor / par, a2:

Com uma matriz de [key,value]matrizes de tipos, há dois caminhos a seguir.

Primeiro, Hash::[]ainda funciona (como aconteceu com *a1):

Hash[a2] # => {"apple"=>1, "banana"=>2}

E então também #to_hfunciona agora:

a2.to_h  # => {"apple"=>1, "banana"=>2}

Portanto, duas respostas fáceis para o caso de matriz aninhada simples.

Isso permanece verdadeiro mesmo com sub-matrizes como chaves ou valores, como em a3:

Hash[a3] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]} 
a3.to_h  # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}

Mas os durianos têm picos (estruturas anômalas causam problemas):

Se obtivermos dados de entrada que não são balanceados, teremos problemas com #to_h:

a4.to_h  # => ArgumentError: wrong array length at 3 (expected 2, was 1)

Mas Hash::[]ainda funciona, apenas configurando nilcomo o valor para durian(e qualquer outro elemento da matriz em a4 que seja apenas uma matriz de 1 valor):

Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}

Achatamento - usando novas variáveis a5ea6

Algumas outras respostas mencionadas flatten, com ou sem 1argumento, então vamos criar algumas novas variáveis:

a5 = a4.flatten
# => ["apple", 1, "banana", 2,  "orange", "seedless" , 3, "durian"] 
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"] 

Eu escolhi usar a4como dados de base por causa do problema de equilíbrio que tivemos, que apareceu a4.to_h. Eu acho que chamar flattenpode ser uma abordagem que alguém pode usar para tentar resolver isso, que pode se parecer com o seguinte.

flattensem argumentos ( a5):

Hash[*a5]       # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)

À primeira vista, isso parece funcionar - mas nos deu o pé errado com as laranjas sem sementes, criando assim 3uma chave e durianum valor .

E isso, assim como a1acontece, simplesmente não funciona:

a5.to_h # => TypeError: wrong element type String at 0 (expected array)

Portanto, a4.flattennão é útil para nós, apenas queremos usarHash[a4]

O flatten(1)caso ( a6):

Mas e o achatamento apenas parcialmente? Vale ressaltar que chamar Hash::[]usando splatno array parcialmente achatado ( a6) não é o mesmo que chamar Hash[a4]:

Hash[*a6] # => ArgumentError: odd number of arguments for Hash

Matriz pré-nivelada, ainda aninhada (maneira alternativa de obter a6):

Mas e se fosse assim que obtivemos a matriz em primeiro lugar? (Ou seja, comparativamente aos a1nossos dados de entrada - desta vez, alguns dos dados podem ser matrizes ou outros objetos.) Vimos que Hash[*a6]isso não funciona, mas e se ainda desejássemos obter o comportamento em que o último elemento (importante! veja abaixo) atuou como uma chave para um nilvalor?

Em tal situação, ainda há uma maneira de fazer isso, usando Enumerable#each_slicepara retornar aos pares de chave / valor como elementos na matriz externa:

a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]] 

Observe que isso acaba nos gerando uma nova matriz que não é " idêntica " a4, mas tem os mesmos valores :

a4.equal?(a7) # => false
a4 == a7      # => true

E assim podemos novamente usar Hash::[]:

Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]

Mas há um problema!

É importante observar que a each_slice(2)solução só recupera a sanidade se a última chave foi a que falta um valor. Se posteriormente adicionarmos um par extra de chave / valor:

a4_plus = a4.dup # just to have a new-but-related variable name
a4_plus.push(['lychee', 4])
# => [["apple",                1],
#     ["banana",               2],
#     [["orange", "seedless"], 3], # multi-value key
#     ["durian"],              # missing value
#     ["lychee", 4]]           # new well-formed item

a6_plus = a4_plus.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian", "lychee", 4]

a7_plus = a6_plus.each_slice(2).to_a
# => [["apple",                1],
#     ["banana",               2],
#     [["orange", "seedless"], 3], # so far so good
#     ["durian",               "lychee"], # oops! key became value!
#     [4]]                     # and we still have a key without a value

a4_plus == a7_plus # => false, unlike a4 == a7

E os dois hashes que obtemos disso são diferentes em aspectos importantes:

ap Hash[a4_plus] # prints:
{
                     "apple" => 1,
                    "banana" => 2,
    [ "orange", "seedless" ] => 3,
                    "durian" => nil, # correct
                    "lychee" => 4    # correct
}

ap Hash[a7_plus] # prints:
{
                     "apple" => 1,
                    "banana" => 2,
    [ "orange", "seedless" ] => 3,
                    "durian" => "lychee", # incorrect
                           4 => nil       # incorrect
}

(Nota: eu estou usando awesome_printé ap. Apenas para torná-lo mais fácil de mostrar a estrutura aqui, não há nenhuma exigência conceitual para isso)

Portanto, a each_slicesolução para uma entrada plana desequilibrada só funciona se o bit desbalanceado estiver no final.


Aprendizado:

  1. Sempre que possível, configure a entrada para essas coisas como [key, value]pares (uma sub-matriz para cada item na matriz externa).
  2. Quando você pode realmente fazer isso, um #to_hou Hash::[]ambos irão funcionar.
  3. Se você não conseguir, Hash::[]combinado com o splat ( *) funcionará, desde que as entradas sejam balanceadas .
  4. Com uma matriz desequilibrada e plana como entrada, a única maneira de isso funcionar razoavelmente é se o último value item for o único que está faltando.

Nota lateral: estou postando esta resposta porque sinto que há valor a ser adicionado - algumas das respostas existentes têm informações incorretas e nenhuma (que li) deu uma resposta tão completa quanto estou tentando fazer aqui. Espero que seja útil. No entanto, agradeço aos que vieram antes de mim, vários dos quais inspiraram partes desta resposta.


3

Anexando à resposta, mas usando matrizes anônimas e anotando:

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

Desmontando essa resposta, começando por dentro:

  • "a,b,c,d" é realmente uma string.
  • split vírgulas em uma matriz.
  • zip junto com a seguinte matriz.
  • [1,2,3,4] é uma matriz real.

O resultado intermediário é:

[[a,1],[b,2],[c,3],[d,4]]

achatar e depois transforma isso para:

["a",1,"b",2,"c",3,"d",4]

e depois:

*["a",1,"b",2,"c",3,"d",4] desenrola isso em "a",1,"b",2,"c",3,"d",4

que podemos usar como argumentos para o Hash[]método:

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

que produz:

{"a"=>1, "b"=>2, "c"=>3, "d"=>4}

Isso também funciona sem o splat ( *) e achatar: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}. Mais detalhes em uma resposta que adicionei.
Lindes

0

se você tiver uma matriz assim:

data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]

e você deseja que os primeiros elementos de cada matriz se tornem as chaves do hash e o restante dos elementos se tornem matrizes de valor, faça algo assim:

data_hash = Hash[data.map { |key| [key.shift, key] }]

#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}

0

Não tenho certeza se é a melhor maneira, mas isso funciona:

a = ["apple", 1, "banana", 2]
m1 = {}
for x in (a.length / 2).times
  m1[a[x*2]] = a[x*2 + 1]
end

b = [["apple", 1], ["banana", 2]]
m2 = {}
for x,y in b
  m2[x] = y
end

-1

Se os valores numéricos forem índices seq, poderíamos ter maneiras mais simples ... Aqui está o meu envio de código, meu Ruby está um pouco enferrujado

   input = ["cat", 1, "dog", 2, "wombat", 3]
   hash = Hash.new
   input.each_with_index {|item, index|
     if (index%2 == 0) hash[item] = input[index+1]
   }
   hash   #=> {"cat"=>1, "wombat"=>3, "dog"=>2}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.