Mis disculpas por el retraso en este artículo. He estado muy ocupado, entre otras cosas, con los preparativos para una charla sobre Functional Data Explorer en Cumbre de descubrimiento del JMP . Parece que los contribuyentes habituales estaban igualmente preocupados, ya que solo recibí dos presentaciones de personas que no eran parte del personal para este desafío.
Aquí están los totales de puntos después del quinto desafío:
Hablando de Discovery Summit, mi colega Stan Koprowski está organizando la Cancelación de sesión de secuencias de comandos JMP . Siga el link para más información; Nos encantaría verte allí.
En el Desafío 5, te pedí que escribieras metafunciones, funciones de funciones. Mi motivación era tomar funciones que solo se pueden aplicar a un único número o cadena y extenderlas para que se puedan aplicar a sus valores compuestos, vectores y listas. He descubierto que este tipo de operación ocurre con cierta regularidad y, a menudo, se puede implementar con una fórmula de tabla de datos cuando aún no existe una función JSL. Quería tener esta funcionalidad disponible fuera de la tabla de datos.
Los puntos 1 y 2 deberían haber sido relativamente sencillos. Cuando se trabaja con vectores, es útil confiar en lo que JSL ya ha implementado a través de funciones JSL u operaciones matriciales preexistentes. El enfoque más simple para calcular la desviación absoluta mediana (MAD) es una línea de código:
Mediana(Abs( invec - Mediana( invec ) ) )
dónde invec es el vector de entrada. Esta solución depende de saber que la mediana se puede generar correctamente a partir de una función JSL incorporada. El código se deriva directamente de la definición de MAD.
Calcular la media recortada es un poco más complicado y ofrece una idea de la importancia de proporcionar una definición precisa de la función (más sobre esto más adelante). Un enfoque común fue calcular el promedio de los valores acotados entre los cuantiles asociados con pctBajo y pctAlto , dónde pctBajo y pctHola son los argumentos tal como se definen en la publicación del Desafío 5. Por ejemplo:
loQ = Cuantil( pctBajo , invec );
hiQ = Cuantil(1- pctHola , invec );
Significar( invec [Loc(loQ < invec < hiQ)]);
Esta solución, sin embargo, recorta no más que pctBajo de los valores bajos y no más que pctHola del alto valor. Debido a esto, el porcentaje total recortado puede ser menor que pcBajo + pctHola pero nunca más . Este enfoque es inconsistente con el encontrado en el Distribución plataforma que no sólo recorta por igual desde ambas colas sino que, lo que es más importante, garantiza que el porcentaje total recortado sea al menos la cantidad solicitada (y puede ser más). El siguiente código produce resultados consistentes con esta definición:
loRank = techo( pctLo *N Fila( invec ));
hiRank = Techo((1- pctHola )*N Fila( invec ));
goodVals = (loRank < Clasificación( invec ) <= alto rango);
buenosVals`* invec /Suma(buenosValores);
Para el elemento 3, estaba buscando una función que encontrara todas las apariciones de una cadena determinada en cada elemento de una lista de cadenas. Si la cadena dada fuera "a" y la lista de cadenas
{“calcaneoastragalar”,”zorro”,”bebé”}
el valor devuelto sería
{[2, 5, 9, 13, 15, 17],.,[2]}
Un ejemplo redactado correctamente habría ayudado a definir la función con mayor precisión. Mi solución con bucle:
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;
);
Lo que lleva entre 2 y 3 segundos procesar una lista de 1 millón de palabras seleccionadas al azar del diccionario en el Desafío 1.
Mi impulso para aplicarFunción Era replicar la funcionalidad que había usado en otros lenguajes de programación, donde podía aplicar (o asignar) una función a una estructura de datos agregados (generalmente una lista de cadenas o una matriz). Quería poder usar algo como:
Longitud( lista de cadenas )
para obtener las longitudes de cada elemento de la lista en lugar de utilizar una fórmula de bucle o columna.
Después de publicar el desafío, me di cuenta de que había sutilezas que no había considerado, como situaciones en las que la función se podía aplicar a elementos individuales de la lista de entrada o a toda la lista de entrada. Además, no dije nada sobre si se podrían aplicar diferentes argumentos de función a cada elemento de la lista.
Con esto en mente, y manteniendo la coherencia con los comentarios en la entrada del Desafío 5, la implementación de aplicarFunción He proporcionado toma cuatro argumentos:
lista en – una lista de uno o más números, cadenas, vectores o listas de cadenas.
fn – el nombre citado de la función a aplicar lista en . La función puede ser incorporada o proporcionada por el usuario.
listaarg – una lista de argumentos para fn que aparecen después lista en o una lista de listas de argumentos. Si se proporciona una lista de listas, debe tener la misma cardinalidad que lista en .
bandera - 1 si fn se aplica a cada elemento de la lista, 0 si se aplica a toda la lista.
Un enfoque simple pero general sería construir funciones usando expresiones y bucles. En pseudocódigo:
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
El código para este enfoque se puede encontrar en aplicarFunción (bucles).JSL . @David_Burnham ofrece un enfoque recursivo en desafío_5_burnham.JSL . Si bien es elegante (¿a quién no le gusta la recursividad?), encuentra problemas si la recursividad es demasiado profunda.
Intentando hacerlo aplicarFunción una función sin bucles resultó ser todo un desafío. El principal problema es no saber el número de artículos en listaarg a priori (o, aún más desafiante, lidiar con situaciones donde el número cambia).Al principio, esperaba poder lograr esto mediante el uso astuto de la evaluación de expresiones. Por ejemplo, si la función solo necesitara un argumento, lo siguiente funcionaría:
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 de Sustituto comienza generando una lista de longitud N artículos( lista en ) siendo cada elemento de la lista la expresión fn (lista en [i++], arg1 ) , dónde fn y arg1 se dan en las llamadas a funciones y listaEn[i++] es literal. Cuando Lista de evaluación es aplicado a lista externa , sin embargo, cada i se le asigna un valor secuencial separado y el elemento de la lista se evalúa, produciendo los resultados deseados. Aprendí este enfoque para usar expresiones de @Joseph Morgan quien, junto con @drewfoglia / @EvanMcCorkle Han brindado excelentes tutoriales de Discovery que cubren este tema. Los tutoriales se pueden encontrar. aquí y aquí .
Desafortunadamente, no pude descubrir cómo usar este enfoque por completo y tuve que confiar en una técnica que trato de evitar debido a su ineficiencia y falta de elegancia, construyendo una función general mediante la manipulación de cadenas y luego usando la evaluación de expresiones para obtener el resultado. . mi codigo esta en applyFunction (sin bucles).JSL . Si bien este código conserva gran parte del sabor de aplicarFunciónSimple , expresión regular se utiliza para eliminar las llaves de la expresión de listaarg . Afortunadamente, @Craige_Hales proporciona una solución puramente basada en expresiones en Craige 5.JSL . Dado que me lo enviaron antes de las aclaraciones posteriores del Desafío 5, se aplica fn a cada elemento en lista en solo. Además, se utiliza el mismo conjunto de argumentos para cada llamada a función. Tanto esta solución como la mía comparten el mismo problema de ser más lentas que nuestras soluciones basadas en bucles (entre 2 y 5 veces).
Con suerte, todo esto tiene sentido. Si no es así, o si tiene ideas adicionales, publique sus comentarios a continuación.
Solo agrego mi granito de arena aquí mientras estoy creando algunas funciones auxiliares de tipo programación funcional.
En mi experiencia, es muy fácil dejarse llevar y crear una solución demasiado complicada que es a la vez un poco lenta y un poco engorrosa de entender.
Por lo tanto, aquí hay una implementación rápida y fácil para map/filtrar/reducir que es casi tan rápida como los métodos integrados (ya sean bucles o llamadas integradas directas) y tiene el beneficio adicional de no requerir el uso de la función Expr(...) para utilizarlos.
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" )
);Cuando ejecuto esto en mi computadora portátil (bastante lenta), obtengo el siguiente resultado:
/*<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 />*/Tenga en cuenta que encontrar el número máximo en una lista de bits (50,000 entradas) usando la función de reducción solo tomó ~2 veces más tiempo que la llamada incorporada Max(), mientras que la suma de esa misma lista fue ~6 veces más lenta.Estos son números bastante buenos dada la flexibilidad adicional de las funciones Mapa/Reducir/Filtro.