Calcola la mediana di un miliardo di numeri


127

Se hai un miliardo di numeri e cento computer, qual è il modo migliore per individuare la mediana di questi numeri?

Una soluzione che ho è:

  • Dividi il set equamente tra i computer.
  • Ordinali.
  • Trova le mediane per ogni set.
  • Ordina i set in base alle mediane.
  • Unisci due serie alla volta dalla mediana più bassa alla più alta.

Se m1 < m2 < m3 ...poi abbiamo prima unito Set1e, Set2e nel set risultante, possiamo scartare tutti i numeri inferiori alla mediana di Set12(unita). Quindi in qualsiasi momento abbiamo serie uguali. A proposito, ciò non può essere fatto in modo parallelo. Qualche idea?


3
@ John Boker: in realtà il problema consiste in due sottoproblemi: 1) ordina l'elenco e 2) ottieni l'elemento con indice 5'000'000'000. Non credo che i numeri siano ordinati.
Roman

3
@Roman: il problema non deve necessariamente consistere nei due sottoproblemi che descrivi, ad esempio quickselect. Ma la selezione rapida non si parallelizza, almeno non banalmente. E ovviamente hai ragione che se i numeri sono preordinati è una domanda piuttosto inutile.
Steve Jessop,

5
@fmsf: non credo che nessun paese di lingua inglese usi il lungo miliardo in inglese per scopi ufficiali. Per esempio qui nel Regno Unito, abbiamo smesso di usarlo nel 1974. Considererei l'uso di "miliardi" per significare un milione di milioni, in lingua inglese come una domanda perversa, non un "vero miliardo". Naturalmente in francese sarebbe una questione totalmente diversa, ma la domanda non è in francese.
Steve Jessop,

5
Non è necessario ordinare! en.wikipedia.org/wiki/…
glebm,

2
1 miliardo di numeri è solo pochi gigabyte di dati, non hai bisogno di più PC né algoritmi complessi per risolvere questo compito. Non complicarti troppo.
user626528

Risposte:


54

Ah, il mio cervello è appena entrato in marcia, adesso ho un suggerimento ragionevole. Probabilmente troppo tardi se questa fosse stata un'intervista, ma non importa:

La macchina 1 deve essere chiamata "macchina di controllo", e per ragioni di argomento o inizia con tutti i dati e li invia in pacchi uguali alle altre 99 macchine, oppure i dati si avviano uniformemente distribuiti tra le macchine e invia 1/99 dei suoi dati a ciascuno degli altri. Le partizioni non devono essere uguali, solo vicino.

Ogni altra macchina ordina i suoi dati e lo fa in un modo che favorisce la ricerca dei valori più bassi per primi. Quindi ad esempio un quicksort, ordinando sempre prima la parte inferiore della partizione [*]. Scrive i suoi dati sulla macchina di controllo in ordine crescente non appena possibile (utilizzando IO asincrono per continuare l'ordinamento, e probabilmente con Nagle attivo: sperimentare un po ').

La macchina di controllo esegue un'unione a 99 vie sui dati man mano che arrivano, ma scarta i dati uniti, tenendo semplicemente conto del numero di valori che ha visto. Calcola la mediana come media del 1/2 miliardesimo e 1/2 miliardo più i valori oneth.

Questo soffre del problema "più lento nella mandria". L'algoritmo non può essere completato fino a quando non viene inviato ogni valore inferiore alla mediana da una macchina di selezione. C'è una ragionevole possibilità che uno di questi valori sia piuttosto elevato all'interno della sua porzione di dati. Quindi, una volta completato il partizionamento iniziale dei dati, il tempo di esecuzione stimato è la combinazione del tempo per ordinare 1/99 dei dati e rispedirli al computer di controllo e il tempo per il controllo di leggere 1/2 dei dati . La "combinazione" è da qualche parte tra il massimo e la somma di quei tempi, probabilmente vicino al massimo.

Il mio istinto è che l'invio di dati su una rete sia più veloce dell'ordinamento (figuriamoci solo selezionando la mediana) deve essere una rete piuttosto dannatamente veloce. Potrebbe essere una prospettiva migliore se si può presumere che la rete sia istantanea, ad esempio se si hanno 100 core con uguale accesso alla RAM contenente i dati.

Poiché è probabile che l'I / O di rete sia vincolato, potrebbero esserci dei trucchi che puoi giocare, almeno per i dati che ritornano alla macchina di controllo. Ad esempio, invece di inviare "1,2,3, .. 100", forse una macchina di smistamento potrebbe inviare un messaggio che significa "100 valori inferiori a 101". La macchina di controllo potrebbe quindi eseguire un'unione modificata, in cui trova il minimo di tutti quei valori di fascia alta, quindi dice a tutte le macchine di smistamento che cosa era, in modo che possano (a) dire alla macchina di controllo come molti valori da "contare" al di sotto di tale valore e (b) riprendono a inviare i loro dati ordinati da quel punto.

Più in generale, c'è probabilmente un intelligente gioco di indovinare la risposta alla sfida che la macchina di controllo può giocare con le 99 macchine di selezione.

Ciò comporta però viaggi di andata e ritorno tra le macchine, che la mia prima versione più semplice evita. Non so davvero come stimare ciecamente le loro prestazioni relative e, poiché i compromessi sono complessi, immagino che ci siano soluzioni molto migliori là fuori di qualsiasi cosa penserò di me stesso, supponendo che questo sia sempre un vero problema.

[*] stack disponibile permettendo - la tua scelta di quale parte fare per prima è limitata se non hai O (N) spazio extra. Ma se hai abbastanza spazio extra, puoi fare la tua scelta, e se non hai abbastanza spazio puoi almeno usare quello che devi fare per tagliare alcuni angoli, facendo prima la piccola parte per le prime partizioni.


Per favore, correggimi se sbaglio, perché stai eseguendo l'unione a 99 vie sui dati poiché arrivano solo per scartarli in seguito. Invece è abbastanza per tenere il conto dei numeri man mano che arriva?
sreeprasad,

4
@SREEPRASADGOVINDANKUTTY: il passaggio ripetuto consiste nello scartare il valore più piccolo tra tutti i 99 candidati e aumentare il conteggio. Non serve affatto tenere semplicemente il conto di tutti i valori in entrata senza questo passaggio di unione a 99 vie. Se non li confronti quando entrano, non sai che il valore che stai scartando è inferiore alla mediana.
Steve Jessop,

