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}
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:
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}
h3 = Hash[*a3.flatten(1)]
vez de h3 = Hash[*a3.flatten]
qual seria um erro.
to_h
é melhor.
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 Array
chaves e valores funcionem conforme o esperado.
Hash[*ary.flatten(1)]
, o que preservará as chaves e os valores da matriz. É o recursivo flatten
que está destruindo aqueles, o que é fácil de evitar.
A melhor maneira é usar Array#to_h
:
[ [:apple,1],[:banana,2] ].to_h #=> {apple: 1, banana: 2}
Observe 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 no Ruby 2.6.0+; para rubis precoces, você pode usar minha backports
gema 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.
to_h
método do que as respostas acima, pois expressa a intenção de converter depois de operar na matriz.
Array#to_h
nem Enumerable#to_h
está em núcleo Ruby 1.9.
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]
e desejar a saída como {"apple" =>[1,3], "banana"=>[2,4]}
?
Atualizar
O Ruby 2.1.0 é lançado hoje . E eu venho com Array#to_h
( notas de versão e ruby-doc ), que resolve o problema de converter um Array
em um Hash
.
Exemplo de documentação do Ruby:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
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 }
a.inject({})
única que permite atribuições de valor mais flexíveis.
h = {}
do segundo exemplo usando o injetar, terminando coma.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
a.each_slice(2).to_h
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.
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:
a1
e a2
:(Nota: Eu presumo que apple
e banana
foram 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
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] ],
]
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!
]
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
.
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_h
funciona agora:
a2.to_h # => {"apple"=>1, "banana"=>2}
Portanto, duas respostas fáceis para o caso de matriz aninhada simples.
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]}
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 nil
como 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}
a5
ea6
Algumas outras respostas mencionadas flatten
, com ou sem 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 dados de base por causa do problema de equilíbrio que tivemos, que apareceu a4.to_h
. Eu acho que chamar flatten
pode ser uma abordagem que alguém pode usar para tentar resolver isso, que pode se parecer com 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]`.)
À primeira vista, isso parece funcionar - mas nos deu o pé errado com as laranjas sem sementes, criando assim 3
uma chave e durian
um valor .
E isso, assim como a1
acontece, simplesmente não funciona:
a5.to_h # => TypeError: wrong element type String at 0 (expected array)
Portanto, a4.flatten
não é útil para nós, apenas queremos usarHash[a4]
flatten(1)
caso ( a6
):Mas e o achatamento apenas parcialmente? Vale ressaltar que chamar Hash::[]
usando splat
no array parcialmente achatado ( a6
) não é o mesmo que chamar Hash[a4]
:
Hash[*a6] # => ArgumentError: odd number of arguments for Hash
a6
):Mas e se fosse assim que obtivemos a matriz em primeiro lugar? (Ou seja, comparativamente aos a1
nossos 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 nil
valor?
Em tal situação, ainda há uma maneira de fazer isso, usando Enumerable#each_slice
para 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]
É 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_slice
solução para uma entrada plana desequilibrada só funciona se o bit desbalanceado estiver no final.
[key, value]
pares (uma sub-matriz para cada item na matriz externa).#to_h
ou Hash::[]
ambos irão funcionar.Hash::[]
combinado com o splat ( *
) funcionará, desde que as entradas sejam balanceadas .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.
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}
*
) 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.
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]}
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
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}