【问题标题】:How to filter a javascript object array by grouping the objects based on a specific field value?如何通过基于特定字段值对对象进行分组来过滤 javascript 对象数组?
【发布时间】:2021-08-13 16:13:37
【问题描述】:

下面是我的js对象数组。

const objArray = [
    {
      file: 'file_1',
      start_time: '2021-08-12 14:00:00'
      status: 'pending'
    },
    {
      file: 'file_2',
      start_time: '2021-08-12 14:00:00'
      status: 'completed'
    },
    {
      file: 'file_3',
      start_time: '2021-08-14 15:00:00'
      status: 'pending'
    },
    {
      file: 'file_1',
      start_time: '2021-08-14 03:00:00'
      status: 'pending'
    },
    {
      file: 'file_2',
      start_time: '2021-08-14 03:00:00'
      status: 'pending'
    },
    {
      file: 'file_2',
      start_time: '2021-11-11 11:11:00'
      status: 'pending'
    }
]

从上面的数组中,我需要根据开始时间字段过滤对象。如果开始时间相同,则应将它们分组为子数组。在子数组中也不能有具有相同文件名的对象。例如,在上面的数组中,如果将对象 1&2 与 4&5 进行比较,它们每个都有自己的开始时间值,但它们的文件名相同。因此,我只需要其中一组,即时间戳最低的 1&2。 所以最终的输出数组应该如下,

[
  [
    {
      file: 'file_1',
      start_time: '2021-08-12 14:00:00'
      status: 'pending'
    },
    {
      file: 'file_2',
      start_time: '2021-08-12 14:00:00'
      status: 'completed'
    }
  ],
  [
    {
      file: 'file_3',
      start_time: '2021-08-14 15:00:00'
      status: 'pending'
    }
  ],
  [
    {
      file: 'file_2',
      start_time: '2021-11-11 11:11:00'
      status: 'pending'
    }
  ]
]

我尝试通过遍历初始数组中的每个对象来实现它。但是实现这一目标的最快方法是什么?

【问题讨论】:

  • 没有办法不遍历初始数组中的每个对象。让我们看看你的实现。
  • 并非如此。我需要根据开始时间将它们分组为子数组
  • 您似乎想要的输出是两个不相关操作的结果:(1)过滤与每个文件关联的最古老的项目(2)按日期对剩余项目进行分组。但是您的描述如此混乱,必须从示例输出中猜测预期结果。此外,这并不是对对象进行分组,因此您的问题的措辞具有误导性。我投票支持关闭它。

标签: javascript arrays javascript-objects


【解决方案1】:

任何想在 JavaScript 中执行相当于 SQL GroupBy 的方法的人都不会在这里找到答案。

这个问题是关于一个非常具体的算法,它执行两个步骤:

  1. 按日期将记录分组到子数组中
  2. 如果两个子数组的记录与完全相同的文件集相关,则认为它们是等效的。
    过滤等效子数组以仅保留日期最早的子数组

让我们用一些普通的 JavaScript 来做吧:

function do_some_filtering (records) {

    // create sets containing events grouped by date and index them by date
    let sets = {};
    for (let record of records) {
        // dates are converted to milliseconds since the Epoch for comparison
        let date = Date.parse(record.start_time);
        if (!sets[date]) sets[date] = [record]; else sets[date].push(record);
    }
    
    // filter "unique" sets based on the list of files present in each set
    let unique_date = {};
    for (let date in sets) {
        let signature = sets[date]       // this will concatenate all file names
                       .map(x => x.file) // to create a unique signature for
                       .sort()           // potentially deletable groups
                       .reduce((file,signature) => file+":"+signature)
        // if multiple sets have the same signature, keep the one with the lowest date
        if (!unique_date[signature] || unique_date[signature] > date) {
            unique_date[signature] = date;
        }
    }
    
    // collect "unique" sets
    let result = [];
    for (let signature in unique_date) result.push(sets[unique_date[signature]]);   
    return result;
}

const objArray = [
    {
      file: 'file_1',
      start_time: '2021-08-12 14:00:00',
      status: 'pending'
    },
    {
      file: 'file_2',
      start_time: '2021-08-12 14:00:00',
      status: 'completed'
    },
    {
      file: 'file_3',
      start_time: '2021-08-14 15:00:00',
      status: 'pending'
    },
    {
      file: 'file_1',
      start_time: '2021-08-14 03:00:00',
      status: 'pending'
    },
    {
      file: 'file_2',
      start_time: '2021-08-14 03:00:00',
      status: 'pending'
    },
    {
      file: 'file_2',
      start_time: '2021-11-11 11:11:00',
      status: 'pending'
    }
]

let result = do_some_filtering (objArray);
console.log(JSON.stringify(result, null, "    "));

代码在很大程度上依赖于对象作为关联数组的能力(现代Map 的祖先,有一些限制),随着不变性和函数式编程的出现,这一特性似乎已经被废弃,但是有时仍然被证明非常有用。

