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

时间:2019-02-01 16:20:06

标签: javascript arrays performance filtering lodash

How do I filter this array as described in the question

说明

请注意, entry1 entry4 对于property: 'subject'property: 'field'具有相同的

问题

我正在寻找一种高效且干净的方法来过滤此数组,并为这两个value实体获取共享两者 property的条目。

更新:

返回值

我不是要转换数据而是要分析数据。 因此分析返回的值应如下所示:

[['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" },
]

PS

  1. 我不是在寻找类似的解决方案

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

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

  1. 我的实际数据要大得多,并且每个条目包含更多的属性类型。而且它的排序不如我在这里显示的那么好。我确实做了简化,所以请注意性能。

代码段:

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

5 个答案:

答案 0 :(得分:1)

我必须说输入数据结构不是最佳的,并且将“主题”既用作真实对象属性又用作 property的值将使它变得更加出色令人困惑。我将第一个概念( real subject)称为“ entries”,因为样本值是“ 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);

答案 1 :(得分:0)

我将开始回答这个问题,但我们需要来回走动,这样我才能更好地了解您的需求。

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)

这是您要开始的地方。但是接下来的步骤取决于您要映射数据的内容。

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

答案 2 :(得分:0)

您可以将三元组数组简化为一个对象,其中result[propertyString][valueString]是一个三元组数组,其“属性”等于propertyString,“值”等于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 :(得分:0)

使用lodash可以GROUPBY的subject,转换为对象,GROUPBY对象被新的subject属性和field属性,并转换回项的数组:< / p>

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>

答案 4 :(得分:0)

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

然后我过滤所有property.field,并检查它们的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)
  }
}