【问题标题】:How to filter an array of objects by multiple identical properties如何通过多个相同的属性过滤对象数组
【发布时间】:2019-02-01 16:20:06
【问题描述】:

说明

注意 entry1entry4 对于 property: 'subject'property: 'field' 共享相同的

问题

我正在寻找一种高效且简洁的方法来过滤此数组并获取与propertyies 共享both values 的条目。

更新:

返回值

我不是要转换数据而是分析它。 所以分析的返回值应该是这样的:

[['entry1', 'entry4'],...]

通过这个分析 list 我可以轻松地将我的 triples = [...] 转换为 triples 列表,在其中删除一个条目(不管哪个,可能是 'entry1 ' 或 'entry4'),并更新另一个

[
  { subject: "entry1", property: "subject", value: "sport" },
  { subject: "entry1", property: "field", value: "category" },
  { subject: "entry1", property: "content", value: "football" },
  { subject: "entry1", property: "content", value: "basketball" },
]

附言

  1. 我不是在寻找像这样的解决方案:

    array.filter(({property, value})=> property === 'sport' && value === 'category')

我不知道“运动”或“类别”。 这些是动态值。

  1. 我的实际数据要大得多,每个条目包含更多的属性类型。它也没有我在这里展示的那么好。我确实简化了它,所以请注意性能。

代码sn-p:

const triples = [
  { subject: "entry1", property: "subject", value: "sport" },
  { subject: "entry1", property: "field", value: "category" },
  { subject: "entry1", property: "content", value: "football" },
  
  { subject: "entry4", property: "subject", value: "sport" },
  { subject: "entry4", property: "field", value: "category" },
  { subject: "entry4", property: "content", value: "basketball" },
  
  { subject: "entry2", property: "subject", value: "music" },
  { subject: "entry2", property: "field", value: "category" },
  { subject: "entry2", property: "content", value: "notes" },
  
  { subject: "entry3", property: "subject", value: "painting" },
  { subject: "entry3", property: "field", value: "category" },
  { subject: "entry3", property: "content", value: "drawings" }
];

【问题讨论】:

  • 到目前为止您尝试过什么?添加当前方法的 sn-p。
  • 我也不确定您希望生成的数据结构是什么样的。
  • 我仍然需要看看你期望的结果是什么。另外......问开放式......真的很不​​受欢迎......“你将如何编码这个”。
  • @ueeeieu - 请您运行我提供的 sn-p 并就您正在寻找的内容提供反馈吗?我很确定我已经提供了有关如何处理此问题的足够信息,您应该能够接受我所做的并完成它。我也很乐意继续提供帮助。
  • @ueeeieu,你能给点意见吗?

标签: javascript arrays performance filtering lodash


【解决方案1】:

我必须说输入数据结构不是最优的,使用“主题”作为一个真实的对象属性和一个值forproperty会让它更加混乱。我将第一个概念(real subject)称为“条目”,因为样本值为“entry1”、“entry2”、...。

这是一种为您的示例数据提取["entry1", "entry4"] 的方法:

  1. 通过将数据输入对象进行分组,其中“属性”和“值”被转换为键/值对,因此您会得到如下内容:

    {
        entry1: { subject: "sport", field: "category", content: "football" },
        entry4: { subject: "sport", field: "category", content: "basketball" },
        entry2: { subject: "music", field: "category", content: "notes" },
        entry3: { subject: "painting", field: "category", content: "drawings" }
    }
    

    这将更容易使用。下面的代码实际上会创建一个Map,而不是一个普通的对象,但原理是一样的。

  2. 为这些对象定义一个新的group 属性,其中值由主题和字段组成,字符串化为 JSON。例如,上述结果的第一个对象将扩展为:

    group: '["sport","category"]'
    
  3. 创建一个条目映射,以它们的组值作为键。所以这将给出这个结果:

    {
        '["sport","category"]': ["entry1","entry4"],
        '["music","category"]': ["entry2"],
        '["painting","category"]': ["entry3"]
    }
    
  4. 现在只列出值(子数组)和仅列出具有多个条目值的值是一个简单的步骤。

这里是实现:

