Получение 100 лучших номеров из ста миллионов номеров

Один из моих знакомых спросил с вопросом

Получение максимальных 100 номеров из 100 миллионов номеров

в недавнем интервью о работе. У вас есть идея придумать эффективный способ его решения?

Ответ 1

Запустите их через min-heap размера 100: для каждого номера входа k замените текущий min m на max(k, m). После этого куча содержит 100 самых больших входов.

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

Изменить: Я не могу провести собеседование - я дважды получил детали (после того, как сделал это раньше, на производстве). Здесь код для проверки; он почти такой же, как стандарт Python heapq.nlargest():

import heapq

def funnel(n, numbers):
    if n == 0: return []
    heap = numbers[:n]
    heapq.heapify(heap)
    for k in numbers[n:]:
        if heap[0] < k:
            heapq.heapreplace(heap, k)
    return heap

>>> funnel(4, [3,1,4,1,5,9,2,6,5,3,5,8])
[5, 8, 6, 9]

Ответ 2

Хорошо, вот действительно глупый ответ, но он действительный:

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

Рассуждение:

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

Это хорошее решение для какой-то одноразовой операции. Он будет сосать его x раз в секунду или что-то в этом роде. Но тогда нам нужно больше контекста - поскольку mclientk также имел свой простой оператор SQL - предполагая, что 100 миллионов номеров не существует в памяти, это вопрос, который может быть осуществлен из-за того, что... они могут поступать из базы данных и большую часть времени будут при разговоре о соответствующих бизнес-номерах.

Как таковой, вопрос действительно трудно ответить - эффективность должна быть определена.

Ответ 3

Мергосор в партиях по 100, а затем сохранить только 100.

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

Ответ 4

Если данные уже находятся в массиве, который вы можете изменить, вы можете использовать вариант алгоритма Hoare Select, который (в свою очередь) является вариантом Quicksort.

Основная идея довольно проста. В Quicksort вы разбиваете массив на две части: один из элементов больше, чем опорный, а другой - меньше. Затем вы рекурсивно сортируете каждый раздел.

В алгоритме Select вы делаете шаг секционирования точно так же, как раньше - но вместо рекурсивной сортировки обоих разделов вы смотрите, какой раздел содержит нужные вам элементы, и рекурсивно выбирайте ТОЛЬКО в этом разделе. Например, предположив, что ваш 100 миллионов элементов разделяется почти наполовину, первые несколько итераций вы будете смотреть только на верхний раздел.

В конце концов, вы, вероятно, достигнете точки, где часть, которую вы хотите "мосты" двух разделов - например, у вас есть раздел из 150 чисел, а когда вы разбиваете, вы получаете две части ~ 75 кусок. В этот момент изменяется только одна незначительная деталь: вместо отказа от одного раздела и продолжения работы только другой, вы принимаете верхний раздел из 75 элементов, а затем продолжаете искать верхние 25 в нижнем разделе.

Если вы делаете это на С++, вы можете сделать это с помощью std::nth_element (который обычно будет реализован примерно так, как описано выше). В среднем, это имеет линейную сложность, которая, как я считаю, так же хороша, как вы можете надеяться (без какого-либо существующего порядка, я не вижу никакого способа найти верхние элементы N, не глядя на все элементы).

Если данные уже не находятся в массиве, и вы (например) читаете данные из файла, вы обычно хотите использовать кучу. Вы в основном читаете элемент, вставляете его в кучу, и если куча больше вашей цели (в этом случае 100 единиц), вы удаляете один и повторно heapify.

Что, вероятно, не так очевидно (но на самом деле верно) заключается в том, что вы обычно не хотите использовать максимальную кучу для этой задачи. На первый взгляд это кажется довольно очевидным: если вы хотите получить максимальные элементы, вы должны использовать максимальную кучу.

Проще, однако, думать в терминах предметов, которые вы "удаляете" из кучи. Массивная куча позволяет быстро найти один самый большой элемент в куче. Однако он не оптимизирован для поиска наименьшего элемента в куче.

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

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

