В массиве объектов группировать по одному свойству и подсчитывать вхождения ключевых слов в другом?

#javascript #algorithm #optimization

#javascript #алгоритм #оптимизация

Вопрос:

У меня есть массив объектов

 [ 
    { hashtag: [ 'yo' ], type: [] },
    { hashtag: [ 'yo2' ], type: [] },
    { hashtag: [ 'yo3' ], type: [] },
    { hashtag: [ 'yo4' ], type: [] },
    { hashtag: [ 'yo4' ], type: [] },
    { hashtag: [ 'yo5' ], type: [ 'email' ] },
    { hashtag: [ 'yo5' ], type: [ 'link' ] },
    { hashtag: [ 'asdasdasd' ], type: [ 'email' ] },
    { hashtag: [ 'yo5' ], type: [ 'link' ] },
    { hashtag: [ 'yo6' ], type: [ 'link' ] },
    { hashtag: [ 'yo6' ], type: [ 'link' ] },
    { hashtag: [ 'yo7' ], type: [ 'link' ] },
    { hashtag: [ 'book', 'hello', 'yo5' ], type: [ 'link' ] } 
];
 

Каждое hashtag значение может содержать массив строк. Мне нужно получить весь массив уникальных hashtags объектов, которые имеют точное type значение, и подсчитать их. Ожидаемый результат:

 [ 
    { hashtag: 'yo5', sum: 3 },
    { hashtag: 'yo6', sum: 2 },
    { hashtag: 'yo7', sum: 1 },
    { hashtag: 'book', sum: 1 },
    { hashtag: 'hello', sum: 1 } 
]
 

Я написал код, но мне кажется, что он недостаточно оптимизирован. Есть ли способ сделать это лучше?

 const type = 'link';

const enter = [ 
    { hashtag: [ 'yo' ], type: [] },
       ...
    { hashtag: [ 'book', 'hello', 'yo5' ], type: [ 'link' ] } 
];

const filteredByType = enter.filter((el) => {
    return el.type.includes(type)
});

const newArr = [];

filteredByType.map((el) => {
  for (let i = 0; i < el.hashtag.length; i  ) {
    const z = newArr.filter((e) => {
        return e.hashtag === el.hashtag[i]
    });
    if (z.length) {
      const p = newArr.findIndex((obj) => {
        return obj.hashtag === el.hashtag[i];
      });
      newArr[p].sum  = 1;
    } else {
      newArr.push({
        hashtag: el.hashtag[i],
        sum: 1
      })
    }
  }
});

console.log('newArr', newArr);
 

Вот рабочий пример

Комментарии:

1. Если только уникальные, type значения должны учитываться, не hashtag: 'yo5' должны иметь соответствующих sum: 2 , поскольку 'email' и 'link' являются только 2 уникальными значениями?

2. @evgengorbunkov это должно быть подсчитано для точности type , я немного исправил свой вопрос. результат в моем случае правильный

3. Тогда что вы подразумеваете под «которые имеют точный тип» ?

4. @Evgengorbunkov в моем коде я передаю его как const type = 'link' в первой строке. Итак, мне нужно: из массива, тип которого — link получить все хэштеги и суммировать количество объектов, содержащих этот хэштег

Ответ №1:

Вы могли бы взять a Map , посчитать вхождения и создать из него новый массив.

 const
    getCount = (array, type) => Array.from(
        data.reduce(
            (map, o) => o.type.includes(type)
                ? o.hashtag.reduce((m, v) => m.set(v, (m.get(v) || 0)   1), map)
                : map,
            new Map
        ),
        ([hashtag, sum]) => ({ hashtag, sum })
    ),
    data = [{ hashtag: ['yo'], type: [] }, { hashtag: ['yo2'], type: [] }, { hashtag: ['yo3'], type: [] }, { hashtag: ['yo4'], type: [] }, { hashtag: ['yo4'], type: [] }, { hashtag: ['yo5'], type: ['email'] }, { hashtag: ['yo5'], type: ['link'] }, { hashtag: ['asdasdasd'], type: ['email'] }, { hashtag: ['yo5'], type: ['link'] }, { hashtag: ['yo6'], type: ['link'] }, { hashtag: ['yo6'], type: ['link'] }, { hashtag: ['yo7'], type: ['link'] }, { hashtag: ['book', 'hello', 'yo5'], type: ['link'] }];