Ma non c'è una piccola possibilità che una di queste partizioni contenga solo numeri più alti della mediana e quindi qualsiasi partizione inferiore che restituisce sarà più alta della mediana, ma poiché il controllo non lo sa, li scarterà come inferiori mediana e fallire ...?
Gullydwarf,

@Gullydwarf: un'unione a più vie scarta solo il più piccolo dei 99 valori che ha in mano, ognuno dei quali è il più piccolo valore rimanente di una delle altre macchine. Se una delle partizioni è completamente maggiore della mediana, allora non diventerà il minimo di quei 99 valori fino a quando la mediana non sarà passata (a quel punto abbiamo finito). Quindi non verrà scartato.
Steve Jessop,

52
sort -g numbers | head -n 500000001 | tail -n 2 | dc -e "1 k ? ? + 2 / p"

2
LOL. Funziona davvero o il killer OOM lo screditerà prima che sia completato? (su qualsiasi computer ragionevole)
Isak Savo,

5
Dovresti ... dovrebbe. sort sa come fare un ordinamento out-of-core, quindi non esaurirà la memoria.
DrPizza,

6
@Zagfai Non penso che ci vorrebbe troppo tempo; un miliardo di numeri è solo 4 GB per ints / float a 32 bit, 8 GB per ints / double a 64 bit. Nessuno dei due sembra tremendamente faticoso.
DrPizza,

13
Ho appena provato un Intel i5-4200M a 3,1 GHz (4 core). Secondo il timecomando applicato all'intera pipeline, ci voleva real=36m24s("tempo di clock"), user=113m15s ("tempo parallelo", tutti i core aggiunti). Il comando più lungo, molto più avanti degli altri, fu sort, anche se si collegava ai miei quattro core al 100%. Il consumo di RAM è stato molto accettabile.
Morgan Touverey Quilling il

12
Quindi esegui 100 computer, così puoi essere 100 volte più sicuro che il risultato sia corretto :)
dos

27

Odio essere il contrarian qui, ma non credo che sia necessario l'ordinamento e penso che qualsiasi algoritmo che coinvolge l'ordinamento di un miliardo / 100 numeri sarà lento. Consideriamo un algoritmo su un computer.

1) Seleziona 1000 valori a caso dal miliardo e usali per avere un'idea della distribuzione dei numeri, in particolare un intervallo.

2) Invece di ordinare i valori, assegnarli ai bucket in base alla distribuzione appena calcolata. Il numero di secchi viene scelto in modo che il computer possa gestirli in modo efficiente, ma dovrebbe essere altrettanto grande quanto conveniente. Gli intervalli di bucket devono essere in modo tale che un numero approssimativamente uguale di valori vada in ciascun bucket (questo non è fondamentale per l'algoritmo, ma aiuta l'efficienza. 100.000 bucket potrebbero essere appropriati). Nota il numero di valori in ciascun bucket. Questo è un processo O (n).

3) Scopri quale gamma di benne si trova nella mediana. Questo può essere fatto semplicemente esaminando i numeri totali in ciascun bucket.

4) Trova la mediana effettiva esaminando i valori in quel bucket. Puoi usare un ordinamento qui se vuoi, dato che stai ordinando solo forse 10.000 numeri. Se il numero di valori in quel bucket è grande, è possibile utilizzare nuovamente questo algoritmo fino a quando non si dispone di un numero abbastanza piccolo da ordinare.

Questo approccio parallelizza banalmente dividendo i valori tra i computer. Ogni computer riporta i totali in ciascun bucket a un computer "di controllo" che esegue il passaggio 3. Per il passaggio 4, ogni computer invia i valori (ordinati) nel relativo bucket al computer di controllo (è possibile eseguire anche entrambi gli algoritmi in parallelo, ma probabilmente non ne vale la pena).

Il processo totale è O (n), poiché entrambi i passaggi 3 e 4 sono banali, a condizione che il numero di bucket sia abbastanza grande.


1
Penso che questa sia una via di mezzo tra mediana delle mediane e algoritmi di selezione rapida. en.wikipedia.org/wiki/Selection_algorithm
Dimath

Nel passaggio 4, i bucket potrebbero non contenere solo 10.000. È possibile che la distribuzione sia inclinata verso il centro, in cui potrebbe contenere, per esempio, l'80% dei dati, che è ancora enorme.
solo il

Modificato per tenerne conto.
DJClayworth,

4
Le prestazioni non sono O (n) in questo algoritmo: potresti avere la maggior parte dei numeri che rientrano nel bucket "mediano" e potrebbero comportarsi in modo altrettanto negativo che ordinare tutto.
Sklivvz,

1
@WULF Un'ottima domanda. È la chiave dell'algoritmo e il passaggio 1 lo affronta. Un campionamento dei numeri per stabilire una distribuzione è il migliore che ho ideato.
DJClayworth

12

Un miliardo è in realtà un compito piuttosto noioso per un computer moderno. Stiamo parlando di 4 GB interi di 4 byte qui ... 4 GB ... questa è la RAM di alcuni smartphone.

public class Median {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        int[] numbers = new int[1_000_000_000];

        System.out.println("created array after " +  (System.currentTimeMillis() - start) + " ms");

        Random rand = new Random();
        for (int i = 0; i < numbers.length; i++) {
            numbers[i] = rand.nextInt();
        }

        System.out.println("initialized array after " + (System.currentTimeMillis() - start) + " ms");

        Arrays.sort(numbers);

        System.out.println("sorted array after " + (System.currentTimeMillis() - start) + " ms");

        if (numbers.length % 2 == 1) {
            System.out.println("median = " + numbers[numbers.length / 2 - 1]);
        } else {
            int m1 = numbers[numbers.length / 2 - 1];
            int m2 = numbers[numbers.length / 2];
            double m = ((long) m1 + m2) / 2.0;
            System.out.println("median = " + new DecimalFormat("#.#").format(m));
        }
}

Uscita sulla mia macchina:

created array after 518 ms
initialized array after 10177 ms
sorted array after 102936 ms
median = 19196

