4
votes

JS: filtre le tableau d'objets par valeur maximale par catégorie

Quelle est la manière la plus efficace / élégante d'obtenir un effet de filtrage de type SQL. Je veux les filtrer et obtenir uniquement les objets qui ont une valeur maximale dans un groupe.

Ceci est mon code, cela fonctionne mais ce n'est probablement pas le meilleur moyen:

    // Input:
    [ { name: 'bathroom',
        value: 54,
        timeStamp: 1562318089713 },
      { name: 'bathroom',
        value: 55,
        timeStamp: 1562318090807 },
      { name: 'bedroom',
        value: 48,
        timeStamp: 1562318092084 },
      { name: 'bedroom',
        value: 49,
        timeStamp: 1562318092223 },
      { name: 'room',
        value: 41,
        timeStamp: 1562318093467 } ]

    // Output:
    [ { name: 'bathroom',
        value: 55,
        timeStamp: 1562318090807 },
      { name: 'bedroom',
        value: 49,
        timeStamp: 1562318092223 },
      { name: 'room',
        value: 41,
        timeStamp: 1562318093467 } ]
uniqueValues = (arr) => [...new Set(arr)];
getMaxTimeOf = (arr) => Math.max(...arr.map(o => o.timeStamp), 0);
selectorName = (name) => (obj) => obj.name === name;
selectorTime = (time) => (obj) => obj.timeStamp === time;
getGroup = (obj, selector) => obj.filter(selector)

onlyLastChangedFrom = (history) => {
const uniqueNames = uniqueValues(history.map(o => o.name))
let filtered = []
 uniqueNames.forEach(name => {
  const group = getGroup(history, selectorName(name))
  const groupLastTime = getMaxTimeOf(group)
  const lastChange = getGroup(group, selectorTime(groupLastTime))
  filtered.push(lastChange[0])
 });
 return filtered
}   
onlyLastChangedFrom(history)


0 commentaires

7 Réponses :


8
votes

Réduisez le tableau en objet, en utilisant la propriété name comme clé. Pour chaque élément, vérifiez si l'élément qui existe dans l'accumulateur a une valeur supérieure à l'élément actuel, et sinon remplacez-le par l'élément actuel. Object.values() un tableau avec Object.values() :

