Как отфильтровать массив объектов по нескольким идентичным свойствам - PullRequest
0 голосов
/ 01 февраля 2019

How do I filter this array as described in the question

Описание

Обратите внимание, что entry1 и entry4 имеют одинаковое значение для property: 'subject' и property: 'field'.

Вопрос

Я ищу эффективный и простой способ фильтрации этого массива и получения записей, которые разделяют оба value s для этих property i.

ОБНОВЛЕНИЕ:

Возвращаемое значение

Я не пытаюсь преобразовать данные, но проанализирую их.поэтому возвращаемое значение из анализа должно выглядеть следующим образом:

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

и с помощью этого анализа list я мог бы легко преобразовать свой 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')

Я не знаю "спорт" или«категория». Это динамические значения.

Мои фактические данные намного больше и содержат гораздо больше свойств типов для каждой записи .Кроме того, это не заказано так хорошо, как я показываю здесь.Я упростил его, поэтому, пожалуйста, имейте в виду производительность.

фрагмент кода:

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 голосов
/ 03 февраля 2019

Сначала я отфильтровал все 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)
  }
}
0 голосов
/ 02 февраля 2019

Используя 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>
0 голосов
/ 01 февраля 2019

Вы можете уменьшить массив троек до объекта, где 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;
}, {})

. Затем можно выполнить поиск.этот объект для свойств и значений, которые вы хотите, и проверьте, если существует более одной тройки.

0 голосов
/ 01 февраля 2019

Я должен сказать, что структура входных данных не оптимальна, и использование «субъекта» как свойства реального объекта и как значения для property сделает его еще более запутанным.Я назову первое понятие ( real subject) «entry», поскольку значениями примеров являются «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);
0 голосов
/ 01 февраля 2019

Я начну отвечать на вопрос, но нам нужно будет возвращаться туда и обратно, чтобы я мог лучше понять, что вы ищете.

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)

Здесь вы хотите начать.Но следующие шаги зависят от того, с чем вы хотите связать данные.

Я собираюсь сосредоточиться на следующем комментарии: «записи, которые имеют оба значения для этих свойств».

...