2011-12-30 11 views
9

Questo è un piccolo problema divertente, e volevo verificare con gli esperti qui se c'è un modo migliore/funzionale per affrontare Mathematica rispetto a quello che ho fatto. Non sono molto contento della mia soluzione poiché utilizzo il grande IF THEN ELSE ma non riesco a trovare un comando Mathematica da usare facilmente (come Select, Cases, Sow/Reap, Map .. ecc ...)Come sostituire ogni 0 con l'elemento precedente in una lista in modo idiomatico in Mathematica?

Ecco il problema, dati i valori di una lista (numeri o simboli), ma per semplicità, per ora assumiamo una lista di numeri. L'elenco può contenere zeri e l'obiettivo è sostituire ogni zero con l'elemento visto prima.

Alla fine, l'elenco non deve contenere zeri.

Ecco un esempio, dato

a = {1, 0, 0, -1, 0, 0, 5, 0}; 

il risultato dovrebbe essere

a = {1, 1, 1, -1, -1, -1, 5, 5} 

Si deve naturalmente essere fatto in modo più efficiente.

Questo è ciò che ho potuto venire con

Scan[(a[[#]] = If[a[[#]] == 0, a[[#-1]], a[[#]]]) &, Range[2, Length[a]]]; 

Volevo vedere se posso usare Sow/Reap su questo, ma non sapeva come fare.

domanda: può essere risolto in un modo più funzionale/matematico? Più corto è meglio naturalmente :)

aggiornamento 1 Grazie a tutti per la risposta, tutti sono molto buona per imparare. Questo è il risultato di test di velocità, su V 8.04, utilizzando finestre 7, 4 GB RAM, processore Intel 930 @ 2.8 Ghz:

Ho testato metodi da applicare per n da 100,000 a 4 million. Il metodo ReplaceRepeated non funziona bene per elenchi di grandi dimensioni.

aggiornamento 2

Rimosso risultato in precedenza che è stato mostrato in precedenza in Update1 a causa del mio errore nella copia di una delle prove.

I risultati aggiornati sono di seguito. Il metodo Leonid è il più veloce. Congratulazioni Leonid. Un metodo molto veloce.

enter image description here

Il programma di test è la seguente:

(*version 2.0 *) 
runTests[sizeOfList_?(IntegerQ[#] && Positive[#] &)] := 
Module[{tests, lst, result, nasser, daniel, heike, leonid, andrei, 
    sjoerd, i, names}, 

    nasser[lst_List] := Module[{a = lst}, 
    Scan[(a[[#]] = If[a[[#]] == 0, a[[# - 1]], a[[#]]]) &, 
    Range[2, Length[a]]] 
    ]; 

    daniel[lst_List] := Module[{replaceWithPrior}, 
    replaceWithPrior[ll_, n_: 0] := 
    Module[{prev}, Map[If[# == 0, prev, prev = #] &, ll] 
     ]; 
    replaceWithPrior[lst] 
    ]; 

    heike[lst_List] := Flatten[Accumulate /@ Split[lst, (#2 == 0) &]]; 

    andrei[lst_List] := Module[{x, y, z}, 
    ReplaceRepeated[lst, {x___, y_, 0, z___} :> {x, y, y, z}, 
    MaxIterations -> Infinity] 
    ]; 

    leonid[lst_List] := 
    FoldList[If[#2 == 0, #1, #2] &, [email protected]#, [email protected]#] & @lst; 

    sjoerd[lst_List] := 
    FixedPoint[(1 - Unitize[#]) RotateRight[#] + # &, lst]; 

    lst = RandomChoice[Join[ConstantArray[0, 10], Range[-1, 5]], 
    sizeOfList]; 
    tests = {nasser, daniel, heike, leonid, sjoerd}; 
    names = {"Nasser","Daniel", "Heike", "Leonid", "Sjoerd"}; 

    result = Table[0, {Length[tests]}, {2}]; 

    Do[ 
    result[[i, 1]] = names[[i]]; 

    Block[{j, r = Table[0, {5}]}, 
    Do[ 
    r[[j]] = [email protected][tests[[i]][lst]], {j, 1, 5} 
    ]; 
    result[[i, 2]] = Mean[r] 
    ], 

    {i, 1, Length[tests]} 
    ]; 

    result 
    ] 

Per eseguire i test per lunghezza 1000 il comando è:

Grid[runTests[1000], Frame -> All] 

Grazie a tutti per le risposte.

+3

Solo una nota che l'uso di "Se" è * non * non funziona. I condizionali sono una parte essenziale della programmazione funzionale e non richiedono effetti collaterali. Pensa a 'Se' come una funzione matematica che mappa i boolan (il set {True, False}) a qualcos'altro. Altrimenti mi è venuta in mente la stessa soluzione di Andrei, che ritengo sia la più semplice, ma sicuramente non la più veloce (quindi non la più pratica se si elaborano grandi dati!) – Szabolcs

+2

replaceWithPrior [ll_, n_: 0]: = Modulo [{ prev}, Mappa [Se [# == 0, prev, prev = #] &, ll]] In [12]: = replaceWithPrior [a] Out [12] = {1, 1, 1, -1 , -1, -1, 5, 5} –

+4

BTW cosa dovrebbe accadere se il primo elemento è 0? – Szabolcs

risposta

12

Gran parte (ordine di grandezza) più veloce di altre soluzioni ancora:

FoldList[If[#2 == 0, #1, #2] &, [email protected]#, [email protected]#] & 

L'accelerazione è dovuta alla compilazione automatica Fold. Non sarà così drammatico per gli array non imballati. Benchmark:

In[594]:= 
a=b=c=RandomChoice[Join[ConstantArray[0,10],Range[-1,5]],150000]; 
(b=Flatten[Accumulate/@Split[b,(#2==0)&]]);//Timing 
Scan[(a[[#]]=If[a[[#]]==0,a[[#-1]],a[[#]]])&,Range[2,Length[a]]]//Timing 
(c=FoldList[If[#2==0,#1,#2]&,[email protected]#,[email protected]#]&@c);//Timing 

SameQ[a,b,c] 

Out[595]= {0.187,Null} 
Out[596]= {0.625,Null} 
Out[597]= {0.016,Null} 
Out[598]= True 
6

La domanda sembra esattamente come un compito per la funzione ReplaceRepeated.Quello che fa in pratica è che applica lo stesso insieme di regole all'espressione finché non sono più applicabili. Nel tuo caso l'espressione è una lista e la regola è di sostituire 0 con il suo predecessore ogni volta che si verifica in una lista. Quindi, ecco la soluzione:

a = {1, 0, 0, -1, 0, 0, 5, 0}; 
a //. {x___, y_, 0, z___} -> {x, y, y, z}; 

Il modello per la regola qui è il seguente:

  • x___ - qualsiasi simbolo, zero o più ripetizioni, l'inizio della lista
  • y_ - esattamente un elemento prima dello zero
  • 0 - zero stesso, questo elemento verrà sostituito con y in seguito
  • z___ - qualsiasi simbolo, zero o più ripetizioni, alla fine della lista
+4

Mi spiace portarlo, ma sento che dovrei, dato che nessun altro ha ancora commentato. Questo sarà generalmente molto inefficiente e solo pratico per elenchi molto brevi. Il consiglio generale è di evitare "ReplaceRepeated" con pattern simili a quelli che hai usato (che coinvolgono spazi bianchi double e tripple). L'ho menzionato qui: http://stackoverflow.com/questions/4721171/performance-tuning-in-mathematica/4723969#4723969, come una delle trappole delle prestazioni. Un'altra nota è che dovresti aver usato "RuleDelayed" piuttosto che "Rule", per localizzare correttamente le tue variabili di pattern. –

+0

Ho trovato anche un limite predefinito nell'utilizzo di ReplaceRepeated. Quando lo eseguo su una lista grande, ottengo l'errore dal kernel 'ReplaceRepeated :: rrlim: Exiting after .... scansionato 65536 volte'. Poi ho scoperto che questo limite può essere rimosso usando l'opzione 'MaxIterazioni-> Infinito'. – Nasser

+0

@Leonid, grazie mille, non ha parlato delle prestazioni scadenti di "ReplaceRepeated". – Andrei

8

Questo sembra essere un fattore 4 più veloce sulla mia macchina:

a = Flatten[Accumulate /@ Split[a, (#2 == 0) &]] 

I tempi che ricevo sono

a = b = RandomChoice[Join[ConstantArray[0, 10], Range[-1, 5]], 10000]; 

(b = Flatten[Accumulate /@ Split[b, (#2 == 0) &]]); // Timing 

Scan[(a[[#]] = If[a[[#]] == 0, a[[# - 1]], a[[#]]]) &, 
    Range[2, Length[a]]] // Timing 

SameQ[a, b] 

(* {0.015815, Null} *) 
(* {0.061929, Null} *) 
(* True *) 
+0

Mi piace l'uso di 'Split' qui. Ho fatto qualcosa di simile, ma è più lento: 'Appiattisci [Mappa [ConstantArray [Primo [#], Lunghezza [#]] e, Dividi [a, # 2 == 0 &]]]' –

8
FixedPoint[(1 - Unitize[#]) RotateRight[#] + # &, d] 

è di circa 10 e 2 volte più veloce rispetto alle soluzioni di Heike ma più lento di Leonid di.

+0

Sei sicuro del un po 'più lento? In tutti i miei test finora, il tuo metodo è stato il più veloce. Pubblicherò il codice di test (n caso che ho fatto qualcosa di sbagliato) e risultati della tabella presto. Sto usando la v 8.04 su Windows 7 – Nasser

+0

@Sjoerd. L'efficienza sembra dipendere comunque dalla dimensione media di una serie di zeri (questo potrebbe sembrare errato). +1 –

+0

@LeonidShifrin, grazie per averlo notato. Ho copiato il tuo codice dalla parte superiore del tuo post, trascurato l'extra @ che hai usato qui sotto quando lo hai applicato. Questo è il motivo per cui ho pubblicato il mio codice di test per assicurarmi che sia stato esaminato per primo. Eseguirà nuovamente tutti i test e cancellerà la tabella dei risultati correnti e pubblicherà nuovi risultati in seguito. il test rapido mostra che ora è più veloce. Sto anche cercando di finire un altro metodo e lo aggiungerò al tavolo. Ma adesso devo andare a prendere del cibo prima che i negozi chiudano. – Nasser

Problemi correlati