2011-01-06 9 views
20

Wszystko jest w tytule. Zastanawiam się, czy ktokolwiek zna się na szybką i rozsądną pamięć, wymaga losowego miksowania wszystkich linii pliku z 3 milionami linii. Domyślam się, że nie jest to możliwe za pomocą prostej komendy vim, więc każdy prosty skrypt używa Pythona. Próbowałem z Pythonem przy użyciu generatora liczb losowych, ale nie udało mi się znaleźć prostego wyjścia.Losowe łączenie linii pliku o wartości 3 milionów wierszy

+2

można zobaczyć [pytanie] (http://stackoverflow.co m/questions/1287567/c-is-using-random-and-orderby-a-good-shuffle-algorithm) dla niektórych pomysłów. –

+1

"nie udało się znaleźć prostego wyjścia." Naprawdę? Opublikuj kod, który jest zbyt skomplikowany. –

+0

Powinienem powiedzieć: "nie udało się znaleźć wyjścia". Jestem całkiem nowy w Pythonie, więc znam tylko niektóre polecenia. Chciałem umieścić wszystko w wektorze, wybierając losową liczbę od 1 do 3 milionów, wyjąć tę linię i rozpocząć od nowa z nową liczbą losową z dodatkowym warunkiem, wyłączając poprzednie losowe liczby. Itd. Stąd moje pytanie w prosty sposób (które ty i inni dostarczyliście). Przyjmę twoje, ponieważ masz najwięcej głosów. Jednak dzięki każdemu ... wiele się nauczyłem! – Nigu

Odpowiedz

21
import random 
with open('the_file','r') as source: 
    data = [ (random.random(), line) for line in source ] 
data.sort() 
with open('another_file','w') as target: 
    for _, line in data: 
     target.write(line) 

To powinno wystarczyć. 3 miliony linii zmieści się w pamięci większości maszyn, chyba że linie są OGROMNE (ponad 512 znaków).

+0

3 miliony linii z przeciętnymi 80 znakami na linię będzie wynosić około 240 M bajtów, co jest ogromne do załadowania pliku w pamięci. –

+1

@ Vikram.exe. Nie całkiem. To urządzenie ma 4 GB pamięci. 240M to nic. –

+0

@ S.Lott, tak, zgadzam się, że nic, ale zastanawiałem się, czy możemy to zrobić, jak (przy niewielkim wysiłku), bez ładowania całego pliku w pamięci. –

34

zajmuje tylko kilka sekund w Pythonie:

>>> import random 
>>> lines = open('3mil.txt').readlines() 
>>> random.shuffle(lines) 
>>> open('3mil.txt', 'w').writelines(lines) 
+1

To nie działa. 'shuffle' działa tylko na względnie małych listach, z grubsza 2000 elementów lub mniej. Może również nie mieć "rozsądnych" wymagań dotyczących pamięci, w zależności od długości linii. Teraz, jeśli potrzebujesz tylko "random-ish", może to wystarczy. Ale może nie. Szczegółowe informacje można znaleźć na stronie http://stackoverflow.com/questions/3062741/maximal-length-of-list-to-shuffle-with-python-random- shuffle. –

+6

Z pewnością * działa * i działa dobrze. To, że może generować tylko 2 ** 19937 permutacji, jest banalne, graniczące z nieistotnym. Każdy shuffle oparty na RNG będzie miał to samo "ograniczenie". –

+2

W jaki sposób rozwiązanie "sort()" jest lepsze niż 'shuffle()'? Nie unika tego rzekomego problemu. –

3

W wielu systemach polecenia Shell sort trwa -R do losowo swoje wejście.

+2

Należy zauważyć, że opcja '-R' nadal będzie sortować identyczne linie, co może nie być pożądanym zachowaniem. –

+3

'shuf' losuje linie bez względu na równość i jest prawdopodobnie najszybszym rozwiązaniem – fuzzyTew

2

Oto kolejna wersja

W powłoce użyj tego.

python decorate.py | sort | python undecorate.py 

decorate.py

import sys 
import random 
for line in sys.stdin: 
    sys.stdout.write("{0}|{1}".format(random.random(), line)) 

undecorate.py

import sys 
for line in sys.stdin: 
    _, _, data= line.partition("|") 
    sys.stdout.write(line) 

Używa prawie bez pamięci.

+0

Jak podano powyżej, 'sort -R' sortuje według klucza losowego Łatwiejszy niż dekorowanie i dekonspirowanie pliku –

+0

@Chris B. Jak już wspomniałeś powyżej, '-R' nadal będzie grupować identyczne linie, to nie będzie, więc jeśli jest to pożądane zachowanie, to jest to sposób na pokonanie – aaronasterling

+1

Jak wskazano powyżej,' 'shuf' losuje linie z każdą permutacją równie prawdopodobną i nie wymagającą niestandardowego kodu na początku lepiej niż pisanie i debugowanie własnego programu. –

1

To jest taka sama jak Pan Kugelman, ale przy użyciu vim wbudowanego interfejsu Pythona:

:py import vim, random as r; cb = vim.current.buffer ; l = cb[:] ; r.shuffle(l) ; cb[:] = l 
1

Jeśli nie nie chcesz załadować wszystko do pamięci i sortować je tam, mają do przechowywania linii na dysku podczas sortowania losowego. To będzie bardzo powolne.

Oto bardzo prosta, głupia i wolna wersja. Zauważ, że może to zająć zaskakującą ilość miejsca na dysku i będzie bardzo powolny. Uruchomiłem go z 300 000 linii i zajmuje to kilka minut. 3 miliony linii może zająć godzinę. A więc: Zrób to w pamięci. Naprawdę. To nie jest takie duże.

import os 
import tempfile 
import shutil 
import random 
tempdir = tempfile.mkdtemp() 
print tempdir 

files = [] 
# Split the lines: 
with open('/tmp/sorted.txt', 'rt') as infile: 
    counter = 0  
    for line in infile: 
     outfilename = os.path.join(tempdir, '%09i.txt' % counter) 
     with open(outfilename, 'wt') as outfile: 
      outfile.write(line) 
     counter += 1 
     files.append(outfilename) 

with open('/tmp/random.txt', 'wt') as outfile: 
    while files: 
     index = random.randint(0, len(files) - 1) 
     filename = files.pop(index) 
     outfile.write(open(filename, 'rt').read()) 

shutil.rmtree(tempdir) 

Inną wersją byłoby przechowywanie plików w bazie danych SQLite i losowe losowanie linii z tej bazy danych. To prawdopodobnie będzie szybsze niż to.

+0

"To będzie bardzo powolne"? Wolniej tak. Bardzo powolny jest dyskusyjny. Każdy krok jest dość szybki. –

+1

@ S.Lott: Cóż, zależy to od systemu plików. Użyłem ext3. 30 000 przedmiotów zajęło 5,5 sekundy. 100.000 przedmiotów zajęło 16,3 sekundy. 200.000 przedmiotów zajmuje 339 sekund. Myślę, że wyszukiwanie katalogu jest powolne z wieloma elementami. 3 miliony przedmiotów zajmie * godzin *. Przynajmniej. Baza danych może być dość szybka, ale nie mogę się poddać próbie. :-) Inną opcją byłoby odczytanie pliku i utworzenie indeksu na początkowej pozycji każdego elementu i wykonanie seek(). To powinno być szybsze niż to. –

