JS: написание функции, которая выполняет итерацию по списку строк и возвращает 10 наиболее часто встречающихся строк в списке

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

Вот мое первое решение

const list = [
    "this",
    "is",
    "a",
    "test",
    "which",
    "word",
    "wins",
    "top",
    "i",
    "don't",
    "know",
    "off",
    "hand",
    "do",
    "you",
    "this",
    "a",
    "a",
    "this",
    "test",
    "a",
    "a",
    "do",
    "hand",
    "hand",
    "a",
    "whatever",
    "what",
    "do",
    "do"
  ];

function fn1(strArr) {
    const map = new Map()
    for(const str of strArr) {
        if (map.has(str)) {
            map.set(str, map.get(str) + 1)
        } else {
            map.set(str, 1)
        }
    }
    const sortedMap =[...map.entries()].sort(([_,a], [__,b]) => a < b ? 1 : -1)
    return sortedMap.slice(0 , 10).map(([str]) => str)
}

Но я не могу найти других решений этого вопроса. Может ли кто-нибудь предложить альтернативное предложение?

Кроме того, следует отметить, что список может быть очень большим, может содержать 1 миллион строк. Поэтому нам нужно попытаться свести к минимуму сложность времени выполнения.

Я видел эту проблему где-то на платформах с вызовами кодирования.

jithil 24.12.2020 07:05
.sort() отчасти не нужен и может иметь верхнюю границу O(n log n), но, поскольку вам нужны только 10 лучших, почему бы не перебрать его один раз, чтобы сохранить его на уровне O(n). Я думаю, что ваш начальный цикл for уже O(n). Если это в каком-то производственном приложении, возможно, также запустите веб-работника, чтобы уменьшить массив вдвое.
t348575 24.12.2020 07:10

Я думаю, что это (способ map/dict) лучший подход. Вы можете сделать это намного проще с помощью cnt = {}; for(const s of strArr) cnt[s] ? ++cnt[s] : cnt[s]=1; Другой, но, вероятно, не лучший подход может состоять в том, чтобы отсортировать массив, а затем подсчитать последовательные идентичные записи и обновить список из 10 самых высоких частот. Вы можете найти много похожих записей с помощью поиска Google по запросу «количество вхождений javascript ...».

Max 24.12.2020 07:13

@ t348575 Я не думаю, что ваше предложение равно O (n), потому что для каждого элемента поиск счетчика для этой строки в словаре счетчиков в среднем составляет O (log n). Я думаю, что все эти методы - O (n log n).

Max 24.12.2020 07:21

@Max Я думаю, что ecmascript предписывает .has и другим функциям карты быть на уровне или ниже O(n)

t348575 24.12.2020 07:41

@ t348575 Да, конечно, .has равно o (n), как я уже сказал, это O (log n) (^), но выполнение этого для каждого члена дает O (n log n). (^: Здесь n — количество уже сохраненных записей, но это не меняет асимптотику, поскольку вы можете забыть о первой половине и рассматривать только вторую половину обработки массива (от индексов n/2 до n) и положить n /2 везде, чтобы получить нижнюю границу.Кроме того, учитывая повторения, общее количество записей будет меньше n в какой-то раз, но это также не должно изменить порядок сложности.)

Max 24.12.2020 08:11
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
5
6
914
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

это хорошая проблема для уменьшения карты. :-)

const list = [
  "this",
  "is",
  "a",
  "test",
  "which",
  "word",
  "wins",
  "top",
  "i",
  "don't",
  "know",
  "off",
  "hand",
  "do",
  "you",
  "this",
  "a",
  "a",
  "this",
  "test",
  "a",
  "a",
  "do",
  "hand",
  "hand",
  "a",
  "whatever",
  "what",
  "do",
  "do"
];

const counts = list.reduce((a, c) => {
  a[c] = (a[c] || 0) + 1;
  return a;
}, {})
const items = Object.keys(counts).map(k => {
  return {
    word: k,
    count: counts[k]
  };
});
items.sort((a, b) => a.count > b.count ? -1 : 1);
const result = items.slice(0, 10).map(item => item.word);
console.info(result);

