2012-01-09 10 views
14

Ho esaminato i modi per controllare gli argomenti delle funzioni. Ho notato che MatrixQ accetta 2 argomenti, il secondo è un test da applicare a ciascun elemento.Qual è il modo consigliato per verificare che una lista sia una lista di numeri in argomento di una funzione?

Ma ListQ accetta solo un argomento. (anche per qualche ragione, ?ListQ non ha una pagina di aiuto, come fa ?MatrixQ).

Così, per esempio, per controllare che un argomento a una funzione è una matrice di numeri, scrivo

ClearAll[foo] 
foo[a_?(MatrixQ[#, NumberQ] &)] := Module[{}, a + 1] 

quello che sarebbe un buon modo per fare lo stesso per una lista? Questo di seguito solo controlla che l'ingresso è un elenco

ClearAll[foo] 
foo[a_?(ListQ[#] &)] := Module[{}, a + 1] 

avrei potuto fare qualcosa di simile:

ClearAll[foo] 
foo[a_?(ListQ[#] && (And @@ Map[NumberQ[#] &, # ]) &)] := Module[{}, a + 1] 

in modo che foo[{1, 2, 3}] funzionerà, ma non si foo[{1, 2, x}] (supponendo x è un simbolo). Ma mi sembra di essere un modo complicato per farlo.

Domanda: Conoscete un modo migliore per controllare che un argomento è una lista e controllare anche il contenuto elenco per essere numeri

ed un relativo (o di qualsiasi altro capo noto a Mathematica?) domanda: Eventuali problemi di prestazioni importanti in fase di esecuzione con l'aggiunta di tali controlli a ciascun argomento? In tal caso, si consiglia di rimuovere questi controlli dopo aver completato i test e completato lo sviluppo in modo che il programma finale venga eseguito più rapidamente? (ad esempio, avere una versione del codice con tutti i controlli in, per lo sviluppo/test e una versione senza produzione).

+0

anche io a volte mi trovo chiedendo circa il calo di prestazioni di questo, * + 1 * per quella domanda – Szabolcs

+0

Devo fare la parte domanda performance come una domanda separata o semplicemente lasciare qui ? – Nasser

+0

Lo lascerei qui. Li ho evidenziati in grassetto perché penso che sia molto più facile leggere e * notare * in questo modo. Altrimenti molte persone non leggeranno mai così lontano. Se non accetti la mia risposta, la domanda attirerà più punti di vista. – Szabolcs

risposta

11

Per quanto riguarda il calo di prestazioni (da quando la prima questione è stata risolta già) - con tutti i mezzi, fare i controlli , ma nelle funzioni di primo livello (che ricevono i dati direttamente dall'utente della tua funzionalità. L'utente può anche essere un altro modulo indipendente, scritto da te o da qualcun altro). Non mettere questi controlli in tutte le tue funzioni intermedie, dal momento che tali controlli saranno duplicati e in effetti ingiustificati.

EDIT

Per affrontare il problema degli errori nelle funzioni intermedie, cresciuto da @Nasser nei commenti: v'è una tecnica molto semplice che permette di passare modello controlli e spegnimento in "one click ". È possibile memorizzare i modelli in variabili all'interno del pacchetto, definite prima delle definizioni delle funzioni.

Ecco un esempio, dove f è una funzione di primo livello, mentre g e h sono "funzioni interne". Definiamo due modelli: per la funzione principale e per quelle interne, in questo modo:

Clear[nlPatt,innerNLPatt ]; 
nlPatt= _?(!VectorQ[#,NumericQ]&); 
innerNLPatt = nlPatt; 

Ora, definiamo le nostre funzioni:

ClearAll[f,g,h]; 
f[vector:nlPatt]:=g[vector]+h[vector]; 
g[nv:innerNLPatt ]:=nv^2; 
h[nv:innerNLPatt ]:=nv^3; 

Nota che i modelli vengono sostituiti dentro le definizioni alla definizione tempo, non in fase di esecuzione, quindi questo è esattamente equivalente alla codifica di quei pattern a mano.Una volta che si prova, basta cambiare una sola riga: da

innerNLPatt = nlPatt 

a

innerNLPatt = _ 

e ricaricare il pacchetto.

Un'ultima domanda è: come si trovano rapidamente gli errori? Ho risposto a quello here, nelle sezioni "Invece di restituire $Failed, si può generare un'eccezione, utilizzando Throw." e "Meta-programmazione e automazione".

FINE EDIT

ho inserito una breve discussione di questo problema nel mio libro here. In questo esempio, il successo della performance era al livello del 10% di aumento del tempo di esecuzione, che IMO è al limite accettabile. Nel caso in esame, il controllo è più semplice e la penalizzazione delle prestazioni è molto inferiore. In genere, per una funzione che è qualsiasi controllo di tipo intensivo a livello di calcolo, correttamente scritto costa solo una piccola frazione del tempo di esecuzione totale.

Alcuni trucchi che sono bene sapere:

  • Pattern-matcher può essere molto veloce, se usato sintatticamente (senza Condition o PatternTest presente nel modello).

Ad esempio:

randomString[]:[email protected][{97,122},5]; 
rstest = Table[randomString[],{1000000}]; 

In[102]:= MatchQ[rstest,{__String}]//Timing 
Out[102]= {0.047,True} 

In[103]:= MatchQ[rstest,{__?StringQ}]//Timing 
Out[103]= {0.234,True} 

Solo perché in quest'ultimo caso il PatternTest stato utilizzato, il controllo è molto più lento, perché valutatore viene richiamato dal pattern matcher per ogni elemento, mentre nella prima caso, tutto è puramente sintattico e tutto viene fatto all'interno del pattern-matcher.


  • Lo stesso vale per imballati elenchi numerici (la differenza di temporizzazione è simile). Tuttavia, per gli elenchi numerici, MatchQ e altre funzioni di verifica del modello non vengono decompressi per determinati motivi speciali, inoltre, per alcuni di essi il controllo è istantaneo.

Ecco un esempio:

In[113]:= 
test = RandomInteger[100000,1000000]; 

In[114]:= MatchQ[test,{__?IntegerQ}]//Timing 
Out[114]= {0.203,True} 

In[115]:= MatchQ[test,{__Integer}]//Timing 
Out[115]= {0.,True} 

In[116]:= Do[MatchQ[test,{__Integer}],{1000}]//Timing 
Out[116]= {0.,Null} 

Lo stesso, a quanto pare, sembra essere vero per le funzioni come VectorQ, MatrixQ e ArrayQ con alcuni predicati (NumericQ) - questi test sono estremamente efficienti.


  • Molto dipende da come si scrive il test, vale a dire fino a che punto si riutilizza le strutture Mathematica efficienti.

Per esempio, vogliamo mettere alla prova che abbiamo una vera e propria matrice numerica:

In[143]:= rm = RandomInteger[10000,{1500,1500}]; 

Ecco il modo più straight-forward e lento:

In[144]:= MatrixQ[rm,NumericQ[#]&&Im[#]==0&]//Timing 
Out[144]= {4.125,True} 

Questo è meglio, dal momento che riutilizziamo meglio il pattern-matcher:

In[145]:= MatrixQ[rm,NumericQ]&&FreeQ[rm,Complex]//Timing 
Out[145]= {0.204,True} 

Non abbiamo utilizzare tuttavia la natura imballata della matrice. Questo è ancora meglio:

In[146]:= MatrixQ[rm,NumericQ]&&Total[Abs[Flatten[Im[rm]]]]==0//Timing 
Out[146]= {0.047,True} 

Tuttavia, questa non è la fine. Il successivo è quasi istantaneo:

In[147]:= MatrixQ[rm,NumericQ]&&Re[rm]==rm//Timing 
Out[147]= {0.,True} 
+0

Se ti capisco, non è possibile tuttavia che eseguo i controlli solo nella parte superiore della funzione, ma quando effettuo una chiamata alle mie funzioni di livello inferiore, da quella funzione di livello superiore, un argomento con corrispondenza errata (a causa di bug di logica) viene utilizzato nella chiamata da qualche parte nel flusso? Posso vedere me stesso, durante lo sviluppo, cambiare il codice e passare il parametro sbagliato, e senza questi controlli dei parametri su ogni funzione che uso, potrei trascurare l'errore? Grazie – Nasser

+0

@Nasser ho indirizzato la tua domanda nella mia modifica, per favore dai un'occhiata –

13

È possibile utilizzare VectorQ in un modo completamente analogo a MatrixQ. Ad esempio,

f[vector_ /; VectorQ[vector, NumericQ]] := ... 

noti inoltre due differenze tra VectorQ e ListQ:

  1. Una pianura VectorQ (senza secondo argomento) dà solo vero se non elementi della lista sono liste stesse (cioè solo per strutture 1D)

  2. VectorQ gestirà SparseArray s mentre ListQ no


io non sono sicuro circa l'impatto sulle prestazioni di utilizzo di questi, in pratica, sono molto curioso di sapere anch'io.

Ecco un punto di riferimento ingenuo. Sto confrontando due funzioni: una che controlla solo gli argomenti, ma non fa nulla, e una che aggiunge due vettori (questa è un'operazione molto veloce incorporata, cioè qualsiasi cosa più veloce di questa potrebbe essere considerata trascurabile). Sto usando NumericQ che è un controllo più complesso (quindi potenzialmente più lento) rispetto a NumberQ.

In[2]:= add[a_ /; VectorQ[a, NumericQ], b_ /; VectorQ[b, NumericQ]] := 
    a + b 

In[3]:= nothing[a_ /; VectorQ[a, NumericQ], 
    b_ /; VectorQ[b, NumericQ]] := Null 

Matrice imballata. Si può verificare che il controllo sia costante (non mostrato qui).

In[4]:= rr = RandomReal[1, 10000000]; 

In[5]:= Do[add[rr, rr], {10}]; // Timing 

Out[5]= {1.906, Null} 

In[6]:= Do[nothing[rr, rr], {10}]; // Timing 

Out[6]= {0., Null} 

Matrice non compressa omogenea. Il controllo è lineare, ma molto veloce.

In[7]:= rr2 = Developer`[email protected][10000, 1000000]; 

In[8]:= Do[add[rr2, rr2], {10}]; // Timing 

Out[8]= {1.75, Null} 

In[9]:= Do[nothing[rr2, rr2], {10}]; // Timing 

Out[9]= {0.204, Null} 

Matrice non imballata non omogenea. Il controllo prende lo stesso tempo dell'esempio precedente.

In[10]:= rr3 = Join[rr2, {Pi, 1.0}]; 

In[11]:= Do[add[rr3, rr3], {10}]; // Timing 

Out[11]= {5.625, Null} 

In[12]:= Do[nothing[rr3, rr3], {10}]; // Timing 

Out[12]= {0.282, Null} 

Conclusione basato su questo semplice esempio:

  1. VectorQ è altamente ottimizzato, almeno quando si usa il secondo argomento comuni. È molto più veloce di ad es. aggiungendo due vettori, che a sua volta è un'operazione ben ottimizzata.
  2. Per gli array imballati VectorQ è un tempo costante.

@Leonid's answer è molto rilevante, si prega di vederlo.

+1

grazie, non sapeva di 'VectorQ'. Dovrei prima digitare '? * Q' per vedere tutte le funzioni di tipo' Q'. Ne vedo così tanti ora. – Nasser

3

Dal ListQ solo controlla che la testa è List, il seguente è una soluzione semplice:

foo[a:{___?NumberQ}] := Module[{}, a + 1] 
+2

Mi sono appena reso conto di una carenza di ciò quando ho letto la tua risposta "Dato che' ListQ' controlla che la testina sia 'List'": sia questa che qualsiasi soluzione 'ListQ' non funzioneranno con' SparseArray's. 'VectorQ' sarà. – Szabolcs

Problemi correlati