Получить элемент с наибольшим вхождением в массиве - PullRequest
66 голосов
/ 28 июня 2009

Я ищу элегантный способ определить, какой элемент имеет наибольшее вхождение ( mode ) в массиве JavaScript.

Например, в

['pear', 'apple', 'orange', 'apple']

элемент 'apple' является наиболее частым.

Ответы [ 27 ]

71 голосов
/ 28 июня 2009

Это просто режим. Вот быстрое неоптимизированное решение . Это должно быть O (n).

function mode(array)
{
    if(array.length == 0)
        return null;
    var modeMap = {};
    var maxEl = array[0], maxCount = 1;
    for(var i = 0; i < array.length; i++)
    {
        var el = array[i];
        if(modeMap[el] == null)
            modeMap[el] = 1;
        else
            modeMap[el]++;  
        if(modeMap[el] > maxCount)
        {
            maxEl = el;
            maxCount = modeMap[el];
        }
    }
    return maxEl;
}
42 голосов
/ 24 декабря 2013

С 2009 года в javascript произошли некоторые события - я подумал, что добавлю еще один вариант. Меня меньше беспокоит эффективность, пока это не станет проблемой, поэтому мое определение «элегантного» кода (как предусмотрено в OP) способствует удобочитаемости - что, конечно, субъективно ...

function mode(arr){
    return arr.sort((a,b) =>
          arr.filter(v => v===a).length
        - arr.filter(v => v===b).length
    ).pop();
}

mode(['pear', 'apple', 'orange', 'apple']); // apple

В этом конкретном примере, если два или более элемента набора имеют одинаковые вхождения, будет возвращен тот, который появляется последним в массиве. Стоит также отметить, что он изменит ваш исходный массив - этого можно избежать, если вы заранее захотите вызвать Array.slice.


Редактировать: обновил пример с некоторыми ES6 жирными стрелами , потому что 2015 произошло, и я думаю, что они выглядят довольно ... Если вы заинтересованы в обратной совместимости, вы можете найти это в истории изменений .

31 голосов
/ 10 августа 2010

В соответствии с запросом George Jempty's на учет алгоритма для связей, я предлагаю модифицированную версию алгоритма Matthew Flaschen's.

function modeString(array)
{
    if (array.length == 0)
        return null;

    var modeMap = {},
        maxEl = array[0],
        maxCount = 1;

    for(var i = 0; i < array.length; i++)
    {
        var el = array[i];

        if (modeMap[el] == null)
            modeMap[el] = 1;
        else
            modeMap[el]++;

        if (modeMap[el] > maxCount)
        {
            maxEl = el;
            maxCount = modeMap[el];
        }
        else if (modeMap[el] == maxCount)
        {
            maxEl += '&' + el;
            maxCount = modeMap[el];
        }
    }
    return maxEl;
}

Теперь будет возвращена строка с элементами режима, разделенными символом '&'. Когда результат получен, его можно разделить на этот элемент '&', и у вас есть режим (ы).

Другой вариант - вернуть массив элементов режима следующим образом:

function modeArray(array)
{
    if (array.length == 0)
        return null;
    var modeMap = {},
        maxCount = 1, 
        modes = [];

    for(var i = 0; i < array.length; i++)
    {
        var el = array[i];

        if (modeMap[el] == null)
            modeMap[el] = 1;
        else
            modeMap[el]++;

        if (modeMap[el] > maxCount)
        {
            modes = [el];
            maxCount = modeMap[el];
        }
        else if (modeMap[el] == maxCount)
        {
            modes.push(el);
            maxCount = modeMap[el];
        }
    }
    return modes;
}

В приведенном выше примере вы сможете обработать результат функции как массив режимов.

11 голосов
/ 28 июня 2009
a=['pear', 'apple', 'orange', 'apple'];
b={};
max='', maxi=0;
for(let k of a) {
  if(b[k]) b[k]++; else b[k]=1;
  if(maxi < b[k]) { max=k; maxi=b[k] }
}
10 голосов
/ 08 февраля 2017