Quindi questo si completa sulla mia macchina in meno di due minuti (1:43 di cui 0:10 devono generare numeri casuali) usando un singolo core e sta persino facendo un ordinamento completo. Niente di speciale, davvero.

Questo è sicuramente un compito interessante per grandi serie di numeri. Voglio solo fare un punto qui: un miliardo di noccioline. Quindi pensaci due volte prima di iniziare a lanciare soluzioni complesse in compiti sorprendentemente semplici;)


questo è quello che ho detto nella mia risposta qui :-) stackoverflow.com/a/31819222/363437
vidstige

1
@vidstige Onestamente non l'ho letto, ma hai ragione. la mia risposta è sicuramente più pratica, che la gente sembra apprezzare un po 'di più;)
sfussenegger

Questa non è la mediana, la mediana è (numbers[numbers.length / 2]+numbers[numbers.length / 2+1])/2se numbers.lengthè pari e numbers[numbers.length / 2]solo se numbers.lengthè dispari.
Sklivvz,

@Sklivvz è corretto, ma non si dovrebbe notare il tempo necessario per calcolare la mediana.
vidstige,

1
@Sklivvz hai ovviamente ragione. Ho appena aggiornato il calcolo mediano. Tuttavia, non cambia il resto della risposta.
sfussenegger,

10

La stima di statistiche d'ordine come mediana e 99 ° percentile può essere distribuita in modo efficiente con algoritmi come t-digest o Q-digest .

Utilizzando uno degli algoritmi, ciascun nodo produce un digest, che rappresenta la distribuzione dei valori memorizzati localmente. I digest vengono raccolti in un singolo nodo, uniti (sommando efficacemente le distribuzioni) e quindi è possibile cercare la mediana o qualsiasi altro percentile.

Questo approccio viene utilizzato da elasticsearch e, presumibilmente, da BigQuery (seguendo la descrizione della funzione QUANTILES).


5

La mediana per questo set di numeri

2, 3, 5, 7, 11, 13, 67, 71, 73, 79, 83, 89, 97

è 67.

La mediana per questo set di numeri

2, 3, 5, 7, 11, 13, 67, 71, 73, 79, 83, 89

è 40.

Supponendo che la domanda fosse circa 1.000.000.000 di numeri interi (x) dove 0> = x <= 2.147.483.647 e che il PO stava cercando (elemento (499.999.999) + elemento (500.000.000)) / 2 (se i numeri fossero ordinati). Supponendo anche che tutti i 100 computer fossero tutti uguali.

usando il mio laptop e GigE ...

Quello che ho scoperto è che il mio laptop è in grado di ordinare 10.000.000 di Int32 in 1,3 secondi. Quindi una stima approssimativa sarebbe che un ordinamento di un numero di miliardi richiederebbe 100 x 1,3 secondi (2 minuti e 10 secondi);).

