Как я могу отсортировать 1 миллион чисел и напечатать только 10 лучших в Python?

У меня есть файл с 1 миллионом номеров. Мне нужно знать, как я могу эффективно сортировать его, чтобы он не тормозил компьютер и печатал ТОЛЬКО первые 10.

#!/usr/bin/python3

#Find the 10 largest integers
#Don't store the whole list

import sys

def fOpen(fname):
        try:
                fd = open(fname,"r")
        except:
                print("Couldn't open file.")
                sys.exit(0)
        all = fd.read().splitlines()
        fd.close()
        return all

words = fOpen(sys.argv[1])

big = 0
g = len(words)
count = 10

for i in range(0,g-1):
        pos = i
        for j in range(i+1,g):
                if words[j] > words[pos]:
                        pos = j
                if pos != i:
                        words[i],words[pos] = words[pos],words[i]
                count -= 1
                if count == 0:
                        print(words[0:10])

Я знаю, что это сортировка выбором, я не уверен, что лучше всего сделать.


person Seth Kania    schedule 10.02.2012    source источник
comment
Это домашнее задание? Или упражнение из книги?   -  person ChrisW    schedule 11.02.2012
comment
Очевидно, это проблема XY. Проблема не в сортировке, а в нахождении десяти самых больших целых чисел. Хотя их можно найти, сначала отсортировав, а затем выбрав первую десятку записей, это не лучшее решение. Лучшее решение — это предложение pepsi.   -  person pillmuncher    schedule 11.02.2012
comment
Я бы не сказал, что решение Pepsi — лучшее из возможных, возможно, первое существующее решение. На самом деле он не предоставил никакого рабочего кода, но показал, что это проблема XY.   -  person robert king    schedule 12.02.2012


Ответы (4)


Если вам нужны только первые 10 значений, вы потратите много времени на сортировку каждого числа.

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

Это будет означать, что вам нужно сделать только один проход через файл (т.е. временная сложность тета (n))

Простая задача

Вы можете рассматривать свою проблему как обобщение поиска максимального значения в списке чисел. Если бы вам дали {2,32,33,55,13, ...} и попросили найти наибольшее значение, что бы вы сделали? Типичное решение состоит в том, чтобы пройтись по списку, запоминая при этом наибольшее число, встречавшееся до сих пор, и сравнивая его со следующим числом.

Для простоты предположим, что мы имеем дело с положительными числами.

Initialize max to 0
0 < 2, so max = 2
2 < 32, so max = 32
32 < 33, so max = 33
33 < 55, so max = 55
55 > 13, so max = 55
...
return max

Итак, вы видите, что мы можем найти максимум за один проход по списку, в отличие от любой сортировки сравнением.

Обобщение

Поиск 10 первых значений в списке очень похож. Единственная разница в том, что нам нужно отслеживать топ-10, а не только максимум (топ-1).

Суть в том, что вам нужен контейнер, который содержит 10 значений. Когда вы перебираете свой гигантский список чисел, единственное значение, которое вас волнует в контейнере size-10, — это минимум. Это потому, что это номер, который будет заменен, если вы обнаружите новый номер, который заслуживает того, чтобы быть в топ-10 на данный момент.

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

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

person pepsi    schedule 10.02.2012
comment
Это рискует быть в 10 раз медленнее, что может означать 10 миллисекунд вместо 1 миллисекунды. но это может означать 10 секунд вместо 1 секунды. - person robert king; 11.02.2012
comment
если вы хотите получить верхние значения K, то это O(KN) (в зависимости от того, как вы отслеживаете первые 10), проверьте en.wikipedia.org/wiki/Selection_algorithm, что-то вроде медианы медиан равно O(N) - person robert king; 11.02.2012
comment
@robertking: В задаче OP k задается как константа 10, поэтому я упростил ее до тета (n). Если нас действительно интересует общий алгоритм для верхних значений k, мы можем использовать кучу размера k для отслеживания верхних значений k, уменьшая ее до тета (n * lg (k)). Вероятно, это то же самое, что и heapq. Но кто знает, может быть, накладные расходы на управление кучей больше, чем накладные расходы на обход массива размера 10. Вы должны были бы профилировать это, чтобы узнать. - person pepsi; 11.02.2012
comment
Истинный. Мне нравится, как ваш ответ показывает, что не нужно сортировать весь список. Однако просто отследить 10 самых больших значений, на мой взгляд, не так просто, как кажется. более просто можно просто взять минимум списка, а затем извлечь минимум. сделать это десять раз, и это может быть так же быстро. - person robert king; 11.02.2012
comment
Извините, но я все еще изучаю алгоритмы и тому подобное для CS. Хотите краткое объяснение того, что я должен сделать, чтобы запустить список из 10 над 1 миллионом номеров? - person Seth Kania; 11.02.2012
comment
@pepsi: решение для кучи тоже не оптимально. Выбор равен O(n) независимо от k. - person Neil G; 12.02.2012
comment
@NeilG смотрите мой ответ о методе выбора. - person robert king; 12.02.2012
comment
@NeilG: Это правда, но помните, что мы имеем дело с числами в большом файле, и в коде ОП есть комментарий, в котором говорится: «Не сохранять весь список». Приведенный здесь метод выполняет один проход по файлу, что означает, что весь файл не нужно сразу считывать в память. Кроме того, файл читается последовательно, что позволяет использовать тот факт, что последовательный дисковый ввод-вывод намного быстрее случайного. Это важно, учитывая, что IO, вероятно, будет узким местом. - person pepsi; 12.02.2012
comment
@pepsi: хорошо, что вы заметили комментарий, в котором говорится, что не следует хранить все целиком (хотя миллион чисел - это не очень большой файл). Вы правы, что ваше решение выполняет один последовательный проход. Однако быстрый выбор также будет обращаться к файлу последовательно, довольно случайным образом (но обычно для этого требуется много проходов). - person Neil G; 12.02.2012