Честно говоря, я не понимаю, чем это отличается от решения, которое я представил в описании. Конечно, вы используете сокращение. Но оба решения отсортировали массив, что усложняет выполнение nlogn.

Joji 24.12.2020 07:28

Попробуй это:

const list = [
    "this",
    "is",
    "a",
    "test",
    "which",
    "word",
    "wins",
    "top",
    "i",
    "don't",
    "know",
    "off",
    "hand",
    "do",
    "you",
    "this",
    "a",
    "a",
    "this",
    "test",
    "a",
    "a",
    "do",
    "hand",
    "hand",
    "a",
    "whatever",
    "what",
    "do",
    "do"
  ];


let counter = {}
for (let item of list){
    counter[item] = 1 + (counter[item] || 0)
}
console.info(counter);

let result = [];
let entries = Object.entries(counter);
let sorted = entries.sort((a, b) => b[1] - a[1]);

for(let i = 0; i < 10; i++){
    result.push(sorted[i][0])
}

console.info(result)

Честно говоря, я не понимаю, чем это отличается от решения, которое я представил в описании.

Joji 24.12.2020 07:31
const list = [
  "this",
  "is",
  "a",
  "test",
  "which",
  "word",
  "wins",
  "top",
.
.
.
  "do",
  "do"
];

const newObjectArray = {};

list.forEach(item=>{
   newObjectArray[item]? newObjectArray[item]++: newObjectArray[item] = 1
});

// now sort based on the newObjectArray  values and return the first 10

это newObjectArray массив или объект?

Joji 24.12.2020 07:31

это объект {}

jithil 25.12.2020 19:22

Это точно такой же подход, как и у ОП.

Mo B. 28.12.2020 23:00

Я думаю, что мы можем сделать следующие шаги для больших данных:

  1. Сопоставление необработанного объекта со структурой карты;
  2. Пройдитесь по карте, чтобы найти на этот раз 1 наиболее часто встречающуюся строку;
  3. Удалить топ 1 с карты;
  4. Повторить 2.

Вот код:

const list = [
    "this",
    "is",
    "a",
    "test",
    "which",
    "word",
    "wins",
    "top",
    "i",
    "don't",
    "know",
    "off",
    "hand",
    "do",
    "you",
    "this",
    "a",
    "a",
    "this",
    "test",
    "a",
    "a",
    "do",
    "hand",
    "hand",
    "a",
    "whatever",
    "what",
    "do",
    "do"
];

// mock 1 million data;
for (let i = 0; i < 1000000; i++) {
    list.push('a');
}

console.clear();
let resultsmap = new Map();

// Object to map with frequent times
function toMap() {
    for (let i = 0; i < list.length; i++) {
        let item = list[i];
        if (resultsmap.has(item)) {
            let count = resultsmap.get(item);
            resultsmap.set(item, count + 1);
        } else {
            resultsmap.set(item, 1);
        }
    }
}

// Found the top 1 for current map
function top1() {
    let maxCount = 0;
    let keyOfMaxCount = '';
    resultsmap.forEach((value, key) => {
        if (maxCount < value) {
            maxCount = value;
            keyOfMaxCount = key;
        }
    });

    return { key: keyOfMaxCount, value: maxCount };
}

toMap();

for(let i=0; i< 10; i++) {
    let topObj = top1();
    console.info(topObj);
    resultsmap.delete(topObj.key);
}

Могу я спросить, почему вы используете некоторые функции ES6, такие как map и forEach, но все еще используете var вместо const или let?

Joji 24.12.2020 17:48

Я пытаюсь не использовать ES6 и использовать пару ключ-значение объекта вместо Map, но это очень утомительно, поэтому замените Map и некоторые функции ES6 и забыли пересмотреть ключевое слово var. Извини за это.

Kevin Zhang 25.12.2020 02:05

Просто замените все var на let.

Kevin Zhang 25.12.2020 02:09

Для этого есть два подхода, причем первый (простая сортировка) уже реализован самостоятельно. Сложность времени: O (N log N), поскольку вы переопределяете весь массив N раз, а затем сортируете N элементов, которые занимают N log N раз. Космическая сложность: O(N)

