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}
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 deh3 = Hash[*a3.flatten]
qual seria um erro.to_h
é melhor.Basta usar
Hash[*array_variable.flatten]
Por exemplo:
O uso
Array#flatten(1)
limita a recursão para queArray
chaves e valores funcionem conforme o esperado.fonte
Hash[*ary.flatten(1)]
, o que preservará as chaves e os valores da matriz. É o recursivoflatten
que está destruindo aqueles, o que é fácil de evitar.A melhor maneira é usar
Array#to_h
:Observe que
to_h
também aceita um bloco:Nota :
to_h
aceita um bloco no Ruby 2.6.0+; para rubis precoces, você pode usar minhabackports
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[]
:Por fim, desconfie de qualquer solução em uso
flatten
, isso pode criar problemas com valores que são matrizes em si.fonte
to_h
método do que as respostas acima, pois expressa a intenção de converter depois de operar na matriz.Array#to_h
nemEnumerable#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 umArray
em umHash
.Exemplo de documentação do Ruby:
fonte
A segunda forma é mais simples:
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:
fonte
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
Você também pode simplesmente converter uma matriz 2D em hash usando:
fonte
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:
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
a1
ea2
:(Nota: Eu presumo que
apple
ebanana
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..)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:
Matriz desequilibrada, como
a4
:Para uma boa medida, pensei em adicionar um para um caso em que possamos ter uma entrada incompleta:
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:O uso
Hash::[]
combinado com o operador splat faz: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
):E então também
#to_h
funciona agora: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
: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
:Mas
Hash::[]
ainda funciona, apenas configurandonil
como o valor paradurian
(e qualquer outro elemento da matriz em a4 que seja apenas uma matriz de 1 valor):Achatamento - usando novas variáveis
a5
ea6
Algumas outras respostas mencionadas
flatten
, com ou sem1
argumento, então vamos criar algumas novas variáveis:Eu escolhi usar
a4
como dados de base por causa do problema de equilíbrio que tivemos, que apareceua4.to_h
. Eu acho que chamarflatten
pode ser uma abordagem que alguém pode usar para tentar resolver isso, que pode se parecer com o seguinte.flatten
sem argumentos (a5
):À primeira vista, isso parece funcionar - mas nos deu o pé errado com as laranjas sem sementes, criando assim
3
uma chave edurian
um valor .E isso, assim como
a1
acontece, simplesmente não funciona:Portanto,
a4.flatten
nã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::[]
usandosplat
no array parcialmente achatado (a6
) não é o mesmo que chamarHash[a4]
: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
a1
nossos dados de entrada - desta vez, alguns dos dados podem ser matrizes ou outros objetos.) Vimos queHash[*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 umnil
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:Observe que isso acaba nos gerando uma nova matriz que não é " idêntica "
a4
, mas tem os mesmos valores :E assim podemos novamente usar
Hash::[]
: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:E os dois hashes que obtemos disso são diferentes em aspectos importantes:
(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.Aprendizado:
[key, value]
pares (uma sub-matriz para cada item na matriz externa).#to_h
ouHash::[]
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.
fonte
Anexando à resposta, mas usando matrizes anônimas e anotando:
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 é:
achatar e depois transforma isso para:
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:que produz:
fonte
*
) 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:
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:
fonte
Não tenho certeza se é a melhor maneira, mas isso funciona:
fonte
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
fonte