console.log(getCount(data, 'link'));
console.log(getCount(data, 'email')); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

Комментарии:

1. Работает не так, как ожидалось. Он должен учитывать тип. Но решение очень полезно

2. @angelzzz, извини, я пропустил эту type часть. пожалуйста, смотрите Редактирование.

3. Спасибо! Отлично работает

Ответ №2:

Если под суммой вы на самом деле подразумеваете количество, вы можете использовать Map вместе с Array.prototype.reduce() для группировки вашего массива по hashtag значению.

Итак, предполагая, что ваш type файл не содержит повторяющихся элементов, вы можете получить что-то вроде этого:

 const src=[{hashtag:["yo"],type:[]},{hashtag:["yo2"],type:[]},{hashtag:["yo3"],type:[]},{hashtag:["yo4"],type:[]},{hashtag:["yo4"],type:[]},{hashtag:["yo5"],type:["email"]},{hashtag:["yo5"],type:["link"]},{hashtag:["asdasdasd"],type:["email"]},{hashtag:["yo5"],type:["link"]},{hashtag:["yo6"],type:["link"]},{hashtag:["yo6"],type:["link"]},{hashtag:["yo7"],type:["link"]},{hashtag:["book","hello","yo5"],type:["link"]}],

      groupSearch = _type => 
        [...src
          .reduce((acc, {hashtag, type}) => {
            const increment =  type.includes(_type)
            increment amp;amp;
            hashtag.forEach(tag => {
              const group = acc.get(tag)
              group
                ? group.sum  = increment
                : acc.set(tag, {hashtag:tag, sum: increment})
            })
            return acc
          }, new Map)
          .values()
        ]
        
      
      
console.log(groupSearch('link')) 
 .as-console-wrapper{min-height:100%;} 

Комментарии:

1. Я не вижу в вашем коде, где вы считаете, что он должен иметь точное type значение. Но интересно, как вы использовали Map и Set. Я постараюсь использовать это в своем коде

2. @angelzzz: Я понял вашу точку зрения и скорректировал свое решение, чтобы обеспечить именно ожидаемый результат.

3. @angelzzz : рассмотрим небольшой аварийный люк, который я добавил в последней правке моего ответа, что значительно повышает производительность

Ответ №3:

я сделал это, понятия не имею, как это сработало …?

 const data = 
  [ { hashtag: [ 'yo' ],        type: []       } 
  , { hashtag: [ 'yo2' ],       type: []        } 
  , { hashtag: [ 'yo3' ],       type: []         } 
  , { hashtag: [ 'yo4' ],       type: []          } 
  , { hashtag: [ 'yo4' ],       type: []           } 
  , { hashtag: [ 'yo5' ],       type: [ 'email' ]   } 
  , { hashtag: [ 'yo5' ],       type: [ 'link' ]     } 
  , { hashtag: [ 'asdasdasd' ], type: [ 'email' ]     } 
  , { hashtag: [ 'yo5' ],       type: [ 'link' ]       } 
  , { hashtag: [ 'yo6' ],       type: [ 'link' ]        } 
  , { hashtag: [ 'yo6' ],       type: [ 'link' ]         } 
  , { hashtag: [ 'yo7' ],       type: [ 'link' ]          } 
  , { hashtag: [ 'book', 'hello', 'yo5' ], type: [ 'link'] } 
  ]

const getCount = ( arr, typeK ) =>  
    arr.reduce((res, {hashtag,type }) => [...res,...(type.includes(typeK)?hashtag:[])],[])
      .sort()
      .reduce((res,val,i,{ [i -1]: last }) =>
          {
          if (last === val ) res[res.length -1].sum   
          else res.push({hastag:val,sum:1})
          return res
          },[]) 

console.log('link =', getCount(data, 'link'))
console.log('email =', getCount(data, 'email')) 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

Комментарии:

1. Что касается производительности, я бы ожидал, что она может быть относительно медленной из-за .sort() (что само по себе довольно дорого). Однако я могу ошибаться, не могу сравнить это с другими, потому что это не дает ожидаемого результата.

2. @Evgengorbunkov действительно, я полностью упустил этот момент. Я поправил 😉

3. @Evgengorbunkov ожидаемый результат представлен в исходном вопросе и в jsfiddle

4. @angelzzz: Я имел в виду, что этот конкретный ответ не предоставил результат, который вы указали, как ожидалось, поэтому его нельзя сравнивать Apple с Apple с другими решениями, поскольку они выполняют желаемый результат.

5. Однако одно предположение, которое я сделал правильно : приведенное выше решение является одним из самых медленных, в то время как самыми быстрыми являются те, которые опубликовала Нина, и те, которые опубликовал я — в разных тестовых тестах ее или мои тесты показывают самую высокую производительность.

Ответ №4:

Другой способ…

 const data = 
  [ { hashtag: [ 'yo' ],        type: []       } 
  , { hashtag: [ 'yo2' ],       type: []        } 
  , { hashtag: [ 'yo3' ],       type: []         } 
  , { hashtag: [ 'yo4' ],       type: []          } 
  , { hashtag: [ 'yo4' ],       type: []           } 
  , { hashtag: [ 'yo5' ],       type: [ 'email' ]   } 
  , { hashtag: [ 'yo5' ],       type: [ 'link' ]     } 
  , { hashtag: [ 'asdasdasd' ], type: [ 'email' ]     } 
  , { hashtag: [ 'yo5' ],       type: [ 'link' ]       } 
  , { hashtag: [ 'yo6' ],       type: [ 'link' ]        } 
  , { hashtag: [ 'yo6' ],       type: [ 'link' ]         } 
  , { hashtag: [ 'yo7' ],       type: [ 'link' ]          } 
  , { hashtag: [ 'book', 'hello', 'yo5' ], type: [ 'link'] } 
  ]

const getCount = ( arr, typeK ) =>  
  Object.entries(arr.reduce((r,{hashtag,type})=>
    {
    if (type.includes(typeK))
      hashtag.forEach(h=>{ r[h]=(r[h] ?? 0); r[h]   })
    return r
    },{})).map(([k,v])=> ({hashtag:k,sum:v}))

console.log('link =', getCount(data, 'link'))
console.log('email =', getCount(data, 'email')) 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

Комментарии:

1. И это тоже.

2. @Evgengorbunkov действительно, я полностью пропустил этот момент и здесь. Я исправил 😉

Ответ №5:

Другое решение с использованием двойного reduce . Мы отфильтровываем те значения, которые не включают наш целевой тип, складываем результат в объект like {yo5: 3, yo6: 2, yo7: 1, book: 1, hello: 1} , берем записи этого и сопоставляем их с вашим {hashtag, sum} форматом:

 const tagCountByType = (targetType) => (xs) => 
  Object .entries (xs .filter (({type}) => type .includes (targetType))
     .reduce (
       (a, {hashtag}) => hashtag .reduce ((a, tag) => ((a [tag] = (a[tag] || 0)   1), a), a), 
       {}
     )
  ) .map (([hashtag, sum]) => ({hashtag, sum}))

const input = [{hashtag: ['yo'], type: []}, {hashtag: ['yo2'], type: []}, {hashtag: ['yo3'], type: []}, {hashtag: ['yo4'], type: []}, {hashtag: ['yo4'], type: []}, {hashtag: ['yo5'], type: ['email']}, {hashtag: ['yo5'], type: ['link']}, {hashtag: ['asdasdasd'], type: ['email']}, {hashtag: ['yo5'], type: ['link']}, {hashtag: ['yo6'], type: ['link']}, {hashtag: ['yo6'], type: ['link']}, {hashtag: ['yo7'], type: ['link']}, {hashtag: ['book', 'hello', 'yo5'], type: ['link']}]

console .log ('link:', tagCountByType ('link') (input))
console .log ('email:', tagCountByType ('email') (input)) 
 .as-console-wrapper {max-height: 100% !important; top: 0} 

Как указывали другие, sum здесь странный выбор слов. count имело бы больше смысла.