Второй подход будет заключаться в использовании кучи счетчиков (очередь с минимальным приоритетом, которая сначала выталкивает наименьшие счетчики), после каждой вставки мы проверяем, что, если размер кучи больше 10, мы выталкиваем последний 1, а затем вместо сортируя, мы просто выталкиваем последние 10 элементов. Временная сложность: O (N log k), где N — длина списка, а K — самые верхние строки, в вашем случае это 10. Это потому, что мы следим за тем, чтобы размер кучи не превышал K и с каждым вставить в кучу, он сортирует себя за время O (log K), что дает нам O (N log k) общей временной сложности. Космическая сложность: O(N)

Пример:

const list = [
  "this",
  "is",
  "a",
  ...
  "do"
];

const counts = {};

list.forEach(item=>{
   counts[item]? counts[item]++: counts[item] = 1;
});

// Now that counts is a map of {word -> count}, we need to add these into a priority queue and just pop.
// I will not be implementing a priority queue in this example, you can probably just find an npm package that implements it in a much more efficient way.

const priorityQueue = new PriorityQueue((a,b) => a.count - b.count); // you usually need to pass a comparator function

Object.entries(counts).forEach(([word, count]) => {
  priorityQueue.offer({word, count});
  if (priorityQueue.size() > 10)
    priorityQueue.pop();
}};

// now, just pop the first K (10) elements from the PriorityQueue:
for(let i=0; i<10; i++) {
  console.info(priorityQueue.pop())
}

«Это потому, что при каждой вставке в кучу он сортируется за время O (log K)». Я не думаю, что это правильно. Глядя на код, я могу сказать, что размер кучи может достигать N, а вставка в дерево кучи с N узлами стоит O(logN) времени.

yemre 27.12.2020 17:44

@yemre вы правы, я забыл, что важной частью подхода было использование очереди с минимальным приоритетом вместо максимального. Я отредактировал пример кода и объяснение, теперь он должен работать с фактическим O (N log K)

Chaos Monkey 28.12.2020 04:59

(Когда и где речь идет об одном и том же, пожалуйста, не чередуйте прописные и строчные буквы, как kK выше. (Если вообще используйте одинаковые буквы в разных регистрах для достаточно связанных величин.))

greybeard 28.12.2020 12:38

Я думаю, что временная сложность для первого подхода, т.е. подход, который я написал в описании, не O (nlogn), а O (n + klogk), где n — общее количество строк, а k — количество уникальных строк, поскольку вы только сортировка ключей хэш-карты.

Joji 27.03.2021 01:17

Вот решение, которое позволяет избежать сортировки, собирая записи количества слов Map() в объекте по значению количества. Результатом будут последние 10 элементов в Object.entries() этого объекта. Следует отметить, что это основано на упорядочении объектных ключей, которое исторически было занижено, но, особенно для целочисленных ключей, используемых здесь, предлагает предсказуемое восходящее упорядочение в соответствии с обновлениями спецификации. – см.: Вводит ли ES6 четко определенный порядок перечисления свойств объекта?

Это решение имеет дополнительное преимущество, заключающееся в том, что оно возвращает массивы слов с одинаковым количеством, а не произвольное отсечение, которое вводят решения, использующие sort().

const input = [ "this", "is", "a", "test", "which", "word", "wins", "top", "i", "don't", "know", "off", "hand", "do", "you", "this", "a", "a", "this", "test", "a", "a", "do", "hand", "hand", "a", "whatever", "what", "do", "do"];

let counts = new Map(), i;
for (i = 0; i < input.length; i++) {
  counts.set(input[i], (counts.get(input[i]) ?? 0) + 1);
}

let countHash = {};
counts.forEach((count, word) => (countHash[count] ??= []).push(word));

let result = Object.entries(countHash);
if (result.length > 10) result = result.slice(result.length - 10);

