Pergunta

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

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

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

{apple => 1, banana => 2}
Foi útil?

Solução

NOTA:De uma forma concisa e eficiente solução, por favor, consulte Marc-André Lafortune resposta abaixo.

Esta resposta foi originalmente oferecido como uma alternativa para as abordagens usando achatar, que foram os mais altamente votos positivos na hora de escrever.Eu deveria ter esclarecido que eu não tinha a intenção de apresentar este exemplo como uma melhor prática ou uma estratégia eficiente.Original responder a seguir.


Atenção! Soluções achate não vai preservar a Matriz de chaves ou valores!

Edifício @João Topley populares resposta, 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 estava esperando uma Matriz de mesmo comprimento (e.g.['k1','v1,'k2','v2']).E o pior é que uma Matriz diferente que achatada para um mesmo comprimento seria de apenas silenciosamente nos dar um Hash com valores incorretos.

Se você quiser usar chaves de Matriz ou valores, você pode usar mapa:

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

Isso preserva a Matriz chave:

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

Outras dicas

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}"

Usando Array#flatten(1) limites de recursão para Array chaves e valores funcionam como esperado.

A melhor maneira é usar Array#to_h:

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

Note que to_h também aceita um bloco:

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

Nota: to_h aceita um bloco em Ruby 2.6.0+;para o início de rubis você pode usar o meu backports gem e require 'backports/2.6.0/enumerable/to_h'

to_h sem um bloco foi introduzido em Ruby 2.1.0.

Antes de Ruby 2.1, pode-se usar o menos legível Hash[]:

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

Por fim, desconfie de soluções usando flatten, isso poderia criar problemas com valores que são matrizes de si.

Atualização

Ruby 2.1.0 é lançado hoje.E me vem com Array#to_h (notas de lançamento e ruby-doc), que resolve o problema da conversão de um Array para um Hash.

Ruby docs exemplo:

[[:foo, :bar], [1, 2]].to_h    # => {:foo => :bar, 1 => 2}

Editar:Vi as respostas postadas enquanto eu estava escrevendo, de Hash[um.achate], parece-me o caminho a percorrer.Deve ter perdido um pouco na documentação quando eu estava pensando através da resposta.Pensei que as soluções que eu escrevi pode ser utilizado 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 = retorno do valor de hash (aquele que se acumulam na), i = item na matriz

O mais puro maneira que eu posso 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 }

Você também pode simplesmente converter um array 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} 

Acrescentar a resposta, mas usando anônimo matrizes e anotar:

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

Tendo que responder a aparte, a partir de dentro:

  • "a,b,c,d" é, na verdade, uma seqüência de caracteres.
  • split no vírgulas em uma matriz.
  • zip de que, juntamente com a seguinte matriz.
  • [1,2,3,4] é uma matriz real.

O resultado intermediário é:

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

achate-o, em seguida, transforma a:

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

e em seguida:

*["a",1,"b",2,"c",3,"d",4] desbobinar que em "a",1,"b",2,"c",3,"d",4

o que podemos usar como argumentos para a 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}

Resumo & TL;DR:

Esta resposta espera para ser uma lista completa de wrap-up de informação a partir de outras respostas.

A versão muito curta, considerando os dados da questão, além de um par de 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.


O programa de configuração:variáveis

A fim de mostrar os dados que iremos usar na frente, eu vou criar algumas variáveis para representar as várias possibilidades para os dados.Eles se encaixam nas seguintes categorias:

Com base no que foi diretamente na questão, como a1 e a2:

(Nota:Eu presumo que apple e banana foram feitos para representar variáveis.Como outros têm feito, eu vou estar usando seqüências de caracteres a partir de agora, de modo que a entrada e os resultados podem coincidir.)

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

Multi-teclas de valor e/ou valores, como a3:

Em algumas outras respostas, uma outra possibilidade foi apresentada (que eu expanda aqui) – chaves e/ou valores podem ser matrizes em seus próprios:

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

Desequilibrado matriz, como a4:

Para uma boa medida, pensei em adicionar um para um caso onde podemos ter uma incompleta de entrada:

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

Agora, para o trabalho:

Começando com um, inicialmente, televisão de matriz, a1:

Alguns têm sugerido usando #to_h (que apareceu em Ruby 2.1.0, e pode ser backported para versões anteriores).Para um inicialmente-matriz simples, isso não funciona:

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

