Znajdowanie elementu, który występuje najczęściej w bardzo dużym pliku


12

Słyszałem to pytanie podczas wywiadu i miałem nadzieję uzyskać opinie na temat dobrych odpowiedzi: masz duży plik 10+ GB i chcesz dowiedzieć się, który element występuje najczęściej, co jest dobrym sposobem zrobić to?

Iterowanie i śledzenie mapy prawdopodobnie nie jest dobrym pomysłem, ponieważ zużywasz dużo pamięci, a śledzenie pojawiających się wpisów nie jest najlepszą opcją, ponieważ po postawieniu tego pytania plik zwykle już istnieje.

Inne przemyślenia, które załączyłem, to podzielenie pliku, który ma być iterowany i przetwarzany przez wiele wątków, a następnie połączyć te wyniki, ale problem dotyczący pamięci dla map nadal występuje.


2
Jakie są elementy pliku? Czy to są sznurki? Jeśli weźmiesz znaki za elementy, mapa nie będzie miała problemu z pamięcią. Jeśli elementami są słowa, to znowu myślę, że nie będzie problemu. Jeśli masz wszystkie możliwe podciągi, możesz mieć problemy ...
Nejc

1
Jeśli warunkiem był „element, który pojawia się w ponad połowie wszystkich elementów”, wówczas istnieje rozwiązanie liniowe.
st0le,

Wierzę, że elementy są zwykle łańcuchami. Ale nie rozumiem, jak mapa nie stanowi problemu. W najgorszym przypadku, gdy każdy element jest unikalny, czy nie podwoiłeś swojego zapotrzebowania na pamięć?
Pat

1
Jeśli ma zastosowanie algorytm większości kandydatów Boyera-Moore'a, działa on w czasie liniowym i jest na miejscu.
Juho,

Odpowiedzi:


6

Kiedy masz naprawdę duży plik i jest w nim wiele elementów, ale najbardziej powszechny element jest bardzo powszechny - występuje w ułamku czasu - możesz go znaleźć w czasie liniowym ze słowami spacji stała w notacji jest bardzo mała, w zasadzie 2, jeśli nie policzysz pamięci na rzeczy pomocnicze, takie jak haszowanie. Co więcej, działa to świetnie z pamięcią zewnętrzną, ponieważ plik jest przetwarzany w sekwencji jeden element na raz, a algorytm nigdy nie „ogląda się wstecz”. Jednym ze sposobów, aby to zrobić, jest klasyczny algorytm Misry i Griesa, patrz notatki z wykładu . Problem ten jest obecnie znany jako problem ciężkich uderzających (częstymi elementami są ciężkie uderzające).>1/kO(k)O()

Założenie, że najczęściej pojawia się element ułamka czasu dla małej liczby, może wydawać się silne, ale jest to w pewien sposób konieczne! To znaczy, jeśli będziesz miał sekwencyjny dostęp do swojego pliku (a jeśli plik jest ogromny losowy dostęp będzie zbyt drogi), każdy algorytm, który zawsze znajdzie najczęstszy element w stałej liczbie przejść, użyje spacji liniowej w liczbie elementów . Więc jeśli nie zakładasz czegoś o danych wejściowych, nie możesz pobić tabeli skrótów. Założenie, że najczęstszy element jest bardzo częsty, jest być może najbardziej naturalnym sposobem na obejście negatywnych wyników.>1/kk

Oto szkic dla , tj. Gdy występuje pojedynczy element, który występuje w ponad połowie przypadków. Ten szczególny przypadek jest znany jako algorytm głosowania większościowego i jest spowodowany przez Boyera i Moore'a. Zachowamy jeden element i jedną liczbę. Zainicjuj licznik na 1 i zapisz pierwszy element pliku. Następnie przetworz plik w sekwencji:k=2

  • jeśli bieżący element pliku jest taki sam jak element przechowywany, zwiększ liczbę o jeden
  • jeśli bieżący element pliku różni się od zapisanego elementu, zmniejsz liczbę o jeden
  • jeśli zaktualizowana liczba wynosi 0, „wykop” zapisany element i zapisz bieżący element pliku; zwiększyć liczbę do 1
  • przejdź do następnego elementu pliku

Trochę myślenia o tej procedurze przekona cię, że jeśli istnieje element „większościowy”, tj. Taki, który występuje ponad połowę czasu, to element ten będzie elementem przechowywanym po przetworzeniu całego pliku.

Dla ogólnego , zachować elementów i liczy się, i zainicjować elementy do pierwszej różnych elementów pliku oraz liczbę do liczby razy każdy z tych elementów wydaje się, zanim zobaczyć -tego wyraźny element. Następnie wykonujesz zasadniczo tę samą procedurę: liczba elementów jest zwiększana za każdym razem, gdy się napotyka, wszystkie liczby elementów są zmniejszane, jeśli napotkany jest element, który nie jest przechowywany, a gdy pewna liczba jest równa zero, element ten jest wykopywany na korzyść bieżący element pliku. To jest algorytm Misra-Gries.kk1k kk1kk