Исходя из ответа Эмиссара ES6 +, вы можете использовать Array.prototype.reduce для сравнения (в отличие от сортировки, выталкивания и, возможно, изменения вашего массива), что, на мой взгляд, выглядит довольно гладко.

const mode = (myArray) =>
  myArray.reduce(
    (a,b,i,arr)=>
     (arr.filter(v=>v===a).length>=arr.filter(v=>v===b).length?a:b),
    null)

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

Недостатком, как и в случае с другими решениями, является то, что он не обрабатывает «состояния рисования», но это все же может быть достигнуто с несколько более сложной функцией уменьшения.

3 голосов
/ 14 августа 2016

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

const arr = ['hello', 'world', 'hello', 'again'];

const tally = (acc, x) => { 

  if (! acc[x]) { 
    acc[x] = 1;
    return acc;
  } 

  acc[x] += 1;
  return acc;
};

const totals = arr.reduce(tally, {});

const keys = Object.keys(totals);

const values = keys.map(x => totals[x]);

const results = keys.filter(x => totals[x] === Math.max(...values));
3 голосов
/ 21 ноября 2017

Поскольку я использую эту функцию в качестве опроса для интервьюеров, я публикую свое решение:

const highest = arr => (arr || []).reduce( ( acc, el ) => {
  acc.k[el] = acc.k[el] ? acc.k[el] + 1 : 1
  acc.max = acc.max ? acc.max < acc.k[el] ? el : acc.max : el
  return acc  
}, { k:{} }).max

const test = [0,1,2,3,4,2,3,1,0,3,2,2,2,3,3,2]
console.log(highest(test))
2 голосов
/ 03 октября 2016

Вот мое решение этой проблемы, но с числами и с использованием новой функции «Установить». Это не очень эффективно, но мне определенно было весело писать это, и он поддерживает несколько максимальных значений.

const mode = (arr) => [...new Set(arr)]
  .map((value) => [value, arr.filter((v) => v === value).length])
  .sort((a,b) => a[1]-b[1])
  .reverse()
  .filter((value, i, a) => a.indexOf(value) === i)
  .filter((v, i, a) => v[1] === a[0][1])
  .map((v) => v[0])

mode([1,2,3,3]) // [3]
mode([1,1,1,1,2,2,2,2,3,3,3]) // [1,2]

Кстати, не используйте это для производства, это всего лишь иллюстрация того, как вы можете решить эту проблему только с помощью функций ES6 и Array.

2 голосов
/ 09 августа 2015

Это решение может возвращать несколько элементов массива, если они встречаются за одно и то же время. например, массив arr = [3,4,3,6,4] имеет два значения режима: 3 и 6.

Вот решение,

function find_mode(arr) {
    var max = 0;
    var maxarr = [];
    var counter = [];
    var maxarr = [];

    arr.forEach(function(){
       counter.push(0);
    });

    for(var i = 0;i<arr.length;i++){
       for(var j=0;j<arr.length;j++){
            if(arr[i]==arr[j])counter[i]++; 
       }
    } 


    max=this.arrayMax(counter);   

    for(var i = 0;i<arr.length;i++){
         if(counter[i]==max)maxarr.push(arr[i]);
    }

    var unique = maxarr.filter( this.onlyUnique );
    return unique;

  };


function arrayMax(arr) {
      var len = arr.length, max = -Infinity;
      while (len--) {
              if (arr[len] > max) {
              max = arr[len];
              }
      }
  return max;
 };

 function onlyUnique(value, index, self) {
       return self.indexOf(value) === index;
 }
1 голос
/ 04 ноября 2015
function mode(arr){
  return arr.reduce(function(counts,key){
    var curCount = (counts[key+''] || 0) + 1;
    counts[key+''] = curCount;
    if (curCount > counts.max) { counts.max = curCount; counts.mode = key; }
    return counts;
  }, {max:0, mode: null}).mode
}
...