// output
for (let j = result.length - 1; j >= 0; j--) {
  console.info(JSON.stringify(result[j]));
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

Чтобы избежать неопределенности упорядочения/сложности сортировки объекта, используемого для хеширования счетчиков Map с использованием значения счетчика в качестве ключа, вместо этого можно использовать разреженный массив, просто заменив назначение объекта на назначение массива и используя значение счетчика в качестве индекса.

let countArr = [];
counts.forEach((count, word) => (countArr[count] ??= []).push(word));

Object.entries(), вызванный для разреженного массива, учитывает дыры и возвращает только существующие [key, value] пары. (Это верно для Object.keys(), а также для итерации с использованием for...in).

const input = [ "this", "is", "a", "test", "which", "word", "wins", "top", "i", "don't", "know", "off", "hand", "do", "you", "this", "a", "a", "this", "test", "a", "a", "do", "hand", "hand", "a", "whatever", "what", "do", "do"];

let counts = new Map(), i;
for (i = 0; i < input.length; i++) {
  counts.set(input[i], (counts.get(input[i]) ?? 0) + 1);
}

let countArr = [];
counts.forEach((count, word) => (countArr[count] ??= []).push(word));

let result = Object.entries(countArr);
if (result.length > 10) result = result.slice(result.length - 10);

// output
for (let j = result.length - 1; j >= 0; j--) {
  console.info(JSON.stringify(result[j]));
}
// Output of this answer
 [
   [6, ['a']],
   [4, ['do']],
   [3, ['this', 'hand']],
   [2, ['test']],
   [1, ['is', 'which', 'word', 'wins', 'top', 'i', "don't", 'know', 'off', 'you', 'whatever', 'what']]
 ]

// Output using sort
 [
   [6, 'a'],
   [4, 'do'],
   [3, 'this'],
   [3, 'hand'],
   [2, 'test'],
   [1, 'is'],
   [1, 'which'],
   [1, 'word'],
   [1, 'wins'],
   [1, 'top']
 ]
// [ 1, 'i' ],
// [ 1, "don't" ],
// [ 1, 'know' ], 
// [ 1, 'off' ],
// [ 1, 'you' ], 
// [ 1, 'whatever' ],
// [ 1, 'what' ]

это интересно. Таким образом, он основан на том факте, что объекты JS с целочисленным ключом будут автоматически сортировать их по возрастанию...

Joji 29.12.2020 23:25

Точно (можно также использовать разреженный массив со счетчиками в качестве индексов для того же конца, хотя я нашел его менее эффективным). В целом это оставляет вам решение O (n) (начальное сопоставление точно, и в худшем случае, если все слова уникальны, хеширование счетчика и вызов Object.entries, но они, скорее всего, меньше из-за группировки).

pilchard 30.12.2020 00:10

извините, как это все еще O (n)? сортировка целочисленного ключа все еще имеет сложность времени выполнения O (nlogn), верно? Кроме того, «разреженный массив со счетчиками в виде индексов на одном конце» звучит для меня довольно интересно. не могли бы вы добавить это в качестве альтернативы вопросу?

Joji 30.12.2020 01:42

Из того, что я могу понять, Object.keys() имеет сложность O (n) Object.keys()? и целые ключи не обрабатываются так же, как именованные ключи ( v8.dev/blog/fast-properties) и сильно оптимизированы, поэтому, хотя могут быть некоторые затраты, я не верю, что это приближается к O(nlogn ). Я снова рассмотрю вариант разреженного массива.

pilchard 30.12.2020 01:53

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

pilchard 30.12.2020 03:30

Я понимаю, что Object.keys — это линейная сложность, поскольку она должна просто перебирать ключи. Однако, поскольку объект сортирует целочисленные ключи автоматически, моя интуиция подсказывает, что сортировка — это процесс O(nlogn). Также мне интересно, можете ли вы реализовать countHash как Map вместо простого объекта? Думаю, Map тоже ведет себя как Object?

Joji 30.12.2020 18:05
Map() упорядочивается по порядку вставки и поддерживает его посредством преобразования, поэтому не работает. Я думаю, что разреженный массив является самым чистым (и наиболее производительным при тестировании) со встроенным неявным упорядочением.
pilchard 30.12.2020 18:12

Спасибо. Кстати, где вы нашли онлайн-редактор, который поддерживает последний синтаксис js, такой как ??=

Joji 30.12.2020 18:16

В основном я редактирую локально (запуск node v15.4.0 в vscode), но все консоли firefox/safari/chrome поддерживают его (и фрагменты здесь). Только что попробовал jsfiddle, jsbin и stackblitz, и все они тоже, хотя codeandbox выдает ошибку.

pilchard 30.12.2020 19:04

@pilchard Проголосовал за, я действительно изо всех сил старался найти лучшее решение, чем ваше, с учетом производительности, но не смог. Я сравнил все решения во всех ответах здесь, и ваше решение было лучшим. Единственное улучшение, которое вы можете сделать, заключается в том, что в for (i = 0; i < input.length; i++) Для каждой итерации цикла JavaScript извлекает input.length, операции поиска ключа в каждом цикле. Нет причин, по которым этого не должно быть: for (i = 0, n = input.length; i < n; i++)

Abbas Hosseini 02.01.2021 23:05

Спасибо за отзыв @Abbas, и я согласен с вашей настройкой. Оплошность с моей стороны.

pilchard 02.01.2021 23:08

@Joji Нет гарантии, что вставка пары ключ-значение в объект будет O (1), а получение keys() - O (n). На самом деле, поскольку никакой движок JavaScript не может творить чудеса, в какой-то момент он должен быть O(n log n), когда вы можете использовать его для сортировки. Детали зависят от конкретного двигателя, количества и распределения ключей. См. этот ответ и обсуждение под ним: stackoverflow.com/a/64912755/689923

Mo B. 03.01.2021 10:05

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


Предполагая, что list такой же, как в вашем примере, превратите его в массивную строку:

const search = list.join(' ');

Почему? Потому что тогда вы можете подсчитывать вхождения с помощью RegExp#match, например,

search.match(/\bhand\b/g).length;
//=> 3

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

Затем превратите list в уникальный список:

const uniq_list = [...new Set(list)];

Затем вы можете отсортировать uniq_list, подсчитав, сколько раз каждый элемент появляется в search:

const ordered_by_occurrence =
  uniq_list.sort((a, b) => {
    const count_a = search.match(new RegExp(`\\b${a}\\b`, 'g')).length;
    const count_b = search.match(new RegExp(`\\b${b}\\b`, 'g')).length;
    return count_a >= count_b ? -1 : 1;
  });

(Возврат первых n элементов упорядоченного списка тривиален.)

У меня возник соблазн понизить голосование, но вы правы, ОП просто попросил альтернативные, а не хорошие решения. Ваше решение, безусловно, креативное, и трудно придумать еще более неэффективное. ;)