Oczywiście możesz użyć tabeli skrótów do zindeksowania przechowywanych elementów . Na zakończenie algorytm gwarantuje, że zwróci każdy element, który wystąpi częściej niż ułamka czasu. Jest to w zasadzie najlepsze, co możesz zrobić z algorytmem, który wykonuje stałą liczbę przejść przez plik i przechowuje tylko słów.1 / k O ( k )k11/kO(k)

I ostatnia rzecz: po znalezieniu kandydatów na „ciężkich hitterów” (tj. Częstych elementów), możesz wykonać jeszcze jedno przejście przez plik, aby policzyć częstotliwość każdego elementu. W ten sposób można uszeregować elementów między sobą i sprawdzić, czy wszystko z nimi występować więcej niż ułamek czasu (jeśli istnieją mniej niż takich elementów, niektóre z elementów zwracanych przez algorytm może być fałszywie dodatnie ).1 / k k - 1k1/kk1


Nie można używać algorytmów Boyer-Moore lub Misra-Gries-Demaine. Podany problem jest inny: nie szukasz elementu większościowego, ale elementu, którego wystąpienia są> = wystąpień wszystkich elementów. Oto prosty kontrprzykład. Niech n będzie całkowitą liczbą elementów, tak że n = 2k + 1 . Niech pierwsze k elementów będzie wynosić 0, następne k elementów będzie miało wartość 1, a ostatni element będzie wynosić 2. Algorytm Boyera-Moorea zgłosi ostatni element, 2, jako potencjalny kandydat większości. Ale w tym konkretnym przypadku wynik musi wynosić 0 lub 1.
Massimo Cafaro,

@MassimoCafaro nie mogę przeanalizować wyrażenia „czyje wystąpienia… elementy”. w każdym razie dobrze wiadomo, że znalezienie najczęstszego elementu w mija wymaganą pamięć ! więc jeśli chcesz mieć mały ślad pamięci, musisz poczynić dodatkowe założenie, przy czym założenie dotyczące ciężkiego uderzenia jest dla mnie najbardziej naturalne. Ω ( n )O(1)Ω(n)
Sasho Nikolov,

Właśnie wskazałem, że jeśli podejmiesz błędne założenie, możesz uzyskać błędne wyniki. Co jest lepsze, mały ślad pamięci i potencjalnie niepoprawny wynik lub poprawny wynik, mimo że kosztuje więcej pamięci? Gdybym musiał wybrać potencjalnie niepoprawny wynik, wybrałbym raczej algorytm losowy niż Boyer-Moore, zakładając coś, o czym nie wiem, że to prawda.
Massimo Cafaro,

@MassimoCafaro, który nie jest kompromisem, który musisz podjąć. jak wskazałem, pojedyncze przejście przez plik łatwo weryfikuje, czy założenie jest spełnione!
Sasho Nikolov,

@MassimoCafaro, a to tylko trywialne rozwiązanie! założenie to można zweryfikować z dużym prawdopodobieństwem za pomocą szkicu CM bez dodatkowych przejść.
Sasho Nikolov,

3

Oczywistą odpowiedzią jest oczywiście utrzymanie mapy skrótów i przechowywanie licznika występowania elementów podczas przeglądania pliku, jak już sugerował Nejc. Jest to (pod względem złożoności czasowej) optymalne rozwiązanie.

Jeśli jednak masz mało miejsca, możesz wykonać sortowanie zewnętrzne w pliku, a następnie znaleźć najdłuższy kolejny ciąg równych elementów. Poniższe powinny mieć stały ślad pamięci i można to zrobić wΘ(nlogn).


Czy mógłbyś bardziej rozwinąć podejście do kodowania Huffmana? Pisałem koder Huffmana wcześniej, ale już jakiś czas, jak dokładnie używałbyś go w tym przypadku?
Pat

@Pat Nevermind tej części było za wcześnie rano i jakoś pomyślałem, że sensowne byłoby skompresowanie danych wejściowych.
Jernej

1

Jeśli najbardziej powszechny element jest znacznie powszechniejszy niż następny wspólny element o znaczny margines, a liczba różnych elementów jest niewielka w porównaniu do rozmiaru pliku, możesz losowo próbkować kilka elementów i zwracać najczęstszy element w próbce.


Co więcej, jeśli wiele razy występuje niewielka liczba elementów, możesz je znaleźć, próbkując, a następnie dokładnie policzyć tylko te elementy.
Maks.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.