Считать уникальные элементы массива без сортировки

В JavaScript следующие элементы найдут количество элементов в массиве. Предполагая, что в массиве

должно быть не менее одного элемента,
arr = ["jam", "beef", "cream", "jam"]
arr.sort();
var count = 1;
var results = "";
for (var i = 0; i < arr.length; i++)
{
    if (arr[i] == arr[i+1])
    {
      count +=1;
    }
    else
    {
        results += arr[i] + " --> " + count + " times\n" ;
        count=1;
    }
}

Можно ли это сделать без использования sort() или без мутирования массива? Я бы предположил, что массив нужно будет заново создать, а затем сортировка может быть выполнена на вновь созданном массиве, но я хочу знать, что лучший способ без сортировки. И да, я художник, а не программист, ваша честь.

Ответ 1

Быстрый способ сделать это - скопировать уникальные элементы в объект.

var counts = {};
for (var i = 0; i < arr.length; i++) {
    counts[arr[i]] = 1 + (counts[arr[i]] || 0);
}

Когда этот цикл будет завершен, объект counts будет иметь счет каждого отдельного элемента массива.

Ответ 2

Быстрый способ сделать это с помощью new Set() объекта.

Наборы являются удивительными, и мы должны использовать их чаще. Они быстрые и поддерживаются Chrome, Firefox, Microsoft Edge и node.js.
- Что быстрее Set или Object? от Andrei Kashcha

Элементы в Set всегда будут уникальными, поскольку он хранит только одну копию каждого введенного значения. Здесь используется функция, которая использует это свойство:

function countUnique(iterable) {
  return new Set(iterable).size;
}

console.log(countUnique('banana')); //=> 3
console.log(countUnique([5,6,5,6])); //=> 2
console.log(countUnique([window, document, window])); //=> 2

Ответ 3

Почему не что-то вроде:

var arr = ["jam", "beef", "cream", "jam"]
var uniqs = arr.reduce((acc, val) => {
  acc[val] = acc[val] === undefined ? 1 : acc[val] += 1;
  return acc;
}, {});
console.log(uniqs)

Ответ 4

Это выражение дает вам все уникальные элементы массива без его мутации:

arr.filter(function(v,i) { return i==arr.lastIndexOf(v); })

Вы можете связать это выражение с этим выражением, чтобы построить строку результатов без сортировки:

.forEach(function(v) {
     results+=v+" --> " + arr.filter(function(w){return w==v;}).length + " times\n";
});

В первом случае фильтр принимает только последний из каждого конкретного элемента; во втором случае фильтр включает в себя все элементы этого типа, а .length - счетчик.

Ответ 5

То же, что это решение, но меньше кода.

let counts = {};
arr.forEach(el => counts[el] = 1  + (counts[el] || 0))