【问题标题】:JS: Filter array of objects by max value per categoryJS:按每个类别的最大值过滤对象数组
【发布时间】:2020-11-25 18:48:43
【问题描述】:

什么是最有效/优雅的方式来实现类似 sql 的过滤效果。我想过滤它们并仅获取某些组中最大值的对象。

这是我的代码,它可以工作,但可能不是最好的方法:

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)
    // 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 } ]

【问题讨论】:

    标签: javascript arrays


    【解决方案1】:

    Reduce 数组到一个对象,使用name 属性作为键。对于每个项目,检查累加器中存在的项目是否具有比当前项目更高的值,如果没有,则将其替换为当前项目。用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)

    【讨论】:

    • 没有。我们需要将三元 r[o.name] && r[o.name].value > o.value ? r[o.name] : o 的结果分配给 r[o.name]
    • 然后我看到 "r[o.name] = r[o.name] && r[o.name].value > o.value ? r[o.name] : o" 是 " r[o.name] = (r[o.name] && r[o.name].value > o.value) ? r[o.name] : o"
    【解决方案2】:

    我喜欢用lodash 来处理这样的事情。它非常实用,因此非常清晰明了。

    看看下面的代码:

    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}]
    

    【讨论】:

    【解决方案3】:

    这是另一个 reduce 替代方案:

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

    【讨论】:

      【解决方案4】:

      什么是最有效/优雅的方式来实现类似sql的过滤效果。

      您可以为每个步骤获取函数并将所有函数传递给单个结果。

      例如在 SQL 中,您将有以下查询:

      SELECT name, value, MAX(timeStamp) 
      FROM data 
      GROUP BY name;
      

      使用类似 SQL 的方法,您可以先分组,然后从结果集中取出最大对象。

      result = pipe(
          groupBy('name'),
          select(max('timeStamp'))
      )(data);
      

      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);
      .as-console-wrapper { max-height: 100% !important; top: 0; }

      【讨论】:

        【解决方案5】:

        您可以使用.reduce(),方法是保留一个累积对象,该对象保留当前找到的最大组,然后使用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 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);

        【讨论】:

          【解决方案6】:

          分阶段进行。

          1. 获取一组名称
          2. 按降序创建排序数组
          3. 然后只需使用 find 映射即可获得第一个

          下面是一个例子。

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

          【讨论】:

            【解决方案7】:

            使用map()foreach() 获得所需的输出

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2020-01-13
              • 1970-01-01
              • 2022-06-11
              • 2015-02-16
              • 2020-10-22
              • 1970-01-01
              • 2022-01-23
              相关资源
              最近更新 更多