const triples = [{subject: "entry1", property: "subject", value: "sport"},{subject: "entry1", property: "field", value: "category"},{subject: "entry1", property: "content", value: "football"},{subject: "entry4", property: "subject", value: "sport"},{subject: "entry4", property: "field", value: "category"},{subject: "entry4", property: "content", value: "basketball"},{subject: "entry2", property: "subject", value: "music"},{subject: "entry2", property: "field", value: "category"},{subject: "entry2", property: "content", value: "notes"},{subject: "entry3", property: "subject", value: "painting"},{subject: "entry3", property: "field", value: "category"},{subject: "entry3", property: "content", value: "drawings"},];

// 1. Group the data by subject into objects where "property" and "value" are translated into key/value pairs:
const entries = new Map(triples.map(o => [o.subject, { entry: o.subject }]));
triples.forEach(o => entries.get(o.subject)[o.property] = o.value);
// 2. Define a group value for these objects (composed of subject and field)
entries.forEach(o => o.group = JSON.stringify([o.subject, o.field]));
// 3. Create Map of entries, keyed by their group value
const groups = new Map(Array.from(entries.values(), o => [o.group, []]));
entries.forEach(o => groups.get(o.group).push(o.entry));
// 4. Keep only the subarrays that have more than one value
const result = [...groups.values()].filter(group => group.length > 1);
console.log(result);

注意输出是一个嵌套数组,因为理论上可以有更多的组合条目,比如[ ["entry1", "entry4"], ["entry123", "entry521", "entry951"] ]

上面可以修改/扩展以获得最终的过滤结果。在第三步中,您仍然会收集对象(不仅仅是条目值),然后将过滤后的结果映射回原始格式:

const triples = [{subject: "entry1", property: "subject", value: "sport"},{subject: "entry1", property: "field", value: "category"},{subject: "entry1", property: "content", value: "football"},{subject: "entry4", property: "subject", value: "sport"},{subject: "entry4", property: "field", value: "category"},{subject: "entry4", property: "content", value: "basketball"},{subject: "entry2", property: "subject", value: "music"},{subject: "entry2", property: "field", value: "category"},{subject: "entry2", property: "content", value: "notes"},{subject: "entry3", property: "subject", value: "painting"},{subject: "entry3", property: "field", value: "category"},{subject: "entry3", property: "content", value: "drawings"},];

// 1. Group the data by subject into objects where "property" and "value" are translated into key/value pairs:
const entries = new Map(triples.map(o => [o.subject, { entry: o.subject }]));
triples.forEach(o => entries.get(o.subject)[o.property] = o.value);
// 2. Define a group value for these objects (composed of subject and field)
entries.forEach(o => o.group = JSON.stringify([o.subject, o.field]));
// 3. Create Map of objects(*), keyed by their group value
const groups = new Map(Array.from(entries.values(), o => [o.group, []]));
entries.forEach(o => groups.get(o.group).push(o));
// 4. Keep only the subarrays that have more than one value
const result = [...groups.values()].filter(group => group.length > 1)
// 5. ...and convert it back to the original format:
    .flatMap(group => [
        { subject: group[0].entry, property: "subject", value: group[0].subject },
        { subject: group[0].entry, property: "field", value: group[0].field },
        ...group.map(o => ({ subject: group[0].entry, property: "content", value: o.content }))
    ]);

console.log(result);

