Mi scuso per il ritardo in questo articolo. Sono stato molto impegnato, tra le altre cose, con i preparativi per una conferenza su Functional Data Explorer A Summit sulla scoperta di JMP . Sembra che i contributori abituali fossero ugualmente preoccupati, dato che ho ricevuto solo due proposte non appartenenti allo staff per questa sfida.
Ecco i punti totali dopo la quinta sfida:
A proposito del Discovery Summit, il mio collega Stan Koprowski ne sta organizzando uno Annullamento della sessione di scripting JMP . Segui il collegamento per maggiori dettagli; ci piacerebbe vederti lì.
Nella Sfida 5, ti ho chiesto di scrivere metafunzioni, funzioni di funzioni. La mia motivazione era quella di prendere funzioni che possono essere applicate solo a un singolo numero o stringa ed estenderle in modo che possano essere applicate ai loro valori compositi, vettori ed elenchi. Ho scoperto che questo tipo di operazione si verifica con una certa regolarità e spesso può essere implementato con una formula di tabella dati quando non esiste già una funzione JSL. Volevo avere questa funzionalità disponibile al di fuori della tabella dati.
I punti 1 e 2 avrebbero dovuto essere relativamente semplici. Quando si lavora con i vettori, è utile fare affidamento su ciò che JSL ha già implementato tramite funzioni JSL preesistenti o operazioni di matrice. L'approccio più semplice per calcolare la deviazione assoluta mediana (MAD) è l'unica riga di codice:
Mediana(Ass( inVec - Mediana( inVec ) ) )
Dove inVec è il vettore di input. Questa soluzione dipende dalla consapevolezza che la mediana può essere generata correttamente da una funzione JSL incorporata. Il codice segue direttamente dalla definizione di MAD.
Il calcolo della media troncata è un po' più complicato e offre informazioni sull'importanza di fornire una definizione precisa della funzione (ne parleremo più avanti). Un approccio comune era quello di calcolare la media dei valori delimitati tra i quantili associati pctLow E pctHigh , Dove pctLow E pctCiao sono gli argomenti definiti nel post Sfida 5. Per esempio:
loQ = Quantile( pctLow , inVec );
hiQ = Quantile(1- pctCiao , inVec );
Significare( inVec [Loc(loQ < inVec < hiQ)]);
Questa soluzione, però, rifila non più di pctLow dai valori bassi e non più di pctCiao dal valore elevato. Per questo motivo, la percentuale totale ritagliata potrebbe essere inferiore a pcLow + pctCiao ma mai più . Questo approccio non è coerente con quello trovato in Distribuzione piattaforma che non solo taglia equamente da entrambe le code ma, cosa ancora più importante, garantisce la percentuale totale di taglio almeno l'importo richiesto (e potrebbe essere superiore). Il codice seguente produce risultati coerenti con questa definizione:
loRango = Soffitto( pctLo *N riga( inVec ));
rango elevato = Soffitto((1- pctCiao )*N riga( inVec ));
goodVals = (loRank < Ranking( inVec ) <= rango elevato);
buonVals`* inVec /Somma(valoribuoni);
Per l'elemento 3, stavo cercando una funzione che trovasse tutte le occorrenze di una determinata stringa in ogni elemento di un elenco di stringhe. Se la stringa data fosse "a" e l'elenco delle stringhe
{“calcaneoastragalar”,”volpe”,”bambino”}
il valore restituito sarebbe
{[2, 5, 9, 13, 15, 17],.,[2]}
Un esempio formulato correttamente avrebbe aiutato a definire la funzione in modo più preciso. La mia soluzione con il looping:
listContains = Function({listIn,valueString},{i,outList,nextMat,start},
outList = Repeat({.},N Items(listIn));
For(i=1,i<=N Items(listIn),i++,
nextMat = [];
start = 1;
While(start = Contains(listIn[i],valueString,start),
nextMat = nextMat |/ start;
start+=Length(valueString);
);
If(N Row(nextMat) > 0,outList[i] = nextMat,outList[i]=.);
);
outList;
);
L'elaborazione di un elenco di 1 milione di parole selezionate casualmente dal dizionario nella Sfida 1 richiede dai 2 ai 3 secondi.
Il mio impulso per applyFunction era replicare la funzionalità che avevo usato in altri linguaggi di programmazione, dove potevo applicare (o mappare) una funzione su una struttura di dati aggregati (tipicamente un elenco di stringhe o una matrice). Volevo essere in grado di usare qualcosa come:
Lunghezza( elencoDiStringhe )
per ottenere la lunghezza di ciascun elemento nell'elenco anziché utilizzare una formula di ciclo o di colonna.
Dopo aver pubblicato la sfida, mi sono reso conto che c'erano sottigliezze che non avevo considerato, come situazioni in cui la funzione poteva essere applicata ai singoli elementi nell'elenco di input o all'intero elenco di input. Inoltre, non ho detto nulla sulla possibilità di applicare argomenti di funzione diversi a ciascun elemento della lista.
Tenendo presente questo, e rimanendo coerenti con i commenti nella voce della Sfida 5, l'implementazione di applyFunction Ho fornito richiede quattro argomenti:
elencoIn – un elenco di uno o più numeri, stringhe, vettori o elenchi di stringhe.
ecc – il nome tra virgolette della funzione a cui applicare elencoIn . La funzione può essere integrata o fornita dall'utente.
argList – un elenco di argomenti a ecc che compaiono dopo elencoIn o un elenco di elenchi di argomenti. Se viene fornito un elenco di elenchi, deve avere la stessa cardinalità di elencoIn .
bandiera - 1 se ecc viene applicato a ciascun elemento della lista, 0 se viene applicato all'intera lista.
Un approccio semplice ma generale sarebbe quello di creare le funzioni utilizzando espressioni e cicli. Nello pseudocodice:
If flag == 1 then
If argList is a list of lists then
For each item in listIn/argList, build an expression for fn
Else
For each item in listIn, build an expression for fn using the same items from argList
Else flag == 0
Build one expression for listIn and argList
Il codice per questo approccio può essere trovato in applyFunction (loop).JSL . @David_Burnham offre un approccio ricorsivo in challenge_5_burnham.JSL . Sebbene elegante (a chi non piace la ricorsione?) incontra problemi se la ricorsione va troppo in profondità.
Cercando di fare applyFunction una funzione senza ciclo si è rivelata una vera sfida. Il problema principale è non conoscere il numero di articoli contenuti argList a priori (o, cosa ancora più impegnativa, affrontare situazioni in cui il numero cambia).All'inizio speravo di riuscire a raggiungere questo obiettivo attraverso un uso astuto della valutazione delle espressioni. Ad esempio, se la funzione necessitasse di un solo argomento, funzionerebbe quanto segue:
applyFunctionSimple = Function({listIn,fn,argList},{i,fnExpr,outList},
i = 1;
outList = {};
outList[1::N Items(listIn)] = Substitute(
Expr(func(in,arg)),
Expr(func),Parse(fn),
Expr(in), Expr(listIn[i++]),
Expr(arg), argList[1]
);
Eval List(outList)
);
La magia di Sostituire inizia generando un elenco di lunghezza N elementi( elencoIn ) con ogni elemento dell'elenco che rappresenta l'espressione ecc (listaIn[i++], arg1 ) , Dove ecc E arg1 sono forniti nelle chiamate di funzione e elencoIn[i++] è letterale. Quando Elenco di valutazione viene applicato a outList , tuttavia, ciascuno io viene assegnato un valore sequenziale separato e l'elemento dell'elenco viene valutato, producendo i risultati desiderati. Ho imparato questo approccio all'uso delle espressioni da @Joseph Morgan chi, insieme a @drewfoglia / @EvanMcCorkle hanno fornito eccellenti tutorial Discovery su questo argomento. I tutorial possono essere trovati Qui E Qui .
Sfortunatamente, non sono riuscito a capire come utilizzare interamente questo approccio e ho dovuto fare affidamento su una tecnica che cerco di evitare a causa della sua inefficienza e ineleganza, costruendo una funzione generale tramite la manipolazione delle stringhe e quindi utilizzando la valutazione dell'espressione per ottenere l'output . Il mio codice è dentro applyFunction (nessun loop).JSL . Anche se questo codice conserva gran parte del sapore di applyFunctionSimple , Regex viene utilizzato per eliminare le parentesi graffe dall'espressione di argList . Fortunatamente, @Craige_Hales fornisce una soluzione puramente basata sull'espressione in Craige 5.JSL . Poiché mi è stato inviato prima dei chiarimenti successivi sulla Sfida 5, si applica ecc a ciascun elemento in elencoIn soltanto. Inoltre, per ogni chiamata di funzione viene utilizzato lo stesso insieme di argomenti. Sia questa soluzione che la mia condividono lo stesso problema di essere più lente delle nostre soluzioni basate su loop (tra 2-5X).
Si spera che tutto questo abbia un senso. In caso contrario, o se hai ulteriori approfondimenti, pubblica i tuoi commenti qui sotto.
Aggiungo solo i miei due centesimi qui mentre sto costruendo alcune funzioni di supporto di tipo programmazione funzionale.
Nella mia esperienza è troppo facile lasciarsi prendere la mano e costruire una soluzione eccessivamente complicata che è allo stesso tempo un po' lenta e un po' macchinosa da comprendere.
Pertanto, ecco un'implementazione rapida e semplice per map / filter / reduce che è veloce quasi quanto i metodi integrati (loop o chiamate integrate dirette) e ha l'ulteriore vantaggio di non richiedere l'uso della funzione Expr(...) per utilizzarli.
map = Function( {inputs/* list, function*/},
/* uses a single underscore _ as the wild-card */
{__i__, __result__, __list__, _},
__result__ = {};
__list__ = Arg( inputs, 1 );
Eval(
Substitute(
Expr(
For( __i__ = N Items( __list__ ), __i__ > 0, __i__--,
Insert Into( __result__, _ = __list__[__i__]; __function__, 1 )
)
)
,
Expr( __function__ ), Arg( inputs, 2 )
);
);
__result__
);
reduce = Function( {inputs/*list, function, seed = 0*/},
/* uses a single underscore for current item, 'acc' for current value */
{__i__, __list__, _, acc = 0},
If( N Items( inputs ) == 3,
acc = Arg( inputs, 3 )
);
__list__ = Arg( inputs, 1 );
Eval(
Substitute(
Expr(
For( __i__ = N Items( __list__ ), __i__ > 0, __i__--,
_ = __list__[__i__];
acc = __function__
)
)
,
Expr( __function__ ), Arg( inputs, 2 )
)
);
acc
);
filter = Function( {inputs/*list, filter = _*/},
/* uses a single underscore _ for current item */
{__i__, __list__, __result__, _filter_, _},
__result__ = {};
If( N Items( inputs ) == 1,
_filter_ = Expr( _ )
,
_filter_ = Arg( inputs, 2 )
);
__list__ = Arg( inputs, 1 );
Eval(
Substitute(
Expr(
For( __i__ = N Items( __list__ ), __i__ > 0, __i__--,
_ = __list__[__i__];
If( __filter__,
__result__[N Items(__result__) + 1] = _
)
)
)
,
Expr( __filter__ ), Name Expr( _filter_ )
);
);
Reverse( __result__ )
);
filter({{1, 0, 2, Empty(), 3, .}});
lst = repeat( {{1, "a"}, {2, "b"}, {3, "c"}, {4, "d"}}, 10000 );
start = HP Time();
a = filter({lst, _[1] < 3} );
end = HP Time();
Write( "\!NMy Method: ", (end - start) / 1000000 );
reduce( {{"a", "b", "c"}, Uppercase( _ || acc ), seed = ""} );
list = Repeat( {Random Integer( 1, 100000000 )}, 50000 );
start = HP Time();
Show( reduce( {list, If( _ > acc, _, acc)} ) );
end = HP Time();
Write( "\!NMy Method: ", (end - start) / 1000000 );
start = HP Time();
Show( Max( list ) );
end = HP Time();
Write( "\!NBuiltin Method: ", (end - start) / 1000000 );
start = HP Time();
Show( reduce( {list, acc + _} ) );
end = HP Time();
Write( "\!NMy Method: ", (end - start) / 1000000 );
start = HP Time();
Show( Sum( list ) );
end = HP Time();
Write( "\!NBuiltin Method: ", (end - start) / 1000000 );
strings = Repeat( {"abcdefg"}, 1e5 ); // about a second
start = Tick Seconds();
substrings1 = map( {strings, Substr( _, 1 + 2, 1 )} );
stop = Tick Seconds();
Write( "\!n applyfunction time: ", stop - start );
start = Tick Seconds();
substrings2 = {};
For( i = N Items( strings ), i > 0, i--,
Insert Into( substrings2, Substr( strings[i], 3, 1 ), 1 )
);
stop = Tick Seconds();
Write( "\!n JSL loop time: ", stop - start );
If( substrings1 == substrings2,
Write( "\!nOK" ),
Write( "\!noops" )
);Quando lo eseguo sul mio laptop (piuttosto lento), ottengo il seguente output:
/*<BR />My Method: 0.152577
reduce({list, If(_ > acc, _, acc)}) = 99994198;
My Method: 0.056897
Max(list) = 99994198;
Builtin Method: 0.024798
reduce({list, acc + _}) = 2495626010963;
My Method: 0.04259
Sum(list) = 2495626010963;
Builtin Method: 0.007765
applyfunction time: 0.166666666656965
JSL loop time: 0.0500000000029104
OK<BR />*/Si noti che trovare il numero massimo in un elenco di bit (50.000 voci) utilizzando la funzione di riduzione ha richiesto solo circa 2 volte il tempo della chiamata Max() incorporata, mentre la somma dello stesso elenco è stata circa 6 volte più lenta.Questi sono numeri piuttosto buoni data la maggiore flessibilità delle funzioni Mappa/Riduci/Filtro.