44 votes

Convertir un tableau en un hachage d'index en Ruby

J'ai un tableau, et je veux faire un hash afin que je puisse rapidement se demander "est de X dans le tableau?".

En perl, il est facile (et rapide) pour ce faire:

my @array = qw( 1 2 3 );
my %hash;
@hash{@array} = undef;

Cela génère un hash qui ressemble à ceci:

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

Le meilleur que j'ai trouvé en Ruby, c'est:

array = [1, 2, 3]
hash = Hash[array.map {|x| [x, nil]}]

ce qui donne:

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

Est-il un meilleur Ruby façon?

EDIT 1

Non, Tableau.inclure? n'est pas une bonne idée. Sa lenteur. Il fait une requête en O(n) au lieu de O(1). Mon exemple du tableau a trois éléments à des fins de concision; assumer le réel a des millions d'éléments. Faisons une petite analyse comparative:

#!/usr/bin/ruby -w
require 'benchmark'

array = (1..1_000_000).to_a
hash = Hash[array.map {|x| [x, nil]}]

Benchmark.bm(15) do |x|
    x.report("Array.include?") { 1000.times { array.include?(500_000) } }
    x.report("Hash.include?") { 1000.times { hash.include?(500_000) } }
end

Produit:

                     user     system      total        real
Array.include?  46.190000   0.160000  46.350000 ( 46.593477)
Hash.include?    0.000000   0.000000   0.000000 (  0.000523)

44voto

rampion Points 38697

Si tout ce dont vous avez besoin du hash, c’est de l’adhésion, utilisez un Set :

 ------------------------------------------------------------- Class: Set
     Set implements a collection of unordered values with no duplicates.
     This is a hybrid of Array's intuitive inter-operation facilities
     and Hash's fast lookup.

     Several methods accept any Enumerable object (implementing +each+)
     for greater flexibility: new, replace, merge, subtract, |, &, -, ^.

     The equality of each couple of elements is determined according to
     Object#eql? and Object#hash, since Set uses Hash as storage.

     Finally, if you are using class Set, you can also use
     Enumerable#to_set for convenience.


Example
-------
       require 'set'
       s1 = Set.new [1, 2]                   # -> #<Set: {1, 2}>
       s2 = [1, 2].to_set                    # -> #<Set: {1, 2}>
       s1 == s2                              # -> true
       s1.add("foo")                         # -> #<Set: {1, 2, "foo"}>
       s1.merge([2, 6])                      # -> #<Set: {6, 1, 2, "foo"}>
       s1.subset? s2                         # -> false
       s2.subset? s1                         # -> true

------------------------------------------------------------------------

--------------------------------------------------------------- Set::new
     Set::new(enum = nil) {|o| ...}
------------------------------------------------------------------------
     Creates a new set containing the elements of the given enumerable
     object.

     If a block is given, the elements of enum are preprocessed by the
     given block.
 

24voto

edx Points 201

essaye celui-là:

 a=[1,2,3]
Hash[a.zip []]
 

14voto

viebel Points 1990

Vous pouvez faire ce truc très pratique:

 Hash[*[1, 2, 3, 4].map {|k| [k, nil]}.flatten]
=> {1=>nil, 2=>nil, 3=>nil, 4=>nil}
 

9voto

Zach Langley Points 3523

Si vous voulez rapidement demander "est de X dans le tableau?", vous devez utiliser Array#include?.

Modifier (en réponse à de plus en OP):

Si vous voulez speedy regarder les temps, l'utilisation d'un Ensemble. Avoir un Hash à tous les points de nils est stupide. La Conversion est un processus facile, trop avec Array#to_set.

require 'benchmark'
require 'set'

array = (1..1_000_000).to_a
set = array.to_set

Benchmark.bm(15) do |x|
    x.report("Array.include?") { 1000.times { array.include?(500_000) } }
    x.report("Set.include?") { 1000.times { set.include?(500_000) } }
end

Les résultats sur ma machine:

                     user     system      total        real
Array.include?  36.200000   0.140000  36.340000 ( 36.740605)
Set.include?     0.000000   0.000000   0.000000 (  0.000515)

Vous devriez considérer l'utilisation d'un ensemble, pour commencer, au lieu d'un tableau, de sorte que la conversion n'est jamais nécessaire.

6voto

chrismear Points 591

Je suis assez certain qu'il n'y a pas un one-shot moyen astucieux pour construire ce hash. Mon inclination serait d'être explicite et de l'état, ce que je fais:

hash = {}
array.each{|x| hash[x] = nil}

Il n'a pas l'air particulièrement élégant, mais il est clair, et fait le travail.

FWIW, votre proposition initiale (sous Ruby 1.8.6 au moins) ne semble pas fonctionner. Je reçois un "ArgumentError: nombre impair d'arguments en faveur de Hachage" erreur. De hachage.[] s'attend à un littéral, même-lengthed liste de valeurs:

Hash[a, 1, b, 2] # => {a => 1, b => 2}

j'ai donc essayé de changer votre code:

hash = Hash[*array.map {|x| [x, nil]}.flatten]

mais la performance est à dire:

#!/usr/bin/ruby -w
require 'benchmark'

array = (1..100_000).to_a

Benchmark.bm(15) do |x|
  x.report("assignment loop") {hash = {}; array.each{|e| hash[e] = nil}}
  x.report("hash constructor") {hash = Hash[*array.map {|e| [e, nil]}.flatten]}
end

donne

                     user     system      total        real
assignment loop  0.440000   0.200000   0.640000 (  0.657287)
hash constructor  4.440000   0.250000   4.690000 (  4.758663)

À moins que je me manque quelque chose ici, une simple affectation de la boucle semble la plus claire et la plus efficace pour construire ce hash.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X