J'essaie de parcourir un tableau de chiffres et de compter le nombre de fois que chaque chiffre est trouvé dans le tableau.
Dans ruby, c'est facile, je déclare simplement un Hash.new (0) code> et que le hachage est déjà configuré pour compter à partir de 0 comme valeur. Par exemple:
arr = [1,0,0,0,1,0,0,1]
counter = Hash.new(0)
arr.each { |num| counter[num] += 1 } # which gives {1=> 3, 0=> 5}
Je voulais faire la même chose en JavaScript mais let counter = {} me donne {'0': NaN, '1': NaN} .
Avez-vous une idée de la façon de créer ce même hachage en tant qu'objet en JavaScript?
3 Réponses :
Vous pouvez utiliser Carte ,
hachage en tant que Map hash , augmentez sa valeur de 1 sinon définissez-la sur 1
let arr = [1, 0, 0, 0, 1, 0, 0, 1]
let hash = new Map()
arr.forEach(val => {
hash.set(val, (hash.get(val) || 0) + 1)
})
console.log([...hash])
J'ai fait cette idée avec une instruction if else: `let hash = new Map () arr.forEach (num => {hash [num]? Hash [num] ++: hash [num] = 1})`. Existe-t-il un moyen en JavaScript d'éviter l'instruction If else et de faire simplement le comptage rapide comme dans mon exemple ruby dans l'article?
@Commando utilisant l'instruction if else n'affecte pas beaucoup les performances, celle que j'ai publiée utilise un OU logique
Y a-t-il un avantage à utiliser Map , set et get par rapport au littéral de hachage {} avec [] et [] = ?
@maxpleaner: les objets ECMAScript n'autorisent que des chaînes ou des symboles comme clés. Tout le reste sera implicitement converti en chaînes. En fonction de la proximité de l'analogie avec Ruby, cela pourrait poser un problème. Par exemple. dans Ruby, je peux avoir {0 => 23, '0' => 42} , dans ECMAScript, ce serait la même clé. Les Map sont l'équivalent ECMAScript des Hash de Ruby.
En Javascript, vous le faites avec Array.reduce
const reducer = (acc, e) => acc.set(e, (acc.get(e) || 0) + 1);
[1, 0, 0, 0, 1, 0, 0, 1].reduce(reducer, new Map())
//â Map(2)Â {1 => 3, 0 => 5}
C'est aussi la solution "correcte" dans Ruby arr.each_with_object (Hash.new (0)) {| el, acc | acc [el] + = 1} . La question est de savoir comment obtenir un dictionnaire avec des valeurs par défaut.
ECMAScript n'a pas de valeurs par défaut pour les clés manquantes dans les objets de la même manière que Ruby le fait pour les Hash es . Vous pouvez cependant utiliser la métaprogrammation introspective dynamique pour faire quelque chose de similaire, en utilisant Objets ECMAScript Proxy :
require 'multiset' Multiset[*arr] #=> #<Multiset:#5 0, #3 1>
Vous pouvez donc faire quelque chose comme ceci:
class Histogram extends DefaultMap {
constructor(iterator=undefined) {
super(undefined, 0);
if (iterator) {
for (const el of iterator) {
this.set(el);
}
}
}
set(key) {
super.set(key, this.get(key) + 1)
}
}
new Histogram(arr)
//=> Histogram [Map] { 1 => 3, 0 => 5 }
Cependant, ce n'est toujours pas équivalent à la version Ruby, où les clés du Hash peuvent être des objets arbitraires alors que les clés de propriété dans un objet ECMAScript ne peut être que des String et Symbole s.
Le l'équivalent direct d'un Hash Ruby est un Map ECMAScript.
Malheureusement, les cartes ECMAScript n'ont pas non plus de valeurs par défaut. Nous pourrions utiliser la même astuce que nous avons utilisée pour les objets et créer un Proxy , mais ce serait gênant car il faudrait intercepter les accès au get méthode de la Map , puis extrayez les arguments, appelez a , et ainsi de suite.
Heureusement, les cartes sont conçues pour être sous-classables:
arr.reduce(
(acc, el) => acc.set(el, acc.get(el) + 1),
new DefaultMap(undefined, 0)
)
//=> DefaultMap [Map] { 1 => 3, 0 => 5 }
Cela nous permet de faire quelque chose comme ceci:
class DefaultMap extends Map {
constructor(iterable=undefined, defaultValue=undefined) {
super(iterable);
Object.defineProperty(this, "defaultValue", { value: defaultValue });
}
get(key) {
return this.has(key) ? super.get(key) : this.defaultValue;
}
}
const hash = new DefaultMap(undefined, 42);
hash.has(1)
//=> false
hash.get(1)
//=> 42
Bien sûr, une fois que nous avons commencé à définir notre propre carte de toute façon, nous pourrions aller jusqu'au bout:
arr.reduce(
(acc, el) => { acc[el]++; return acc },
new Proxy(
{},
{ get: (target, name) => name in target ? target[name] : 0 }
)
)
//=> Proxy [ { '0': 5, '1': 3 }, { get: [Function: get] } ]
Cela démontre également une leçon très importante: le choix de la structure des données peut grandement influencer la complexité de l'algorithme. Avec le choix correct de la structure de données (un Histogramme ), l'algorithme disparaît complètement , tout ce que nous faisons est d'instancier la structure de données.
Notez que le il en va de même pour Ruby également. En choisissant la bonne structure de données (il existe plusieurs implémentations d'un MultiSet flottant sur le Web), tout votre algorithme disparaît et tout ce qui reste est :
const defaultValue = 42;
const proxyHandler = {
get: (target, name) => name in target ? target[name] : defaultValue
};
const underlyingObject = {};
const hash = new Proxy(underlyingObject, proxyHandler);
1 in hash
//=> false
1 in underlyingObject
//=> false
hash[1]
//=> 42
underlyingObject[1]
//=> undefined
Note de bas de page: dans Ruby vous abusez de
chacunpour réduire. Cela devrait être fait commearr.each_with_object (Hash.new {0}) {| num, counter | compteur [num] + = 1}.Note de bas de page: dans Ruby il pourrait être plus concis avec
[1,0,0,0,1,0,0,1] .group_by (&: lui-même ) .transform_values (&: count) # ⇒ {1 => 3, 0 => 5}.Vous ne pouvez pas faire cela, JavaScript n'a pas d'analogue pour
Hash.new (0)dans Ruby. Vous devez tester si la clé existe avant d'essayer d'incrémenter sa valeur.