+0

Interesujące dane. Wydaje mi się, że spędziłem zbyt długo na bardzo dużych serwerach. –

12

Po prostu próbowałem tego na pliku z 4,3 M linii i najszybszą rzeczą było polecenie "shuf" na Linuksie. Użyj go tak:

shuf huge_file.txt -o shuffled_lines_huge_file.txt 

Trwało 2-3 sekundy.

0

Oto kolejny sposób korzystania random.choice, może to zapewnić pewne stopniowe pamięci zwalnia, jak również, ale z gorszym Big-O :)

from random import choice 

with open('data.txt', 'r') as r: 
    lines = r.readlines() 

with open('shuffled_data.txt', 'w') as w: 
    while lines: 
     l = choice(lines) 
     lines.remove(l) 
     w.write(l) 
+0

"Lepszy Big-O" <- Niestety nie :-(Powtórne usunięcie w 'lines.remove (l)' daje twój algorytmowi czas pracy, który jest kwadratowy w liczbie linii. Będzie on bezużyteczny (czas działania godzin do dni) dla pliku o wartości 3 milionów wierszy –

+0

Ups, masz rację :-) naprawiłeś to –

0

Poniżej Vimscript może być używany do wymiany linie:

function! Random()              
    let nswaps = 100              
    let firstline = 1              
    let lastline = 10              
    let i = 0                
    while i <= nswaps              
    exe "let line = system('shuf -i ".firstline."-".lastline." -n 1')[:-2]" 
    exe line.'d'               
    exe "let line = system('shuf -i ".firstline."-".lastline." -n 1')[:-2]" 
    exe "normal! " . line . 'Gp'           
    let i += 1               
    endwhile                
endfunction 

Wybierz funkcję w trybie wizualnym i wpisz :@" następnie wykonać ją :call Random()

Powiązane problemy