Mo B. 28.12.2020 22:55
Ответ принят как подходящий

Я считаю, что следующее решение является самым быстрым на практике:

  1. Сопоставьте строки n с их частотами, как вы уже сделали. O(n)
  2. Преобразуйте карту в массив с парами строк/частот. O(n)
  3. Преобразуйте массив в Max-heap на основе частоты, используя метод Флойда (т. е. вызвав max-heapify для всех индексов от ⌊n/2⌋ - 1 до 0). O(n)
  4. Извлеките верхний элемент k раз. (В вашем случае k=10.) O(k log n)

(Я не привожу здесь никакого кода, потому что он в основном состоит из вызова библиотеки двоичной кучи (см. здесь высокооптимизированную реализацию).)

Анализ

Асимптотическая сложность равна O(n + k log n), что меньше O(n log k) решения, предоставленного Chaos Monkey. Для маленьких k (например, 10), вероятно, не будет существенной разницы. Разница становится более очевидной для больших k (пока k ≪ n; для гораздо больших k см. «Альтернатива» ниже). Также обратите внимание, что постоянный коэффициент для шагов 1 и 2 равен 1, а постоянный коэффициент для шага 3 также в среднем мал (1,8814), поэтому общий постоянный коэффициент меньше 4.

Альтернатива

Существует решение, которое решает задачу в O(n) в среднем, также с небольшим постоянным множителем, что гораздо эффективнее при больших k (т.е. когда k приближается к n/2). Недостатком является то, что (очень маловероятно) сложность в худшем случае равна O(n²):

  1. Сопоставьте строки n с их частотами, как вы уже сделали. O(n)
  2. Преобразуйте карту в массив с парами строк/частот. O(n)
  3. Примените Quickselect (точно так же, как Quicksort, но просто рекурсивно на одной стороне раздела), чтобы найти k-ю по величине частоту. Все элементы слева еще больше, поэтому в результате получаются первые k элементы массива Quickselected. O(n) среднее, O(n²) худшее.