Una stima di un trasferimento di file unidirezionale di un file da 40 MB su un Gigabit Ethernet è di .32 secondi. Ciò significa che i risultati ordinati da tutti i computer verranno restituiti in circa 32 secondi (il computer 99 non ha ricevuto il suo file fino a 30 secondi dopo l'avvio). Da lì non dovrebbe volerci molto per scartare i 499.999.998 numeri più bassi, aggiungere i successivi 2 e dividere per 2.


3
Giù commento degli elettori? Mi aiuterebbe a capire come posso fare di meglio.
dbasnett,

5
Non sono il votante verso il basso, ma l'ordinamento di un miliardo di numeri non richiederà 100 volte tanto quanto l'ordinamento di 10 milioni, perché la complessità peggiore dell'ordinamento di un elenco è O (n log n). L'ordinamento è anche più lento degli ordini di grandezza quando si esaurisce la memoria e si deve iniziare l'ordinamento su disco.
Richard Poole,

Penso che tu sia sulla buona strada; Se l'obiettivo è la risposta più rapida possibile una volta, l'ordinamento su più macchine potrebbe essere una buona idea. Ma se l'obiettivo è il tempo medio più basso, ogni macchina che esegue la propria ricerca ha più senso.
Charlie,

Supponendo che abbiano lo stesso fattore (che probabilmente non hanno a causa di problemi di memoria) allora a*(1e7)log(1e7) = 1.3sec=> a = 1.6e-9sec => a*(1e9)log(1e9) ~ 167sec, quindi la tua stima non era così.
bcorso,

Le tue stime sono troppo approssimative. In primo luogo, alcuni algoritmi di ordinamento vanno come o (n ^ 2) nel peggiore dei casi (ad es. Del quicksort comunemente usato). In secondo luogo, è stato scelto un set di dati di prova delle dimensioni della cache L2. Questo distorce i risultati. In terzo luogo tu (come molti altri risponditori) supponi che "numero" significhi "numero intero". Potrebbe significare float, double o decimal, che hanno caratteristiche prestazionali molto diverse.
Sklivvz,

5

Questo potrebbe sorprendere le persone, ma se i numeri sono numeri interi abbastanza piccoli da adattarsi all'interno di 32 bit (o più piccoli), basta fare una sorta di bucket! Richiede solo 16 GB di RAM per qualsiasi numero di ints a 32 bit e gira in O (n), il che dovrebbe superare qualsiasi sistema distribuito per un ragionevole n, ad esempio un miliardo.

Una volta che hai l'elenco ordinato, è banale scegliere la mediana. In effetti, non è necessario costruire l'elenco ordinato, ma solo guardando i secchi dovrebbe farlo.

Una semplice implementazione è mostrata di seguito. Funziona solo con numeri interi a 16 bit, ma l'estensione a 32 bit dovrebbe essere semplice.

#include <stdio.h>
#include <string.h>

int main()
{
    unsigned short buckets[65536];
    int input, n=0, count=0, i;

    // calculate buckets
    memset(buckets, 0, sizeof(buckets));
    while (scanf("%d", &input) != EOF)
    {
        buckets[input & 0xffff]++;
        n++;
    }

    // find median 
    while (count <= n/2)
    {
        count += buckets[i++];
    }

    printf("median: %d\n", i-1);

    return 0;
}

Utilizzando un file di testo con un miliardo (10 9 ) numeri e funzionando in questo timemodo

time ./median < billion

produce un tempo di esecuzione sulla mia macchina 1m49.293s. La maggior parte del tempo di esecuzione è probabilmente anche l'IO del disco.


Questo in realtà non risponde alla domanda e si basa su ipotesi. Ad esempio, non sai nemmeno che sono numeri interi.
Sklivvz,

In che modo non risponde alla domanda? E sì, la mia risposta presuppone che i numeri siano numeri interi. Ho cercato di affermare chiaramente i miei presupposti.
vidstige,

Non sembra affermare che avere numeri interi sia un presupposto, né ci si rivolge a come utilizzare i 100 computer di cui l'OP chiede. È possibile calcolare la mediana su un nodo ma questa non è la soluzione "migliore" a meno che non si mostri il perché. Inoltre, l'ordinamento radix non è o (n) se il numero di cifre varia, cosa che in questo caso certamente, secondo en.wikipedia.org/wiki/Radix_sort#Efficiency , è o (n log n)
Sklivvz

Comincio dicendo "se gli interi sono abbastanza piccoli da stare all'interno di un numero intero a 32 bit " ... L'ordinamento Radix è O (n) per una dimensione di parola costante w come descritto con grande chiarezza nel link che hai pubblicato. Qui presumo una parola costante di 32.
vidstige

1
Quello che fai con gli altri 99 computer non è rilevante in questa risposta. Potresti impilarli uno sopra l'altro per formare una piramide o bruciarli. O semplicemente ignorali.
vidstige,

3

Stranamente, penso che se hai abbastanza computer, stai meglio smistando che usare O(n)algoritmi di ricerca mediana. (A meno che i tuoi core non siano molto, molto lenti, tuttavia, ne O(n)userei uno e utilizzerei un algoritmo di ricerca mediana solo per numeri 1e9; se avessi 1e12, tuttavia, potrebbe essere meno pratico.)

Ad ogni modo, supponiamo di avere più di semplici log per affrontare questo problema e non ci preoccupiamo del consumo di energia, ma solo di ottenere rapidamente la risposta. Supponiamo inoltre che si tratti di una macchina SMP con tutti i dati già caricati in memoria. (Le macchine a 32 core di Sun sono di questo tipo, per esempio.)

Un thread taglia la lista in cieco in pezzi di uguali dimensioni e dice agli altri thread M di ordinarli. Quei thread lo fanno diligentemente, in (n/M) log (n/M)tempo. Restituiscono quindi non solo i loro mediani, ma, diciamo, anche i loro 25 e 75 percentili (i casi peggiori perversi sono migliori se si scelgono numeri leggermente diversi). Ora hai 4M intervalli di dati. Quindi si ordinano questi intervalli e si procede verso l'alto nell'elenco fino a quando non si trova un numero tale che, se si elimina ogni intervallo che è inferiore o contiene il numero, si sarà eliminato metà dei dati. Questo è il limite inferiore per la mediana. Fai lo stesso per il limite superiore. Questo richiede un po 'di M log Mtempo e tutti i core devono aspettarlo, quindi è davvero uno sprecoM^2 log Mtempo potenziale. Ora hai il tuo singolo thread che dice agli altri di lanciare tutti i dati al di fuori dell'intervallo (dovresti buttarne circa la metà ad ogni passaggio) e ripetere: questa è un'operazione banalmente veloce poiché i dati sono già ordinati. Non dovresti ripetere questo più log(n/M)volte prima che sia più veloce semplicemente catturare i dati rimanenti e utilizzare un O(n)cercatore mediano standard su di esso.

Quindi, la complessità totale è qualcosa di simile O((n/M) log (n/M) + M^2 log M log (n/M)). Quindi, questo è più veloce dell'ordinamento O(n)mediano su un core se M >> log(n/M)e M^3 log M < n, il che è vero per lo scenario che hai descritto.

Penso che sia una pessima idea data l'inefficienza, ma è più veloce.


o (n / M log (n / M)) è, letteralmente, o (n log n), perché o (n / M log (n / M)) = 1 / M o (n (log n - log M) ) = o (n registro n). Non puoi davvero confrontarlo con o (n) in questo modo, poiché "o" significa sostanzialmente "proporzionale a per grande n con una costante non specificata". Se non conosci queste costanti, non puoi confrontarle, tuttavia per N abbastanza grande le costanti non sono dominanti. Per numeri inferiori tutte le scommesse sono disattivate, o (1) può essere facilmente più lento di o (n!).
Sklivvz,

@Sklivvz - ne Msono le variabili che possono essere ridimensionate arbitrariamente, quindi si includono entrambe. In particolare, ho postulato che M> log n, nel senso che se ti importa che sia n log ninvece che solo n, devi preoccuparti Manche di.
Rex Kerr,

3

Questo può essere fatto più velocemente dell'algoritmo votato (n log n)

- Algoritmo di selezione distribuita statistiche ordine - O (n)
Semplifica il problema al problema originale di trovare il kth numero in un array non ordinato.
- Conteggio dell'istogramma di ordinamento O (n)
Devi assumere alcune proprietà sull'intervallo dei numeri - l'intervallo può rientrare nella memoria? - Ordinamento di tipo merge esterno - O (n log n) - descritto sopra
In pratica si ordinano i numeri al primo passaggio, quindi si trova la mediana al secondo.
- Se si sa qualcosa sulla distribuzione dei numeri, è possibile produrre altri algoritmi.

Per maggiori dettagli e implementazione consultare:
http://www.fusu.us/2013/07/median-in-large-set-across-1000-servers.html


2

Un computer è più che sufficiente per risolvere il problema.

Ma supponiamo che ci siano 100 computer. L'unica cosa complessa da fare è ordinare l'elenco. Dividilo in 100 parti, invia una parte a ciascun computer, lascia che siano ordinate lì e unisci le parti dopo.

Quindi prendere il numero dal centro dell'elenco ordinato (cioè con indice 5 000 000 000).


3
Comunque ora il mio rappresentante è piuttosto rotondo :)
Roman

La fusione è nella migliore delle ipotesi O (n) e puoi trovare la mediana su un singolo core in O (n), quindi questo sembra creare molto lavoro extra senza alcun guadagno.
Rex Kerr

2

Dipende dai tuoi dati. Lo scenario peggiore è che si tratta di numeri distribuiti uniformemente.

In questo caso puoi trovare la mediana nel tempo O (N) come in questo esempio:

Supponiamo che i tuoi numeri siano 2,7,5,10,1,6,4,4,6,10,4,7,1,8,4,9,9,3,4,3 (l'intervallo è 1-10) .

Creiamo 3 secchi: 1-3, 4-7, 8-10. Si noti che la parte superiore e inferiore hanno le stesse dimensioni.

Riempiamo i secchi con i numeri, contiamo quanti cadono in ciascuno, il massimo e il minimo

  • basso (5): 2,1,1,3,3, min 1, max 3
  • medio (10): 7,5,6,4,4,6,4,7,4,4, min 4, max 7
  • alto (5): 10, 10, 8, 9, 9, min 8, max 10

La media cade nel secchio di mezzo, ignoriamo il resto

Creiamo 3 bucket: 4, 5-6, 7. Low inizierà con un conteggio di 5 e con un massimo di 3 e con un minimo di 8 e un conteggio di 5.

Per ogni numero contiamo quanti cadono nel secchio basso e alto, il massimo e il minimo e manteniamo il secchio centrale.

  • vecchio basso (5)
  • basso (5): 4, 4, 4, 4, 4, max 4
  • medio (3): 5,6,6
  • alto (2): 7, 7, min 7
  • vecchio alto (5)

Ora possiamo calcolare direttamente la mediana: abbiamo una situazione come questa

old low    low          middle  high  old high
x x x x x  4 4 4 4 4 4   5 6 6  7 7   x x x x x

quindi la mediana è 4.5.

Supponendo che tu sappia qualcosa sulla distribuzione, puoi mettere a punto come definire gli intervalli per ottimizzare la velocità. In ogni caso, la prestazione dovrebbe andare con O (N), perché 1 + 1/3 + 1/9 ... = 1.5

Sono necessari min e max a causa dei casi limite (ad es. Se la mediana è la media tra il massimo del vecchio minimo e l'elemento successivo).

Tutte queste operazioni possono essere parallelizzate, puoi fornire 1/100 dei dati a ciascun computer e calcolare i 3 bucket in ciascun nodo, quindi distribuire il bucket che conservi. Questo ti consente di utilizzare la rete in modo efficiente perché ogni numero viene passato in media 1,5 volte (quindi O (N)). Puoi anche batterlo se passi solo i numeri minimi tra i nodi (ad esempio se il nodo 1 ha 100 numeri e il nodo 2 ha 150 numeri, allora il nodo 2 può dare 25 numeri al nodo 1).

A meno che tu non sappia di più sulla distribuzione, dubito che tu possa fare meglio di O (N) qui, perché in realtà devi contare gli elementi almeno una volta.


1
Non è davvero il caso peggiore (per il tuo algoritmo) quando tutti i numeri sono uguali? Se ho ragione, nessuno dei tuoi secchi verrà mai riempito a parte quello centrale, con tutti gli elementi. Pertanto, dovrai attraversare tutti gli elementi ogni volta, avanzando esponenzialmente velocemente al centro dell'intervallo. Credo che sarebbe un O(n log n)in quel caso. Ha senso ? A proposito, mi piace la tua idea
Dici,

1
@Dici non proprio: in primo luogo puoi facilmente abbreviare lo scenario "tutti uguali" perché conosci min e max. Come ho detto nella risposta, sapere che la distribuzione potrebbe guidare le tue scelte di bucket; in secondo luogo, ci vorrebbe ancora ciò o(n)+o(n/3)+o(n/9)+...che è fermo o(n)e non o(n log n).
Sklivvz,

D'altra parte, c'è probabilmente uno scenario peggiore diverso, una distribuzione a forma di U. Ho bisogno di pensarci un po ', formalizzare il caso peggiore, ma potrebbe fare peggio che o(n)in quel caso, con l'ingenuo partizionamento.
Sklivvz,

Mmm sì, il minimo e il massimo aiuterebbero a gestire il caso "tutti uguali" abbastanza facilmente
Dici,

2

Un metodo più semplice è avere numeri ponderati.

  • Dividi il set più grande tra i computer
  • Ordina ogni set
  • scorrere il set piccolo e calcolare i pesi su elementi ripetuti
  • unire ogni 2 set in 1 (ognuno è già ordinato) aggiornando i pesi
  • continua a unire i set fino a quando non ottieni solo un set
  • scorrere attraverso questo set accumulando pesi fino a raggiungere OneBillion / 2

1

Dividi i 10 ^ 9 numeri, 10 ^ 7 su ciascun computer ~ 80 MB su ciascuno. Ogni computer ordina i suoi numeri. Quindi il computer 1 unisce i propri numeri con quelli del computer 2, computer 3 e 4, ecc ... Quindi il computer 1 scrive la metà dei numeri su 2, 3 a 4, ecc. Quindi 1 unione ordina i numeri dai computer 1,2,3,4, le riscrive. E così via. A seconda della dimensione della RAM sui computer che potresti evitare di non riscrivere tutti i numeri sui singoli computer ad ogni passaggio, potresti essere in grado di accumulare i numeri sul computer 1 per diversi passaggi, ma fai i conti.

Oh, finalmente ottieni la media dei valori 500000000th e 500000001st (ma controlla che ci siano abbastanza 00 in là, non l'ho fatto).

EDIT: @Roman - beh, se non riesci a crederci anche se è vero, allora non ha senso rivelare la verità o la falsità della proposizione. Quello che volevo dire era che la forza bruta a volte batte in modo intelligente in una gara. Mi ci sono voluti circa 15 secondi per escogitare un algoritmo che sono fiducioso di poter implementare, che funzionerà e che sarà adattabile a una vasta gamma di dimensioni di input e numeri di computer e sintonizzabile con le caratteristiche dei computer e accordi di rete. Se impiegherai te, o chiunque altro, a dire 15 minuti per escogitare un algoritmo più sofisticato, ho un vantaggio di 14m45s per codificare la mia soluzione e avviarla in esecuzione.

Ma ammetto liberamente che è tutta un'affermazione, non ho misurato nulla.


qui stiamo solo fondendo tutti i numeri. Possiamo farlo in un modo migliore usando: - "possiamo trovare la mediana di due liste ordinate in tempo di accesso. N è la lunghezza di ogni lista."
anony

1
@anony - mentre rispondi alla tua domanda, farò codificare, testare e completare la mia soluzione. Mi aspetto che ci siano modi migliori, ma a volte parallelizzare un modo semplice mi lascia libero di grattarmi la testa per i problemi davvero difficili.
Mark High Performance

l'hai fatto davvero in 7 minuti? Non ci posso credere, anche se è vero. Ho svolto un compito simile (era un incarico universitario) e ci sono volute circa 2 ore per implementare e testare tutte le cose remote (ho usato Java RMI).
Roman

Capisco quello che stai dicendo, ma allo stesso modo DrPizza ha una soluzione ancora più veloce da pensare, che è quella di ordinare tutti i dati su un singolo nodo e ignorare gli altri 99. Nessuno di noi sa quanto siano costosi i dati il trasferimento dovrebbe essere considerato, quindi stiamo tutti cercando un compromesso che sembra vagamente plausibile. La tua soluzione trasferisce tutti i dati più volte, quindi sono un po 'sospettosa, ma è sicuramente una soluzione.
Steve Jessop,

'vagamente plausibile' - va bene per me @Steve! Soprattutto in risposta a una domanda vagamente non plausibile.
Contrassegno ad alte prestazioni

1

Questo potrebbe essere fatto sui nodi usando i dati che non sono ordinati tra i nodi (diciamo dai file di registro) nel modo seguente.

C'è 1 nodo padre e 99 nodi figlio. I nodi figlio hanno due chiamate API:

  • stats (): restituisce min, max e count
  • compare (median_guess): restituisce il conteggio del valore corrispondente, il conteggio inferiore al valore e il conteggio maggiore del valore

Il nodo padre chiama stats () su tutti i nodi figlio, annotando il minimo e il massimo di tutti i nodi.

Una ricerca binaria può ora essere condotta nel modo seguente:

  1. Bisecare il minimo e il massimo arrotondamento per difetto: questa è la "supposizione" mediana
  2. Se il maggiore di contare è maggiore di minore di contare, impostare il minimo sull'ipotesi
  3. Se il maggiore di contare è inferiore al minore di contare, impostare il massimo sull'ipotesi
  4. Se il conteggio è dispari, quando minimo e massimo sono uguali
  5. Se il conteggio termina anche quando maximum <= minimum + guess.match_count Questo potrebbe essere fatto su nodi usando dati non ordinati (diciamo dai file di registro) nel modo seguente.

C'è 1 nodo padre e 99 nodi figlio. I nodi figlio hanno due chiamate API:

  • stats (): restituisce min, max e count
  • compare (median_guess): restituisce il conteggio del valore corrispondente, il conteggio inferiore al valore e il conteggio maggiore del valore

Il nodo padre chiama stats () su tutti i nodi figlio, annotando il minimo e il massimo di tutti i nodi.

Una ricerca binaria può ora essere condotta nel modo seguente:

  1. Bisecare il minimo e il massimo arrotondamento per difetto: questa è la "supposizione" mediana
  2. Se il maggiore di contare è maggiore di minore di contare, impostare il minimo sull'ipotesi
  3. Se il maggiore di contare è inferiore al minore di contare, impostare il massimo sull'ipotesi
  4. Se il conteggio è dispari, quando minimo e massimo sono uguali
  5. Se il conteggio termina anche quando maximum <= minimo + guess.match_count

Se stats () e compare () potrebbero essere pre-calcolati con un ordinamento O (N / Mlogn / M), quindi un pre-calcolo O (N / M) con una complessità di memoria di O (N) per il pre calcolo. Quindi potresti confrontare () a tempo costante, quindi l'intera cosa (incluso il pre-calcolo) verrebbe eseguita in O (N / MlogN / M) + O (logN)

Fammi sapere se ho fatto un errore!


Sì, farei solo una ricerca binaria. Salverebbe la larghezza di banda della rete chiamando solo ogni computer alcune volte. Inoltre, ogni macchina potrebbe avere un "perno" in cui scambia i numeri su entrambi i lati del perno per risparmiare tempo. (il pivot sarebbe la precedente stima della mediana, quindi la prossima volta, sarà sufficiente esaminare tutti i numeri su un lato del pivot)
robert king

0

Che ne dici di questo: - ogni nodo può prendere 1 miliardo / 100 numeri. Su ciascun nodo è possibile ordinare gli elementi e trovare la mediana. Trova la mediana delle mediane. possiamo, aggregando i conteggi di numeri inferiori alla mediana della mediana su tutti i nodi scoprire x%: y% diviso che la mediana delle mediane fa. Ora chiedi a tutti i nodi di eliminare elementi inferiori alla mediana delle mediane (prendendo esempio del 30%: divisione del 70%). I numeri del 30% vengono eliminati. Il 70% di 1 miliardo è di 700 milioni. Ora tutti i nodi che hanno eliminato meno di 3 milioni di nodi possono inviare quei nodi extra a un computer principale. Il computer principale ridistribuisce in modo tale che ora tutti i nodi abbiano un numero quasi uguale di nodi (7 milioni). Ora che il problema si riduce a 700 milioni di numeri .... continua fino a quando non abbiamo un set più piccolo che può essere calcolato su un comp.


In sostanza, riduciamo sempre il problema posto di almeno il 30% e stiamo ottenendo molti calcoli paralleli attraverso questo. Ogni nodo inizia con 10 milioni e riduce il set di dati del 30% in ogni iterazione.
anony

Nella prima iterazione cerchiamo il numero 500Millionth. Nella seconda iterazione - se il numero di numeri cancellati è 300 milioni, cerchiamo il numero 200 milioni e così via ...
anony

2
Sembra che sia sulla strada giusta, ma non spieghi molto chiaramente come evitare di buttare via la mediana per caso con la tua divisione del 30% / 70%. Prendi il seguente controesempio: supponi che il tuo primo 29% sia tutto zeri e che tutti gli altri blocchi contino per 1000 e che ogni serie di blocchi sia uno in più rispetto all'ultimo. La mediana del 30 ° percentile eliminerà tutto il 29% dei dati e poco meno della metà del 61% dei dati, ovvero 29 + 30% = 59% dei dati. Spiacenti, abbiamo appena eliminato la vera mediana! Quindi apparentemente non intendi questo, o almeno lo intendi in modo più intelligente di quanto io abbia interpretato.
Rex Kerr

0

Scopriamo innanzitutto come trovare una mediana di n numeri su una singola macchina: sto fondamentalmente usando la strategia di partizionamento.

Problema: selezione (n, n / 2): trova n / 2 ° numero dal minimo numero.

Scegli l'elemento centrale k e i dati di partizione in 2 sotto array. il primo contiene tutti gli elementi <k e il secondo contiene tutti gli elementi> = k.

se sizeof (primo array secondario)> = n / 2, sai che questo array secondario contiene la mediana. È quindi possibile eseguire il lancio del secondo sotto-array. Risolvi la selezione di questo problema (dimensione del 1 ° sotto-array, n / 2) .

In caso contrario, eliminare questo primo sottoarray e risolvere la selezione (secondo sottoarray, n / 2 - sizeof (primo sottoarray))

Fallo ricorsivamente.

la complessità temporale è O (n) tempo previsto.

Ora, se abbiamo molte macchine, in ogni iterazione, dobbiamo elaborare un array per dividere, distribuiamo l'array in macchine diff. Ogni macchina elabora il proprio pezzo di array e rinvia il riepilogo all'hub che controlla la macchina, ovvero dimensione del 1 ° sottoarray e dimensione del 2 ° subarray. Le macchine hub sommano i riepiloghi e decidono quale sottoarray (1o o 2o) elaborare ulteriormente e il 2o parametro di selezione e lo rispedisce a ciascuna macchina. e così via.

Questo algoritmo può essere implementato in modo molto accurato utilizzando la mappa riduci?

Come sembra?


0

Penso che la risposta di Steve Jessop sarà la più veloce.

Se la dimensione del trasferimento dei dati di rete è il collo di bottiglia, ecco un altro approccio.

Divide the numbers into 100 computers (10 MB each). 
Loop until we have one element in each list     
    Find the meadian in each of them with quickselect which is O(N) and we are processing in parallel. The lists will be partitioned at the end wrt median.
    Send the medians to a central computer and find the median of medians. Then send the median back to each computer. 
    For each computer, if the overall median that we just computed is smaller than its median, continue in the lower part of the list (it is already partitioned), and if larger in the upper part.
When we have one number in each list, send them to the central computer and find and return the median.

32 MB ciascuno, intendi?
Dici,

Cosa intendi per continuare nella parte inferiore dell'elenco?
Ruthvik Vaila,

0

Lo farei così:

all'inizio tutti e 100 lavorano per trovare il numero più alto e più basso; ogni computer ha la sua parte del database / file che richiede;

quando vengono trovati i numeri più alti e più bassi, un computer legge i dati e distribuisce ogni numero, in modo uniforme, al resto dei 99; i numeri sono distribuiti a intervalli uguali; (uno può richiedere da -100 milioni a 0, un altro - da 0 a 100 milioni, ecc.);

Durante la ricezione dei numeri, ciascuno dei 99 computer li ordina già;

Quindi, è facile trovare la mediana ... Vedi quanti numeri ha ciascun computer, aggiungili tutti (la somma di quanti numeri ci sono, non i numeri stessi), dividi per 2; calcola in quale computer è il numero e in quale indice;

:) voilla

PS Sembra che ci sia molta confusione qui; il MEDIANO - è il NUMERO NEL CENTRO DI UN ELENCO ORDINATO DI NUMERI!



0

Se i numeri non sono distinti e appartengono solo a un determinato intervallo, ovvero vengono ripetuti, una semplice soluzione che mi viene in mente è quella di distribuire i numeri su 99 macchine equamente e mantenere una macchina come master. Ora ogni macchina scorre i propri numeri e memorizza il conteggio di ciascun numero in un set di hash. Ogni volta che il numero viene ripetuto nell'insieme di numeri assegnati a quel particolare computer, aggiorna il suo conteggio nell'insieme di hash.

Tutte le macchine quindi restituiscono il loro set di hash alla macchina principale. La macchina master combina i set di hash, sommando il conteggio della stessa chiave trovata in un set di hash. Ad esempio, il set di hash della macchina n. 1 aveva una voce di ("1", 7) e il set di hash della macchina n. 2 aveva una voce di ("1", 9), quindi la macchina master quando si pettinavano i set di hash fa una voce di ("1", 16) e così via.

Una volta che gli insiemi di hash sono stati uniti, ordina semplicemente le chiavi e ora puoi facilmente trovare l'elemento (n / 2) th e l'elemento (n + 2/2), dall'insieme di hash ordinato.

Questo metodo non sarà utile se i miliardi di numeri sono distinti.


0

Bene, supponiamo che tu sappia che il numero di interi distinti è (diciamo) 4 miliardi, quindi puoi raggrupparli in secchi da 64k e ottenere un conteggio distribuito per ciascun bucket da ogni macchina nel cluster (100 computer). Combina tutti questi conteggi. Ora, trova il bucket che ha la mediana e questa volta chiedi solo bucket per gli elementi 64k che si trovano nel bucket di destinazione. Ciò richiede O (1) (in particolare 2) query sul "cluster". : D


0

Vale la pena, dopo tutto ciò che è già stato sollevato da altri:

Trovare la mediana su una singola macchina è O (N): https://en.wikipedia.org/wiki/Selection_algorithm .

Anche l'invio di N numeri a 100 macchine è O (N). Quindi, per rendere interessante l'utilizzo di 100 macchine, o la comunicazione deve essere relativamente veloce, o N è così grande che una singola macchina non può gestirla mentre N / 100 è fattibile, o vogliamo solo considerare il problema matematico senza preoccuparci di comunicazione dei dati.

Per farla breve, presumo quindi che, entro limiti ragionevoli, possiamo inviare / distribuire i numeri senza influenzare l'analisi dell'efficienza.

Si consideri quindi il seguente approccio, in cui una macchina viene assegnata come "master" per alcune elaborazioni generali. Ciò sarà relativamente veloce, quindi anche il "master" partecipa alle attività comuni che ogni macchina esegue.

  1. Ogni macchina riceve N / 100 dei numeri, calcola la propria mediana e invia tali informazioni al master.
  2. Il master compila un elenco ordinato di tutte le mediane distinte e lo restituisce a ciascuna macchina, definendo una sequenza ordinata di bucket (su ogni macchina la stessa), una per ogni valore mediano (un bucket a valore singolo) e una per ogni intervallo tra mediane adiacenti. Naturalmente ci sono anche i bucket di fascia più bassa e più alta per valori al di sotto della mediana più bassa e al di sopra della più alta.
  3. Ogni macchina calcola quanti numeri cadono in ciascun bucket e comunica tali informazioni al master.
  4. Il master determina quale bucket contiene la mediana, quanti valori inferiori (in totale) scendono al di sotto di tale bucket e quanti sopra.
  5. Se il bucket selezionato è un bucket a valore singolo (una delle mediane) oppure il bucket selezionato contiene solo 1 (N dispari) o 2 (N pari) valori che abbiamo terminato. Altrimenti ripetiamo i passaggi precedenti con le seguenti (ovvie) modifiche:
  6. Solo i numeri dal bucket selezionato vengono (ri) distribuiti dal master alle 100 macchine e inoltre
  7. Non calcoleremo (su ogni macchina) la mediana, ma il valore k-esimo, in cui prendiamo in considerazione quanti numeri più alti sono stati scartati dal totale e quanti numeri più bassi. Concettualmente ogni macchina ha anche la sua parte dei numeri bassi / alti scartati e ne tiene conto quando calcola la nuova mediana nell'insieme che (concettualmente) include (la sua parte di) i numeri scartati.

Time-complessità:

  1. Un piccolo pensiero ti convincerà che ad ogni passaggio il numero totale di valori da analizzare è ridotto di un fattore almeno due (2 sarebbe un caso piuttosto malato; potresti aspettarti una riduzione significativamente migliore). Da questo otteniamo:
  2. Supponendo che trovare la mediana (o il valore k-esimo), che è O (N), impieghi c * N tempo in cui il prefattore c non varia troppo selvaggiamente con N in modo da poterlo prendere come costante per il momento, noi otterremo il nostro risultato finale al massimo 2 * c * N / 100 volte. L'uso di 100 macchine ci dà, quindi, un fattore di accelerazione di 100/2 (almeno).
  3. Come osservato inizialmente: il tempo impiegato per comunicare i numeri tra le macchine può rendere più attraente semplicemente fare tutto su una sola macchina. Tuttavia, se seguiamo l'approccio distribuito, il conteggio totale dei numeri da comunicare in tutte le fasi insieme non supererà 2 * N (N per la prima volta, <= N / 2 la seconda volta, <= metà di quella terzo, e così via).

-1
  1. Dividi il miliardo di numeri in 100 macchine. Ogni macchina avrà 10 ^ 7 numeri.

  2. Per ogni numero in arrivo su una macchina, memorizzare il numero in una mappa di frequenza, numero -> conteggio. Memorizza anche il numero minimo in ogni macchina.

  3. Trova mediana in ogni macchina: a partire dal numero minimo in ogni macchina, sommare i conteggi fino a raggiungere l'indice mediano. La mediana di ciascuna macchina sarà di ca. minore e maggiore di 5 * 10 ^ 6 numeri.

  4. Trova la mediana di tutte le mediane, che sarà minore e maggiore di ca. 50 * 10 ^ 7 numeri, che è la mediana di 1 miliardo di numeri.

Ora qualche ottimizzazione del secondo passaggio: invece di archiviare in una mappa di frequenza, memorizzare i conteggi in un array di bit variabile. Ad esempio: diciamo a partire dal numero minimo in una macchina, questi sono conteggi di frequenza:

[min number] - 8 count
[min+1 number] - 7 count
[min+2 number] - 5 count

Quanto sopra può essere archiviato in array di bit come:

[min number] - 10000000
[min+1 number] - 1000000
[min+2 number] - 10000

Si noti che complessivamente costerà circa 10 ^ 7 bit per ogni macchina, poiché ogni macchina gestisce solo 10 ^ 7 numeri. 10 ^ 7 bit = 1,25 * 10 ^ 6 byte, ovvero 1,25 MB

Quindi, con l'approccio di cui sopra, ogni macchina avrà bisogno di 1,25 MB di spazio per calcolare la mediana locale. E la mediana delle mediane può essere calcolata da quelle 100 mediane locali, risultando in una mediana di 1 miliardo di numeri.


Cosa succede se i numeri sono float?
Sklivvz,

-1

Suggerisco un metodo per calcolare approssimativamente la mediana. :) Se questi un miliardo di numeri sono in ordine casuale, penso di poter scegliere 1/100 o 1/10 di un miliardo di numeri in modo casuale, ordinarli con 100 macchine, quindi scegliere la mediana di essi. Oppure dividiamo miliardi di numeri in 100 parti, lasciamo che ogni macchina scelga 1/10 di ogni parte in modo casuale, calcolandone la mediana. Dopodiché abbiamo 100 numeri e possiamo calcolare più facilmente la mediana del numero 100. Solo un suggerimento, non sono sicuro che sia matematicamente corretto. Ma penso che tu possa mostrare il risultato a un manager non così bravo in matematica.


Ovviamente non è corretto, e ti consiglio vivamente di non dare per scontato che il tuo intervistatore sia uno stupido maiale che puoi ingannare
Dici

Haha ok, anche se non cambia il fatto che la tua risposta non è corretta. È molto facile dimostrarlo
Dici il

OK, dopo aver letto alcune lezioni sulla statistica, penso che l'idea di raccogliere 1/100 o anche 1/1000 in modo casuale di un miliardo di numeri e calcolare la loro mediana non sia poi così male. È solo un calcolo approssimativo.
pigro

-3

La risposta di Steve Jessop è sbagliata:

considerare i seguenti quattro gruppi:

{2, 4, 6, 8, 10}

{21, 21, 24, 26, 28}

{12, 14, 30, 32, 34}

{16, 18, 36, 38, 40}

La mediana è 21, che è contenuta nel secondo gruppo.

La mediana dei quattro gruppi è 6, 24, 30, 36, la mediana totale è 27.

Quindi dopo il primo ciclo, i quattro gruppi diventeranno:

{6, 8, 10}

{24, 26, 28}

{12, 14, 30}

{16, 18, 36}

Il 21 è già stato erroneamente scartato.

Questo algoritmo supporta il caso solo quando ci sono due gruppi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.