Ceci est une tentative pour répondre à une question précédente . . J'ai l'impression que c'est juste assez différent pour justifier un autre sujet dans l'espoir d'aider quiconque essaie de résoudre le même problème.
Si j'ai un ensemble de données de paires clé-valeur, disons que je veux accomplir 3 choses:
Donc, par exemple, disons que j'ai l'ensemble de données suivant:
[ "2019-01-01" => "snow", "2019-02-01" => "none", "2019-03-01" => "rain", "2019-06-01" => "hail" ]
Je voudrais finir avec l'objet Map suivant: p >
[ {"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"}, {"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"}, {"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"}, {"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"}, {"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"}, {"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"}, {"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"} ]
Comme dernier défi, comment pourrais-je faire cela dans une fonction où mon résultat peut être dynamique? Donc, dans l'exemple ci-dessus, j'ai choisi «précip» comme valeur souhaitée dans la carte finale. Mais que faire si je voulais «saison»? Y a-t-il un moyen de le faire de manière dynamique, où je peux passer le nom de la 'clé' comme argument d'une fonction?
De plus, y a-t-il un nom pour cette opération? J'ai eu du mal à trouver un titre. Si quelqu'un a une meilleure idée, je la renommerais volontiers. J'ai l'impression que c'est un problème élégant que beaucoup peuvent rencontrer.
5 Réponses :
Vous pouvez utiliser
Array # filter
pour supprimer toutes les entrées qui finiront par produire des valeurs en double Array # map
sur vos données pour produire des paires valeur / clé Pour avoir cette dynamique, il vous suffit de fournir les noms des propriétés que vous utilisez pour votre clé et votre valeur:
const data = [ {"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"}, {"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"}, {"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"}, {"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"}, {"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"}, {"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"}, {"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"} ]; function transform(keyProp, valueProp, arr) { const keyValuePairs = arr .filter(function(obj) { const value = obj[valueProp]; //only keep the value if it hasn't been encountered before const keep = !this.has(value); //add the value, so future repeats are removed this.add(value) return keep; }, new Set()) // <-- pass a Set to use as `this` in the callback .map(obj => [obj[keyProp], obj[valueProp]]); return new Map(keyValuePairs); } const map = transform("date", "precip", data); //Stack Snippets don't print the Map content //via console.log(map), so doing it manually for (let [key, value] of map) { console.log(`${key} -> ${value}`); }
Notez que cela utilise le deuxième argument de .filter
- il définit le this
contexte du rappel. En le définissant sur un ensemble, cela garantit qu'il n'est utilisé que pour l'opération .filter
- vous n'avez pas besoin de garder une variable supplémentaire dans la portée de la fonction entière. De plus, comme il définit le contexte this
, vous avez besoin d'une fonction
normale par opposition à une fonction de flèche car la valeur cette
ne peut pas être modifiée pour le dernier.
C'est une solution magnifiquement élégante, mais ce n'est que pour la moitié du problème. Cette solution fait le travail de création de la carte. L'autre moitié du problème est que je ne veux que les résultats qui fournissent la première occurrence de valueProp
. Utiliser date
et précip
renverrait une carte de taille 4 (voir la description ci-dessus).
@jasonmclose excuses, j'ai complètement manqué que vous vouliez les valeurs uniques. Je vais retravailler ça.
Bien joué. Merci beaucoup d'avoir répondu. C'était un problème sympa à rencontrer, et c'était formidable d'avoir autant de gens qui répondent. Votre solution a fait l'affaire.
C'est un bon scénario pour Array.prototype.reduce
const invertMap = map => { const entires = Array.from(map.entries()); const reversedKeyValues = entires.map(([key, value]) => [value, key]); return new Map(reversedKeyValues); }; const weatherLogs = [ {"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"}, {"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"}, {"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"}, {"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"}, {"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"}, {"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"}, {"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"} ]; const firstPrecipToDate = weatherLogs.reduce((acc, log) => { const { date, precip } = log; if (!acc.has(precip)) { // only add precip to the `precip->date` map if we don't have it yet acc.set(precip, date); } return acc; }, new Map()); // Now invert map to be date -> precip const dateToPrecip = invertMap(firstPrecipToDate); console.log(dateToPrecip); console.log('-- It shows as empty object in stackoverflow, view browser console');
weatherLogs.reduce
prend 2 arguments. Le premier argument est un callback et le second argument est la valeur initiale (de l'accumulateur).
Le callback est exécuté sur chaque élément du tableau dans l'ordre et peut prendre le arguments suivants.
acc
dans l'exemple)
log
dans l'exemple)
reduction
a été appelé En savoir plus sur Array.prototype.reduce
ici - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
Réalisé que cela ne répond pas entièrement à la question. J'essaierai de mettre à jour en conséquence
Merci beaucoup pour votre contribution. Cela a accompli la tâche. La seule raison pour laquelle je ne l'ai pas vérifié car la solution est que l'autre solution de @VLAZ a ajouté l'option dynamique de pouvoir passer des noms de colonnes. Mais je tiens à féliciter votre code, car je n'aurais jamais rien trouvé de proche.
Utilisez reductionRight
, map
et sort
.
const arr = [{"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"},{"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"},{"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"},{"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"},{"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"},{"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"},{"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"}]; const key = "precip"; const res = new Map(Object.entries(arr.reduceRight((a, { date, [key]: c }) => (a[c] = date, a), {})).map(e => e.reverse()).sort(([a], [b]) => new Date(a) - new Date(b))); console.log(res);
Vous avez en fait quelques questions. Je vais répondre un par un.
Question 1: Rendez-le dynamique
Tout ce que vous avez à faire est d'accepter quelques arguments supplémentaires comme indiqué ci-dessous.
De cette façon, non seulement vous pouvez rendre dynamique la valeur
de votre résultat, mais vous pouvez également rendre la clé
de votre résultat dynamique.
En raison de la nature dynamique, vous voudriez une vérification plus détaillée la plupart du temps. Vous verrez donc beaucoup de lancers conditionnels ci-dessous. Retirez-le comme bon vous semble.
const data = [ {"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"}, {"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"}, {"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"}, {"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"}, {"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"}, {"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"}, {"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"} ]; let result; result = firstOccurance(data, 'date', 'precip'); console.log('date => precip'); log(result); result = firstOccurance(data, 'date', 'season'); console.log('date => season'); log(result); result = firstOccurance(data, 'temp', 'precip'); console.log('temp => precip'); log(result); /** * All you need to do is accept a few more arguments: * 1. keyPropName * 2. valuePropName * * This not only make value dynamic, the key can also be dynamic. */ function firstOccurance(data, keyPropName, valuePropName){ if (keyPropName === valuePropName) throw new TypeError('`keyPropName` and `valuePropName` cannot be the same.'); const ret = new Map(); for (const obj of data){ if (!hasOwnProperty(obj, keyPropName) || !hasOwnProperty(obj, valuePropName)) throw new ReferenceError(`Property ${keyPropName} is not found in the dataset.`); const key = obj[keyPropName] const value = obj[valuePropName]; // Check if `value` already exist in Map. if ([...ret.values()].includes(value)) continue; ret.set(key, value); } return ret; } function hasOwnProperty(obj, name){ return Object.prototype.hasOwnProperty.call(obj, name); } function log(iterable){ for (const [key, val] of iterable){ console.log(key, '=>', val); } console.log('\n'); }
Vous pouvez continuer et changer le keyPropName
et valuePropName
en autre chose, et cela fonctionnera très bien et comme prévu.
Non.
Merci beaucoup d'avoir répondu. C'est génial de voir autant d'angles d'attaque différents sur ce problème.
Essayez cette approche:
const data = [ {"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"}, {"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"}, {"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"}, {"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"}, {"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"}, {"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"}, {"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"} ] function getFirstOccurenceMap(key, value) { const myMap = new Map(); for (let i=0; i<data.length; i++) { if (data[i][value] && !myMap.has(data[i][value])) { myMap.set(data[i][value], data[i][key]); } } result = new Map(); if (myMap) { for (var [key, value] of myMap) { if (!result.has(value)) { result.set(value, key); } } } return result; } console.log(getFirstOccurenceMap("date", "temp"));
Qu'est-ce que
"2019-06-01" => "grêle"
?@ChrisLi clé qui est
"2019-06-01"
et elle est associée à la valeur"hail"
.Comment définissez-vous la première occurrence? Le premier qui apparaît dans le tableau même s'il peut contenir des dates dans un ordre non chronologique ou le premier en termes de chronologie?
Désolé de ne pas vous avoir répondu rapidement. La première occurrence serait simplement celle avec l'index numérique le plus bas (la première à apparaître dans une itération du tableau).