const arr = [{"name":"bathroom","value":54,"timeStamp":1562318089713},{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":48,"timeStamp":1562318092084},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}]

const result = Object.values(arr.reduce((r, o) => {
  r[o.name] = (r[o.name] && r[o.name].value > o.value) ? r[o.name] : o

  return r
}, {}))

console.log(result)


6 commentaires

"r [o.name] = r [o.name]" est redondant, non?


Non. Nous devons attribuer à r[o.name] le résultat du ternaire r[o.name] && r[o.name].value > o.value ? r[o.name] : o .


Je vois alors "r [o.name] = r [o.name] && r [o.name] .value> o.value? R [o.name]: o" est "r [o.name] = ( r [o.name] && r [o.name] .value> o.value)? r [o.name]: o "


J'ai mis à jour la réponse avec votre suggestion.


Super, merci pour le code.


Vous êtes les bienvenus :)



0
votes

Vous pouvez utiliser .reduce() en conservant un objet accumulé qui conserve le groupe max actuellement trouvé, puis utiliser Object.values() pour obtenir un tableau de ces objets (au lieu d'une relation d'objet clé-valeur).

Voir l'exemple ci-dessous:

const arr=[{name:"bathroom",value:54,timeStamp:1562318089713},{name:"bathroom",value:55,timeStamp:1562318090807},{name:"bedroom",value:48,timeStamp:1562318092084},{name:"bedroom",value:49,timeStamp:1562318092223},{name:"room",value:41,timeStamp:1562318093467}];

const res = Object.values(arr.reduce((acc, o) => {
  acc[o.name] = acc[o.name] || o;
  if (o.value > acc[o.name].value)
    acc[o.name] = o;
  return acc;
}, {}));

console.log(res);


0 commentaires

1
votes

J'adore utiliser lodash pour des trucs comme ça. C'est très fonctionnel et donc très clair et simple.

Jetez un œil au code suivant:

const DATA = [
  {
    name: "bathroom",
    value: 54,
    timeStamp: 1562318089713
  },
  {
    name: "bathroom",
    value: 55,
    timeStamp: 1562318090807
  },
  {
    name: "bedroom",
    value: 48,
    timeStamp: 1562318092084
  },
  {
    name: "bedroom",
    value: 49,
    timeStamp: 1562318092223
  },
  {
    name: "room",
    value: 41,
    timeStamp: 1562318093467
  }
];

let max = _
  .chain(DATA)
  .groupBy('name')
  .sortBy('value')
  .map(o => _(o).reverse().first())
  .flatten()
  .value();

console.log(max); // returns [{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}]


0 commentaires

0
votes

Faire cela par étapes.

  1. obtenir un ensemble de noms
  2. créer un tableau trié par ordre décroissant
  3. puis mappez simplement en utilisant find pour obtenir le premier

Voici un exemple.

const input = [{"name":"bathroom","value":54,"timeStamp":1562318089713},{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":48,"timeStamp":1562318092084},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}];

const output = [...new Set(input.map(m => m.name))].
  map(m => [...input].sort(
    (a,b) => b.value - a.value).
    find(x => m === x.name));
  
console.log(output);


0 commentaires

2
votes

Quelle est la manière la plus efficace / élégante d'obtenir un effet de filtrage de type SQL.

Vous pouvez prendre des fonctions pour chaque étape et diriger toutes les fonctions pour un seul résultat.

Par exemple, en SQL, vous auriez la requête suivante:

.as-console-wrapper { max-height: 100% !important; top: 0; }

Avec une approche de type SQL, vous pouvez d'abord grouper et retirer l'objet max des ensembles de résultats.

const
    pipe = (...functions) => input => functions.reduce((acc, fn) => fn(acc), input),
    groupBy = key => array => array.reduce((r, o) => {
        var temp = r.find(([p]) => o[key] === p[key])
        if (temp) temp.push(o);
        else r.push([o]);
        return r;
    }, []),
    max = key => array => array.reduce((a, b) => a[key] > b[key] ? a : b),
    select = fn => array => array.map(fn);


var data = [{ name: 'bathroom', value: 54, timeStamp: 1562318089713 }, { name: 'bathroom', value: 55, timeStamp: 1562318090807 }, { name: 'bedroom', value: 48, timeStamp: 1562318092084 }, { name: 'bedroom', value: 49, timeStamp: 1562318092223 }, { name: 'room', value: 41, timeStamp: 1562318093467 }],
    result = pipe(
        groupBy('name'),
        select(max('timeStamp'))
    )(data);

console.log(result);

result = pipe(
    groupBy('name'),
    select(max('timeStamp'))
)(data);
SELECT name, value, MAX(timeStamp) 
FROM data 
GROUP BY name;

0 commentaires

1
votes

Voici une autre alternative à réduire:

var arr = [{"name":"bathroom","value":54,"timeStamp":1562318089713},{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":48,"timeStamp":1562318092084},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}];

var obj = arr.reduce((r, o) => (o.value < (r[o.name] || {}).value || (r[o.name] = o), r), {});

console.log( Object.values(obj) );


0 commentaires

0
votes

Utilisez map() et foreach() pour obtenir la sortie souhaitée

const arr=[{name:"bathroom",value:54,timeStamp:1562318089713}, 
    {name:"bathroom",value:55,timeStamp:1562318090807}, 
    {name:"bedroom",value:48,timeStamp:1562318092084}, 
    {name:"bedroom",value:49,timeStamp:1562318092223}, 
    {name:"room",value:41,timeStamp:1562318093467}];

let res = new Map();
arr.forEach((obj) => {
    let values = res.get(obj.name);
    if(!(values && values.value > obj.value)){ 
        res.set(obj.name, obj) 
    }
})
console.log(res);
console.log([...res])


0 commentaires