Как я могу сортировать 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])

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

Ответ 1

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

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

Это означает, что вам нужно всего лишь сделать один проход через файл (т.е. временную сложность theta (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

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

Обобщая

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

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

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

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

Ответ 2

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

Ответ 3

import heapq

with open('nums.txt') as f:
    numbers=map(int,f.readlines())
    print heapq.nlargest(10,numbers)
    print heapq.nsmallest(10,numbers)
"""
[1132513251, 13252365, 23512, 2000, 1251, 1235, 324, 100, 82, 82]
[1, 1, 7, 13, 15, 21, 22, 22, 33, 82]
"""

Ответ 4

Что вы хотите - хороший алгоритм

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

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

Как только мы разделили список в O (N) раз, мы остаемся с m большими числами в начале списка. если m = 10, то велика, это ваши десять самых больших чисел. если m больше 10, то нам нужно снова разбить m самых больших чисел, чтобы получить 10 самых больших чисел из самых больших чисел. если m меньше 10, то нам нужно еще 10 чисел, поэтому мы разделяем более ранний участок, чтобы найти 10-миллиметровые числа и добавить их к нашим числам 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]"