Ответ 5

Под TOP 100, вы имеете в виду 100 крупнейших? Если да:

SELECT TOP 100 Number FROM RidiculouslyLargeTable ORDER BY Number DESC

Убедитесь, что вы сообщите интервьюеру, что вы считаете, что таблица правильно проиндексирована.

Ответ 6

Нет причин сортировать весь список. Это должно выполняться в O (n) времени. В псевдокоде:

List top = new List

for each num in entireList
    for i = 0 to top.Length
        if num > top[i] then
            top.InsertBefore(num, i)
            if top.Length > 100 then
                top.Remove(top.Length - 1)
            end if
            exit for
        else
            if i = top.Length - 1 and i < 100 then
                top.Add(num)
            end if
        end if
    next
next

Ответ 7

@darius можно на самом деле улучшить!!!
Посредством "обрезки" или отсрочки операции замены кучи по мере необходимости

Предположим, что a = 1000 в верхней части кучи
Это c, b братья и сестры
Мы знаем, что c, b > 1000

      a=1000
  +-----|-----+
 b>a         c>a




We now read the next number x=1035
Since x>a we should discard a.
Instead we store (x=1035, a=1000) at the root
We do not (yet) bubble down the new value of 1035 
Note that we still know that b,c<a but possibly b,c>x
Now, we get the next number y
when y<a<x then obviously we can discard it 

when y>x>a then we replace x with y (the root now has (y, a=1000))
=> we saved log(m) steps here, since x will never have to bubble down

when a>y>x then we need to bubble down y recursively as required

Worst run time is still O(n log m) 
But average run time i think might be O(n log log m) or something
In any case, it is obviously a faster implementation

Ответ 8

Измените массив в O (n). Затем вытащите 100 лучших элементов.

Ответ 9

Я храню первые 100 номеров в Max -Heap размером 100.

  • На последнем уровне я отслеживаю минимальное количество и новый номер, который я вставляю и проверяю с минимальным номером. Если входящий номер является кандидатом на 100.

    - Снова я призываю reheapify, чтобы у меня всегда была максимальная куча 100 лучших.

    Таким образом, его сложность - O (nlogn).

Ответ 10

int numbers[100000000000] = {...};
int result[100] = {0};
for( int i = 0 ; i < 100000000000 ; i++ )
{
    for( int j = 0 ; j < 100 ; j++ )
    {
         if( numbers[i] > result[j] )
         {
              if( j < 99 )
              {
                  memcpy(result+j+1, result+j, (100-j)*sizeof(int));
              }
              result[j] = numbers[i];
              break;
         }
    }
}

Ответ 11

Первая итерация:

Быстросортировать, принимать 100 лучших. O (n log n). Простой, простой в использовании. Очень очевидно.

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

Любые другие соображения? Ну, миллион номеров - это не так много памяти, но если вы хотите свести к минимуму объем памяти, вы до сих пор сохраняете до 100 номеров, а затем просто просматриваете номера. Каким будет лучший способ?

Некоторые упомянули кучу, но немного лучшее решение может быть двусвязным списком, где вы держите указатель на минимум 100 лучших, найденных до сих пор. Если вы столкнулись с номером a, который больше текущего наименьшего в списке, по сравнению со следующим элементом и переместите число рядом с текущим, пока не найдете место для нового номера. (Это в основном просто специализированная куча для ситуации). При некоторой настройке (если число больше текущего минимума, сравните с текущим максимумом, чтобы увидеть, в каком направлении находится список прохода, чтобы найти точку вставки), это будет относительно эффективно и займет всего 1,5 тыс. Памяти.

Ответ 12

Предположим, что mylist - это список из сотни миллионов данных. поэтому мы можем отсортировать список и взять последние сотни данных из списка.

mylist.sort()

MyList [-100:]

Второй способ:

import heapq

heapq.nlargest(100, mylist)