说到不变性,记录是不重复的,即在输入中改变一个将反映在输出中,反之亦然。由于记录本身保持不变,这仍然可以实现 JavaScript 在没有专用库的情况下可以提供的那种伪不变性。如果您绝对想要复制,请告诉我,我会更新代码。

我不确定您所说的“最快方式”是什么意思。如果要根据它们包含的文件比较集合,则必须付出比较两个列表的代价,据我所知,如果使用排序,则至少为 O(N log N),或者O(N²) 如果您进行成对比较。但是除非您打算在数百个文件上使用它,否则包含多个文件的组的数量应该相当少,您几乎不会感觉到差异。
剩下的就是 O(N),我非常怀疑你可以在不循环所有记录至少一次的情况下实现任何目标。

如果“快速”是指“快速编写”,我猜 15 行原生 JavaScript 应该符合要求?

【讨论】:

  • 对不起,如果问题不清楚。这些对象是用户请求的文件。相同的 start_time 代表用户提出的单个请求。所以它们应该被分组为子数组。如果有多个具有相同 start_name 的子数组,我只想渲染 1 个具有最旧 start_time 的子数组。但是可以有 2 个子数组,其中一个是文件名中另一个的子集。它们将被视为 2 个不同的请求。我希望你现在明白了
  • 如果您的解决方案需要根据我上面的评论进行更新,请告诉我。
  • 给我一个更新来批准这个答案。
【解决方案2】:

使用 Saxon-JS 2 (https://www.saxonica.com/saxon-js/index.xml) 提供的 XSLT 3,您可以对 JSON 数据进行分组:

const objArray = [
    {
      file: 'file_1',
      start_time: '2021-08-12 14:00:00',
      status: 'pending'
    },
    {
      file: 'file_2',
      start_time: '2021-08-12 14:00:00',
      status: 'completed'
    },
    {
      file: 'file_3',
      start_time: '2021-08-14 15:00:00',
      status: 'pending'
    },
    {
      file: 'file_1',
      start_time: '2021-08-14 03:00:00',
      status: 'pending'
    },
    {
      file: 'file_2',
      start_time: '2021-08-14 03:00:00',
      status: 'pending'
    },
    {
      file: 'file_2',
      start_time: '2021-11-11 11:11:00',
      status: 'pending'
    }
];

const xslt = `<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  expand-text="yes">

  <xsl:output method="json" indent="yes"/>

  <xsl:template match="." name="xsl:initial-template">
    <xsl:variable name="groups" as="array(*)*">
      <xsl:for-each-group select="?*" group-by="?start_time">
          <xsl:sequence select="array { current-group() }"/>
      </xsl:for-each-group>           
    </xsl:variable>
    <xsl:variable name="filtered-groups" as="array(*)*">
      <xsl:for-each-group select="$groups" composite="yes" group-by="sort(?*?file)">
        <xsl:sort select="?1?start_time"/>
        <xsl:sequence select="."/>
      </xsl:for-each-group>
    </xsl:variable>
    <xsl:sequence select="array { $filtered-groups }"/>
  </xsl:template>
  
</xsl:stylesheet>`;

const resultArray = SaxonJS.XPath.evaluate(`transform(
  map {
    'stylesheet-text' : $xslt,
    'initial-match-selection' : $json,
    'delivery-format' : 'raw'
  }
)?output`, [], { params : { xslt : xslt, json : [objArray] } });

console.log(resultArray);
&lt;script src="https://martin-honnen.github.io/xslt3fiddle/js/SaxonJS2.js"&gt;&lt;/script&gt;

【讨论】:

  • 这绝不是问题的答案。我在其中看不到“XSLT”标签。它与建议使用 PHP 让服务器完成这项工作一样重要。
  • @kuroineko,Saxon-JS 2 是一个同时运行客户端和服务器端的 JavaScript 库。不知道为什么将它与 PHP 进行比较。如果我在没有标签的情况下使用了 JQuery,您是否会认为使用该库的任何答案也是“否”答案?
  • 感谢您的回答马丁。有没有办法用 ES6 或者简单的 js 代码解决这个问题?
  • 我看不出如何用一种完全不同的语言转储一卡车代码来处理 XML,即使包装在 JavaScript 接口中,对于一个明显的初学者要求一个相当可以用大约 1995 年原生 JavaScript 的六行代码编写的简单算法。
  • 实际行数无关紧要。这只是一种说话方式。关键是,这在合理数量的 vanilla JavaScript 中是非常可行的,我几乎无法想象有人学习一门新语言并包括一个 500K 的库只是为了过滤几个对象。此外,这个问题的主要问题是算法本身,这对我来说似乎有点可疑。我花了一点时间来弄清楚它应该实现什么并且无法掌握其背后的任何逻辑,所以我最终决定向 OP 询问一些问题。
猜你喜欢
  • 2020-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-29
  • 1970-01-01
相关资源
最近更新 更多