【讨论】:

    【解决方案2】:

    您可以将三元组数组简化为一个对象,其中result[propertyString][valueString] 是一个三元组数组,其中“property”等于 propertyString,“value”等于 valueString:

    triples.reduce((acc, triple) => {
        acc[triple.property] = acc[triple.property] || {};
        acc[triple.property][triple.value] = acc[triple.property][triple.value] || [];
        acc[triple.property][triple.value].push(triple);
        return acc;
    }, {})
    

    然后,您可以在该对象中搜索所需的属性和值,并检查是否有超过一个三元组。

    【讨论】:

      【解决方案3】:

      我将开始回答这个问题,但我们需要反复讨论,这样我才能更好地了解您在寻找什么。

      let data = [
        {subject: 'entry1', property: 'subject', value: 'sport'},
      	{subject: 'entry1', property: 'field', value: 'category'},
      	{subject: 'entry1', property: 'content', value: 'football'},
      
      	{ subject: 'entry4', property: 'subject', value: 'sport' },
        { subject: 'entry4', property: 'field', value: 'category' },
        { subject: 'entry4', property: 'content', value: 'basketball' },
      
      	{subject: 'entry2', property: 'subject', value: 'music'},
      	{subject: 'entry2', property: 'field', value: 'category'},
      	{subject: 'entry2', property: 'content', value: 'notes'},
      
      	{subject: 'entry3', property: 'subject', value: 'painting'},
      	{subject: 'entry3', property: 'field', value: 'category'},
      	{subject: 'entry3', property: 'content', value: 'drawing'}
      ]
      
      let keys = data.map((item, inex) => { return item.subject })
      
      let uniqueKeys = keys.filter((item, index) => { return keys.indexOf(item) >= index })
      
      let propertiesWeCareAbout = ['subject', 'field']
      
      let mappedValues = data.reduce((acc, item, index) => {
          acc[item.subject] = {}
          acc[item.subject].values = data.map((subItm, subIndx) => { if (item.subject === subItm.subject) { if (propertiesWeCareAbout.indexOf(subItm.property) > -1) {return subItm.value} }}).filter(Boolean)
          return acc;
      }, {})
      
      // this is where I leave you... because I think you need to finish this up yourself. 
      // You have all the mapped data you need to solve your question. 
      // You now just need to map over the unique keys checking the `mappedValues` data structure for entries that have the same values in the values array. 
      // You can rename things if you want. But these are all the parts of the solution laid out.
      // p.s. You can remove the 'category' string from the propertiesWeCareAbout array based on the example you provided... and you can simplify what I've provided in a number of ways.
      
      // this is where you map to get just the strings of "entry1" and "entry4" based on the other mapped data provided. Then you can combine data as you said you need to.
      let finalListOfEntriesThatNeedToBeMerged = uniqueKeys.map((item, index) => {return item})
      
      console.log(mappedValues)
      console.log(finalListOfEntriesThatNeedToBeMerged)

      这是您要开始的地方。但接下来的步骤取决于您要将数据映射到什么。

      接下来我将重点关注这条评论:“共享这些属性的两个值的条目。”

      【讨论】:

      • 我知道混乱在哪里。我的错。我已经更新了我的问题。查看返回值
      • 但是您确实在尝试转换数据....因为您在询问如何将条目 4 和条目 1 “合并”为一个条目。
      • 最后一步是转换数据,但首先我需要一个中间步骤,告诉转换需要转换什么,这就是为什么我将分析步骤添加到流程中
      • 我已经给了你所有的步骤,除了最后一个;我想看看你试一试。请花点时间提出问题并接受我提供的内容,看看你是否能解决它。
      • 我还想指出:您不必采取这么多步骤来解决这个问题。我已经一步一步地列出了它,以便您更好地理解该方法。但这可以归结为更少的行和操作。(别忘了“接受”我的回答,如果它对你有帮助,请点赞!)
      【解决方案4】:

      使用 lodash,您可以通过 subject 分组,转换为对象,通过新的 subject 属性和 field 属性分组对象,然后转换回项目数组:

      const { flow, partialRight: pr, groupBy, map, set, head, flatMap, toPairs, isArray } = _;
      
      const dontCollect = key => ['entry', 'subject', 'field'].includes(key);
      const createPropery = (subject, property, value) => ({ subject, property, value });
      
      const fn = flow(
        pr(groupBy, 'subject'),
        pr(map, (g, entry) => ({ // convert to object with the subject as entry
          entry,
          ...g.reduce((r, o) => set(r, o.property, o.value), {}),
        })),
        pr(groupBy, o => `${o.subject}-${o.field}`),
        pr(map, g => g.length > 1 ? _.mergeWith(...g, (a, b, k) => { // merge everything to an object
          if(dontCollect(k)) return a;
          return [].concat(a, b); // convert non entry, subject, or field properties to array if repeated
        }) : head(g)),
        pr(flatMap, ({ entry: subject, ...o }) => // convert back a series of rows
          flow(
            toPairs,
            pr(flatMap, ([property, value]) => isArray(value) ?
              map(value, v => createPropery(subject, property, v))
              :
              createPropery(subject, property, value)
            )
          )(o)
        )
      );
      
      const triples = [{"subject":"entry1","property":"subject","value":"sport"},{"subject":"entry1","property":"field","value":"category"},{"subject":"entry1","property":"content","value":"football"},{"subject":"entry4","property":"subject","value":"sport"},{"subject":"entry4","property":"field","value":"category"},{"subject":"entry4","property":"content","value":"basketball"},{"subject":"entry2","property":"subject","value":"music"},{"subject":"entry2","property":"field","value":"category"},{"subject":"entry2","property":"content","value":"notes"},{"subject":"entry3","property":"subject","value":"painting"},{"subject":"entry3","property":"field","value":"category"},{"subject":"entry3","property":"content","value":"drawings"}];
      
      const result = fn(triples);
      
      console.log(result);
      <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

      【讨论】:

        【解决方案5】:

        我首先过滤了所有property.subjects 并将它们缩减为一个多维数组,其中每个数组包含多次出现的主题值。

        然后我过滤所有property.fields 并检查它们的property.subject是否也相等。

        然后我创建一个映射对象 (mergeEntriesBysubjectIndex),在其中我得到 {0: true, 1: false, 2: true},其中每个键引用 subjects 索引值。

        最后,我在mergeEntriesBysubjectIndex 上运行,每个真正的索引都会触发一个基于索引subjects 的新合并条目,以及所有三元组的新更新数组。

        我的实现:

        /* 
        * @description 
        * Get an mulitdimensional array, where each inner array represent a list
        * of entries with similar value
        * 
        * @ return [[], [], []]
        */
        const subjects = Object.values(
          triples
            .filter(triple => triple.property === "subject")
            .reduce((subjects, entry) => {
              if (subjects[entry.value]) {
                subjects[entry.value].push(entry.subject);
              } else {
                subjects[entry.value] = [];
                subjects[entry.value].push(entry.subject);
              }
              return subjects;
            }, {})
        ).filter(arr => arr.length > 1);
        
        const fields = triples.filter(triple => triple.property === "field");
        
        /*
        * @description
        * Create an object based on the "subjects" mulit-dimensional array from before
        * Each key represent the index of "subjects", where the value is a boolean * 
        * representing a similar "property:field" value 
        */
        const mergeEntriesBysubjectIndex = subjects.reduce((filtered, chunk, index) => {
          let values = [];
          chunk.forEach(subject => {
            const obj = fields.find(field => field.subject === subject).value;
            values.push(obj);
          });
          filtered[index] = values.every((val, i, arr) => val === arr[0]);
          return filtered;
        }, {});
        
        /*
        * @description
        * Get an array of subjects value (e.g. "entry1", "entry2")
        * and return a new "merged" collection with uniqe objects
        * and with the same name for a subject
        */
        const mergeEntries = entries => {
          const ent = triples.filter(triple => triple.subject === entries[0]);
          const newContent = triples
            .filter(
              triple => triple.subject === entries[1] && triple.property === "content"
            )
            .map(triple => ({ ...triple, subject: entries[0] }));
          return [...ent, ...newContent];
        };
        
        /*
        * @description
        * return a new updated list of triples without the specified entries
        */
        const removeEntriesFromCurrentTriples = entries =>
          triples.filter(triple => !entries.includes(triple.subject));
        
        for (let index in mergeEntriesBysubjectIndex) {
          if (mergeEntriesBysubjectIndex[index]) {
            const mergeEntry = mergeEntries(subjects[index]);
            const updateEntries = [
              ...removeEntriesFromCurrentTriples(subjects[index]),
              ...mergeEntry
            ];
            // The new trasformed triples collection
            console.log('transformed triples:', updateEntries)
          }
        }
        

        【讨论】:

          猜你喜欢
          • 2020-04-21
          • 1970-01-01
          • 2019-08-22
          • 1970-01-01
          • 2019-05-29
          • 2016-07-23
          • 1970-01-01
          • 2019-01-17
          • 1970-01-01
          相关资源
          最近更新 更多