Usando Hash::[] combinado com o splat operador faz:

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

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

Com uma matriz de chave/valor do par de matrizes, a2:

Com uma matriz de [key,value] tipo de matrizes, há duas maneiras de o fazer.

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

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

E, em seguida, também #to_h funciona agora:

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

Assim, duas respostas fáceis para o simples aninhadas matriz de caso.

Isso permanece verdadeiro mesmo com sub-matrizes como chaves ou valores, como com 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 durians têm picos (anômala estruturas de dar problemas):

Se chegamos dados de entrada que não é equilibrada, vamos executar em problemas com #to_h:

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

Mas Hash::[] ainda funciona, apenas a definição de nil como o valor para durian (e qualquer outro elemento de matriz em a4 que apenas uma 1-matriz de valor):

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

Achatamento - usando novas variáveis a5 e a6

Algumas outras respostas mencionadas flatten, com ou sem um 1 argumento, 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 a4 como a base de dados por causa do saldo de problema que tinha, o qual mostrou-se com a4.to_h.Eu acho que chamar flatten pode ser uma abordagem que alguém pode usar para tentar resolver isso, que pode parecer o seguinte.

flatten sem argumentos (a5):

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

No ingênua de vista, isso parece funcionar – mas ele nos tirou com o pé errado com as laranjas sem sementes, assim também a fazer 3 um chave e durian um valor.

E isso, como com a1, simplesmente não funciona:

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

Então, a4.flatten não é útil para nós, tínhamos apenas deseja usar Hash[a4]

O flatten(1) caso (a6):

Mas o que sobre apenas parcialmente achatamento?Vale a pena notar que chamar Hash::[] usando splat no parcialmente achatada array (a6) é não o mesmo que chamar Hash[a4]:

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

Pré-achatado matriz, ainda aninhada (forma alternativa de obtenção de a6):

Mas o que se era assim que tinha começado a matriz, em primeiro lugar?(Isto é, comparativamente ao a1, foi a nossa entrada de dados - só que desta vez alguns dos dados podem ser vetores ou outros objetos.) Vimos que Hash[*a6] não funciona, mas o que se é ainda queria ter o comportamento em que a o último elemento (importante!veja a seguir) agiu como uma chave para um nil valor?

Em tal situação, ainda há uma maneira de fazer isso, usando Enumerable#each_slice para obter-nos de volta à chave/valor pares como elementos exteriores matriz:

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

Note que isso acaba nos levando a uma nova matriz que não é "idêntico"a a4, mas não tem a mesmos valores:

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

E, assim, podemos usar novamente 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 notar que o each_slice(2) solução só recebe coisas de volta à sanidade, se o última chave foi a falta de um valor.Se nós, mais tarde, acrescentaram uma par 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 de nós deseja obter a partir de este 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's ap apenas para torná-lo mais fácil de mostrar que a estrutura aqui;não há conceitual requisito para esta.)

De modo que o each_slice solução para o desequilíbrio da televisão de entrada só funciona se o desequilíbrio é pouco no final.


Take-aways:

  1. Sempre que possível, defina a entrada para essas coisas como [key, value] pares (uma sub-matriz para cada item na parte externa de matriz).
  2. Quando na verdade, você pode fazer, ou #to_h ou Hash::[] tanto trabalho.
  3. Se você estiver não é possível, Hash::[] combinado com o splat (*) vai trabalhar, enquanto as entradas são balanceadas.
  4. Com um desequilibrado e televisão matriz como entrada, a única maneira que isto vai funcionar em todos os razoável é se o última value item é o único que falta.

Lateral-nota:Estou postando esta resposta, porque eu sinto que não há valor a ser adicionado – alguns dos existentes respostas incorrectas informações, e nenhum (que eu li) deu como concluída uma resposta como eu estou me esforçando para fazer aqui.Espero que seja útil.Eu, no entanto, dar graças a aqueles que vieram antes de mim, vários dos quais servido de inspiração para as partes desta resposta.

se você tiver matriz semelhante a este -

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

e você quer que os primeiros elementos de cada matriz para tornar as chaves de hash e o resto dos elementos, tornando-se valor de matrizes, então você pode fazer algo como isso -

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

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

Não tenho certeza se é a melhor forma, 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

Se os valores numéricos são seq índices, em seguida, poderíamos ter maneiras simples...Aqui está o meu código de submissão, o Meu Ruby é 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}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top