简体   繁体   English

如何通过多个相同的属性过滤对象数组

[英]How to filter an array of objects by multiple identical properties

如何按照问题中的描述过滤此数组

Description描述

Notice that entry1 and entry4 share the same value for property: 'subject' and property: 'field' .请注意,取值范entry4份额相同的property: 'subject'property: 'field'

Question

Im looking for a performative and clean way to filter this array and get the entries that share both value s for those property ies.我正在寻找一种高效且干净的方法来过滤此数组并获取共享这些property ie 的两个value的条目。

UPDATE:更新:

Returned value返回值

I'm not trying to transform the data but analyze it.我不是要转换数据,而是要对其进行分析。 so the returned value from the analysis should look like this:所以分析的返回值应该是这样的:

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

and with this analysis list I could easily transform my triples = [...] into a list of triples where I remove one of entries(doesnt matter which, could be 'entry1' or 'entry4'), and update the other one使用这个分析列表,我可以轻松地将我的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" },
]

PS聚苯乙烯

  1. I'm not looking for a solution like :我不是在寻找像这样的解决方案

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

I dont know 'sport' or 'category'.我不知道“运动”或“类别”。 Those are dynamic values.这些是动态值。

  1. My actual data is much bigger, and contains much more property types for each entry .我的实际数据要大得多,并且每个条目包含更多的属性类型。 Also its not ordered as nicely as I show here.它的订购也没有我在这里展示的那么好。 I did simplify it, so please have in mind performance.我确实简化了它,所以请记住性能。

code snippet:代码片段:

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

I must say the input data structure is not optimal, and the use of "subject" as both a real object property and as a value for property will make it all the more confusing.我必须说,输入数据结构是不是最佳的,并且使用“主体”既是一个真正的对象属性和作为值的property将使其更加扑朔迷离。 I will call the first notion (the real subject ) "entries", since the sample values are "entry1", "entry2", ....我将第一个概念(真正的subject )称为“条目”,因为样本值为“entry1”、“entry2”……

Here is a way to extract ["entry1", "entry4"] for your sample data:这是一种为您的示例数据提取["entry1", "entry4"]的方法:

  1. Group the data by their entry into objects where "property" and "value" are translated into key/value pairs, so you would get something like this:通过将数据输入到“属性”和“值”被转换为键/值对的对象中,将数据分组,因此您将得到如下内容:

     { 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" } }

    This will be easier to work with.这将更容易使用。 The below code will in fact create a Map instead of a plain object, but it is the same principle.下面的代码实际上将创建一个Map而不是一个普通对象,但它的原理是相同的。

  2. Define a new group property for these objects, where the value is composed of subject and field, stringified as JSON.为这些对象定义一个新的group属性,其中值由主题和字段组成,字符串化为 JSON。 For example, the first object of the above result would be extended with:例如,上述结果的第一个对象将扩展为:

     group: '["sport","category"]'
  3. Create a Map of entries, keyed by their group value.创建一个条目映射,以它们的组值为键。 So that would give this result:所以这将给出这个结果:

     { '["sport","category"]': ["entry1","entry4"], '["music","category"]': ["entry2"], '["painting","category"]': ["entry3"] }
  4. Now it is a simple step to only list the values (the subarrays) and only those that have more than one entry value.现在只需列出值(子数组)和具有多个条目值的值,这是一个简单的步骤。

Here is the implementation:这是实现:

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

Be aware that the output is a nested array, because in theory there could be more combined entries, like [ ["entry1", "entry4"], ["entry123", "entry521", "entry951"] ]请注意,输出是一个嵌套数组,因为理论上可以有更多组合条目,例如[ ["entry1", "entry4"], ["entry123", "entry521", "entry951"] ]

The above can be modified/extended to get the final filtered result.以上可以修改/扩展以获得最终的过滤结果。 In the third step you would still collect the objects (not just the entry value), and the filtered result is then mapped back to the original format:在第三步中,您仍将收集对象(不仅仅是条目值),然后将过滤后的结果映射回原始格式:

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

I'll start answering the question but we will need to go back and forth so I can better understand what you are looking for.我将开始回答这个问题,但我们需要来回切换,以便我更好地了解您在寻找什么。

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

This is where you want to start.这是您想要开始的地方。 But the next steps depend on what you are looking to map the data to.但是接下来的步骤取决于您希望将数据映射到什么。

I'm going to focus on this comment next: "entries that share both values for those properties."接下来我将重点关注这条评论:“这些属性共享两个值的条目。”

You can reduce the array of triples to an object where result[propertyString][valueString] is an array of triples with "property" equal to propertyString and "value" equal to valueString:您可以将三元组数组缩减为一个对象,其中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;
}, {})

You can then search that object for the properties and values you want, and check if there is more than one triple.然后,您可以在该对象中搜索您想要的属性和值,并检查是否有多个三元组。

Using lodash you can groupBy the subject , convert to an object, groupBy objects by the new subject property and the field property, and convert back to an array of items:使用 lodash 你可以 groupBy the subject ,通过新的subject属性和field属性转换为一个对象, groupBy 对象,然后转换回一个项目数组:

 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>

I first filtered all property.subject s and reduced them into a multidimensional array, where each array contains subject values that appeared more then once.我首先过滤了所有property.subject并将它们缩减为一个多维数组,其中每个数组包含出现多次的主题值。

Then I filter all property.field s and check if their property.subject are equal as well.然后我过滤所有property.field并检查它们的property.subject是否也相等。

Then I create a mapped object ( mergeEntriesBysubjectIndex ) where I get {0: true, 1: false, 2: true} where each key refer to subjects indexed values.然后我创建一个映射对象( mergeEntriesBysubjectIndex ),我得到{0: true, 1: false, 2: true}其中每个键都引用subjects索引值。

In the end, I run on mergeEntriesBysubjectIndex and each true index will trigger a new merged entry based on the indexed subjects , and new updated array of all triples.最后,我在mergeEntriesBysubjectIndex运行,每个真正的索引将根据索引的subjects触发一个新的合并条目,以及所有三元组的新更新数组。

My implementation:我的实现:

/* 
* @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)
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM