1
votes

Réduire le tableau Javascript en une carte, créer des clés à partir d'une valeur interne

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:

  1. Recherchez la première occurrence d'une valeur d'une paire clé-valeur interne.
  2. Copiez cette valeur dans une carte
  3. Utilisez la valeur d'une autre paire clé-valeur comme clé pour la carte.

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.


4 commentaires

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).


5 Réponses :


6
votes

Vous pouvez utiliser

  1. Array # filter pour supprimer toutes les entrées qui finiront par produire des valeurs en double
  2. Array # map sur vos données pour produire des paires valeur / clé
  3. Il suffit de collecter dans une carte via le constructeur

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.


3 commentaires

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.



4
votes

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.

  • accumulateur ( acc dans l'exemple)
    • Il s'agit de la valeur précédemment renvoyée par le dernier élément
  • currentValue ( log dans l'exemple)
    • Il s'agit de l'élément en cours de traitement dans le tableau
  • index (non utilisé dans l'exemple)
    • Ceci est l'index de l'élément courant
  • array (non utilisé dans l'exemple)
    • Le tableau d'origine sur lequel 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


2 commentaires

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.



0
votes

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);


0 commentaires

1
votes

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.

Question 2: Y a-t-il un nom pour cette opération?

Non.


1 commentaires

Merci beaucoup d'avoir répondu. C'est génial de voir autant d'angles d'attaque différents sur ce problème.



0
votes

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"));


0 commentaires