Можно реализовать вариант Quickselect с гарантированной O(n) сложностью, используя медиану медиан опорного выбора, но на практике это не очень хорошо, потому что постоянный коэффициент довольно высок. Но с академической точки зрения это было бы асимптотически оптимальным решением.

(Вот JavaScript-библиотека для Quickselect, хотя на первый взгляд реализация не выглядит идеальной для этого случая: хорошая реализация должна выполнять трехстороннее разбиение в стиле Дейкстры.)

Ориентир

Быстрый и грязный тест с n = 10^6, k = 10, измеряющий только время выполнения после шага 2 (поскольку шаги 1/2 являются общими для всех 5 методов):

Average time for Sort: 7.5 ms
Average time for ChaosMonkey: 10.25 ms
Average time for CountingSort: 5.25 ms
Average time for Mo B. Max-Heap: 4 ms
Average time for Mo B. Alternative (Quickselect): 3.25 ms

https://dotnetfiddle.net/oHRMsp

Мой вывод состоит в том, что для заданных параметров между разными методами нет большой разницы. Для простоты я бы просто придерживался метода сортировки, который также хорошо масштабируется как для n, так и для k.

(Много предостережений: он написан (неряшливо) на C#, а не на JavaScript; он не проверялся на корректность; отдельные методы не оптимизированы; время выполнения также может зависеть от распределения частот, реализованный Quickselect наивен в том, что он не оптимизирован для (здесь) распространенного случая, когда множество частот равны и т. д...)

Последнее замечание о космосе

Все проверенные методы используют дополнительное пространство O(n), потому что они сначала создают карту частот (а метод сортировки подсчетом использует дополнительное пространство O(n) для наихудшего случая поверх карты частот для подсчета). Задачу можно решить всего за O(1) дополнительного места, но за счет временной сложности O(kn²).

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

Samuel Liew 03.01.2021 05:11

const list = [
    "this",
    "is",
    "a",
    "test",
    "which",
    "word",
    "wins",
    "top",
    "i",
    "don't",
    "know",
    "off",
    "hand",
    "do",
    "you",
    "this",
    "a",
    "a",
    "this",
    "test",
    "a",
    "a",
    "do",
    "hand",
    "hand",
    "a",
    "whatever",
    "what",
    "do",
    "do"
  ];

function fn2(){
    const sorted = list.sort((a, b) => a < b ? -1 : 1 );
    let counter = 1; 
    let word = sorted[0]
    const counted = {} 
    for (i = 1; i < sorted.length; ++i){
        if (sorted[i] === word) counter ++
        else {
            counted[word]= counter;
            counter = 1;
            word = sorted[i]   
        } 
    } 
    const most = Object.entries(counted)
        .sort((a,b) => a[1] > b[1] ? -1: 1)
        .slice(0, 10)
        .map(e => e[0])
    return most;    
}

console.info(fn2(list))

Вы можете сделать это так...

const list = 
  [ "this", "is", "a", "test", "which", "word", "wins", "top"
  , "i", "don't", "know", "off", "hand", "do", "you", "this"
  , "a", "a", "this", "test", "a", "a", "do", "hand"
  , "hand", "a", "whatever", "what", "do", "do"
  ];

const getTopTen = arr =>
  [...new Set(arr)]
    .map(txt=>({txt, n:arr.filter(t=>t===txt).length}))
    .sort((a,b)=>b.n-a.n)
    .slice(0,10)
    .map(({txt,n})=>txt);

console.info( getTopTen(list) );
.as-console-wrapper { max-height: 100% !important; top: 0; }

второе решение без сортировки:

const list = 
  [ "this", "is", "a", "test", "which", "word", "wins", "top"
  , "i", "don't", "know", "off", "hand", "do", "you", "this"
  , "a", "a", "this", "test", "a", "a", "do", "hand"
  , "hand", "a", "whatever", "what", "do", "do"
  ];

function getTopTen(arr)
  {
  let res = []
    , max = 10
    , cnt = [...new Set(arr)]
      .reduce((occ,txt) => {                  // making of { occurences , [ ...terms ] }
        let n = arr.filter(t=>t===txt).length  // count of sames terms
          , p = occ.find(x=>x.n===n)           
          ;
        if (!p) {                              // new occurence
          p = { n, t:[] }
          let i = occ.findIndex(x=>x.n<n)      // check is place   
          if (i<0) occ.push(p)                 // on bottom, or
          else     occ.splice(i,0,p)           // just in right place
          }
        p.t.push(txt)                          // add the term in his occ list
        return occ
      },[])
    ;
  for (let c of cnt) {        // keep only the 10 first elments
    for (let t of c.t)
      { res.push(t); if (!--max) break }  // break loop on  
    if (!max) break                      // quota zero
    }
  return res 
  }

console.info( getTopTen(list) );
.as-console-wrapper { max-height: 100% !important; top: 0; }

Об этом уже говорили ([Даниил Лобан])(stackoverflow.com/a/65535865) за одно), хотя и не все (пока).