Лучшая сортировка — это частичная сортировка, доступная в библиотеке Python как heapq.nlargest.

person Fred Foo    schedule 10.02.2012
comment
таким образом, у вас есть красивое решение O (n) вместо O (nlogn) - person juliomalegria; 11.02.2012
comment
@julio.alegria: и O (1) памяти. - person Fred Foo; 11.02.2012
comment
Лучшее в этом: вы можете указать ключевую функцию, как с sorted. - person Jason Sundram; 18.05.2012

Вам нужен хороший алгоритм выбора.

Следующий код Python основан на функции partition() partition, которая разбивает список на две части. Значения меньше "pivotValue" перемещаются в начало списка. Значения больше, чем pivotValue, перемещаются в конец списка. Это делается за O(N) операций, проходя по списку от начала до конца, каждый раз, когда он просматривает значение, он перемещает его ближе к началу списка, только если оно меньше, чем опорное значение.

(обратите внимание, что в вашем случае мы фактически перемещаем большие значения в начало списка, так как вам нужны самые большие значения, а не самые маленькие).

После того, как мы разбили список за время O(N), у нас останется m больших чисел в начале списка. если m=10, то отлично, это ваши десять самых больших чисел. если m больше 10, то нам нужно снова разбить m самых больших чисел, чтобы получить 10 самых больших чисел из m самых больших чисел. если m меньше 10, то нам нужно на 10 m больше чисел, поэтому мы разбиваем правую часть, чтобы найти 10 m чисел, и добавляем их к нашим m числам, чтобы получить нужные 10 чисел.

Итак, мы продолжаем разбивать, пока не получим 10 самых больших чисел. Это делается методом select(). Весь метод обычно очень быстрый, потому что каждый раз, когда мы делаем разбиение, у нас остается примерно вдвое меньше чисел, с которыми нужно иметь дело. (если вы постоянно делите количество цифр, на которые нужно смотреть, на два, это хорошо). Каждый раз, когда мы делаем разбиение, которое дает более 10 больших чисел, мы игнорируем целую кучу слишком маленьких чисел.

Вот код:

def partition(_list,left,right,pivotIndex):
    pivotValue=_list[pivotIndex]
    _list[right],_list[pivotIndex]=pivotValue,_list[right]
    storeIndex=left
    for i in range(left,right):
        if _list[i] > pivotValue:
            _list[storeIndex],_list[i]=_list[i],_list[storeIndex]
            storeIndex+=1
    _list[right],_list[storeIndex]=_list[storeIndex],_list[right]
    return storeIndex

from random import randint
def select(_list,left,right,k):
    if left==right:
        return _list[:left+1]
    pivotIndex=randint(left,right)
    pivotNewIndex=partition(_list,left,right,pivotIndex)
    pivotDist=pivotNewIndex-left+1
    if pivotDist==k:
        return _list[:pivotNewIndex+1]
    elif k<pivotDist:
        return select(_list,left,pivotNewIndex-1,k)
    else:
        return select(_list,pivotNewIndex+1,right,k-pivotDist)

_list=[1,2,109,2234,23,6,1,234,11,4,12451,1]

left=0
right=len(_list)-1
pivotIndex=4

print _list
"[1, 2, 109, 2234, 23, 6, 1, 234, 11, 4, 12451, 1]"
print partition(_list,left,right,pivotIndex) #partition is order(N).
"7" #index 7, so the lowest number are in the first 7 numbers of the list [1, 2, 1, 6, 1, 11, 4, 23]
print _list
"[1, 2, 1, 6, 1, 11, 4, 23, 2234, 109, 12451, 234]"
print select(_list,left,right,10)
"[1, 2, 1, 1, 4, 11, 6, 23, 109, 234]"

with open('nums.txt') as f:
    numbers=map(int,f.readlines())
    print select(numbers,0,len(numbers)-1,10)
    "[1132513251, 2000, 23512, 13252365, 1235, 1251, 324, 100, 82, 82]"
person robert king    schedule 11.02.2012
comment
Ницца. Хотя вам, вероятно, следует возвращать фрагменты, а не копировать списки, и ваш код будет легче читать, если вы будете следовать pep 8 - person Neil G; 12.02.2012

person    schedule
comment
Спасибо, Роберт, это решение, с которым я пошел. С 1 миллионом слов это занимает всего около 4 секунд. Спасибо! - person Seth Kania; 11.02.2012
comment
Хм, я бы подумал, что это будет быстрее, чем это. Возможно, ваш ввод-вывод медленнее моего. В любом случае, readlines() должен быть самым быстрым способом чтения строк, что, вероятно, является здесь узким местом. Не стесняйтесь голосовать за другие решения или ставить зеленую галочку - person robert king; 11.02.2012
comment
@SethRainerKania просто сообщает вам, что встроенное решение Python, вероятно, не то, что ищет ваш учитель, и может не принести вам никаких баллов. - person Ivo; 11.02.2012
comment
Я приму это во внимание. По крайней мере, пока я работаю над новым ответом, у меня есть правильный топ-10. - person Seth Kania; 11.02.2012
comment
Я предлагаю вам прочитать это: en.wikipedia.org/wiki/Selection_algorithm Обратите также внимание на разницу между О(Н) и О(КН) - person robert king; 11.02.2012
comment
Предпочтительным способом создания списка номеров будет numbers = map(int, f). Это позволяет избежать хранения всего содержимого файла в памяти (и также экономит ввод). - person Sven Marnach; 15.02.2012
comment
Спасибо, Свен. Это был бы мой предпочтительный способ, особенно если файл был больше. - person robert king; 15.02.2012