greybeard 02.01.2021 07:22

@greybeard не совсем: мое решение в настоящее время является единственным, которое использует array.filter (). Кстати, я добавил второе решение, которое не использует сортировку

Mister Jojo 02.01.2021 07:50

Поскольку наш диапазон (частота слов) ограничен длиной списка, у нас может быть алгоритм O(n) без логарифмического фактора, использующий сортировку подсчетом. Более того, если частоты значительно меньше n, наши итерации будут намного короче.

Код JavaScript:

function getTopK(freq, min, max, k){
  let i = min;
  let j = 0;
  let counts = [];
  let result = new Array(k);

  for (; i<=max; i++)
    counts[i] = [];

  for (i=0; i<freq.length; i++)
    counts[freq[i][1]].push(i);

  for (i=max; i>=min, j<k; i--){
    for (let m=0; m<counts[i].length; m++){
      result[j] = freq[counts[i][m]][0];
      j++;
      if (j == k)
        break;
    }
  }
  return result;
};

function f(list, k){
  let max = 0;

  const freq = list.reduce(function(acc, s){
    acc[s] = -~acc[s];
    max = Math.max(max, acc[s]);
    return acc;
  }, new Map())

  return getTopK(Object.entries(freq), 1, max, k);
}

var list = [
    "this",
    "is",
    "a",
    "test",
    "which",
    "word",
    "wins",
    "top",
    "i",
    "don't",
    "know",
    "off",
    "hand",
    "do",
    "you",
    "this",
    "a",
    "a",
    "this",
    "test",
    "a",
    "a",
    "do",
    "hand",
    "hand",
    "a",
    "whatever",
    "what",
    "do",
    "do"
];

console.info(JSON.stringify(f(list, 10)));

@greybeard хорошее замечание. Определение min там неверное. Но соответствующая итерация в любом случае завершается раньше, когда достигается k.

גלעד ברקן 02.01.2021 17:56

Если я правильно понял, временная сложность здесь должна быть O(n+k), где n — длина массива, а k — количество элементов, которые вы хотите вернуть.

const data = ["this", "is", "a", "test", "which", "word", "wins", "top", "i", "don't", "know", "off", "hand", "do", "you", "this", "a", "a", "this", "test", "a", "a", "do", "hand", "hand", "a", "whatever", "what", "do", "do"];

function getTopOccurrences(n, list) { 
    const [_, arr] = list.reduce(([obj, arr], current) => {
        const oldOccurrences = obj[current] || 0;
        const newOccurrences = oldOccurrences + 1;
        if (!arr[oldOccurrences]) { arr[oldOccurrences] = new Set(); }
        if (!arr[newOccurrences]) { arr[newOccurrences] = new Set(); }
        
        if (current in obj) {
            arr[oldOccurrences].delete(current);
        }
        arr[newOccurrences].add(current);
        obj[current] = newOccurrences;

        return [obj, arr];
    }, [{},[]]);

    const result = [];
    outer: for(let i = arr.length - 1, s = arr[i]; i >= 0; i--, s= arr[i]) {
        for (let word of s){
            result.push(word);
            n--;
            if (n <= 0) {
                break outer;
            }
        }
    }
    return result;
}

console.info(getTopOccurrences(10, data));

Другие вопросы по теме