Mes excuses pour le retard de cette rédaction. J'ai été très occupé, entre autres, par les préparatifs de une conférence sur Functional Data Explorer à Sommet découverte JMP . Il semble que les contributeurs réguliers étaient tout aussi préoccupés, car je n'ai reçu que deux candidatures de non-fonctionnaires pour ce défi.
Voici le total de points après le 5ème Défi :
En parlant du Discovery Summit, mon collègue Stan Koprowski organise le Annulation de session de script JMP . Suivez le lien pour plus de détails ; nous serions ravis de vous y voir.
Dans le défi 5, je vous ai demandé d'écrire des métafonctions, des fonctions de fonctions. Ma motivation était de prendre des fonctions qui ne peuvent être appliquées qu'à un seul nombre ou une seule chaîne et de les étendre afin qu'elles puissent être appliquées à leurs valeurs composites, vecteurs et listes. J'ai constaté que ce type d'opération se produit assez régulièrement et peut souvent être implémenté avec une formule de table de données lorsqu'une fonction JSL n'existe pas déjà. Je voulais que cette fonctionnalité soit disponible en dehors de la table de données.
Les éléments 1 et 2 auraient dû être relativement simples. Lorsque vous travaillez avec des vecteurs, il est utile de s'appuyer sur ce que JSL a déjà implémenté via des fonctions JSL ou des opérations matricielles préexistantes. L’approche la plus simple pour calculer l’écart médian absolu (MAD) consiste à utiliser une seule ligne de code :
Médiane(Abdos( inVec - Médiane ( inVec ) ) )
où inVec est le vecteur d'entrée. Cette solution dépend de la connaissance que la médiane peut être correctement générée à partir d'une fonction JSL intégrée. Le code découle directement de la définition de MAD.
Le calcul de la moyenne tronquée est un peu plus délicat et donne un aperçu de l'importance de fournir une définition précise de la fonction (nous y reviendrons plus tard). Une approche courante consistait à calculer la moyenne des valeurs délimitées entre les quantiles associés à PCTFaible et PCTÉlevé , où PCTFaible et PCTBonjour sont les arguments tels que définis dans le post du Défi 5. Par exemple:
loQ = Quantile( PCTFaible , inVec );
hiQ = Quantile(1- PCTBonjour , inVec );
Signifier( inVec [Loc(loQ < inVec < hiQ)]);
Cette solution réduit cependant pas plus que PCTFaible des valeurs faibles et pas plus que PCTBonjour de la valeur élevée. Pour cette raison, le pourcentage total de coupe peut être inférieur à PCFaible + PCTBonjour mais jamais plus . Cette approche est incompatible avec celle trouvée dans Distribution plate-forme qui non seulement coupe également les deux queues mais, plus important encore, garantit que le pourcentage total coupé est au moins le montant demandé (et peut être plus). Le code suivant produit des résultats cohérents avec cette définition :
loRank = Plafond ( pctLo *N rangée( inVec ));
hiRank = Plafond ((1- PCTBonjour )*N Ligne( inVec ));
goodVals = (loRank < Classement ( inVec ) <= hiRank);
bonnesVals`* inVec /Somme(goodVals);
Pour l'élément 3, je cherchais une fonction qui trouverait toutes les occurrences d'une chaîne donnée dans chaque élément d'une liste de chaînes. Si la chaîne donnée était « a » et la liste des chaînes
{"calcanéoastragalaire", "renard", "bébé"}
la valeur renvoyée serait
{[2, 5, 9, 13, 15, 17],.,[2]}
Un exemple correctement formulé aurait aidé à définir la fonction plus précisément. Ma solution avec le bouclage :
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;
);
Ce qui prend entre 2 et 3 secondes pour traiter une liste de 1 million de mots sélectionnés aléatoirement dans le dictionnaire du défi 1.
Mon élan pour appliquerFonction était de reproduire des fonctionnalités que j'avais utilisées dans d'autres langages de programmation, où je pouvais appliquer (ou mapper) une fonction sur une structure de données agrégée (généralement une liste de chaînes ou une matrice). Je voulais pouvoir utiliser quelque chose comme :
Longueur( listeDeChaînes )
pour obtenir les longueurs de chaque élément de la liste plutôt que d'utiliser une formule de boucle ou de colonne.
Après avoir publié le défi, j'ai réalisé qu'il y avait des subtilités que je n'avais pas prises en compte, telles que les situations dans lesquelles la fonction pouvait être appliquée aux éléments individuels de la liste d'entrée ou à l'ensemble de la liste d'entrée. De plus, je n'ai rien dit sur la question de savoir si différents arguments de fonction pouvaient être appliqués à chaque élément de la liste.
Dans cette optique, et en cohérence avec les commentaires de l'entrée Challenge 5, la mise en œuvre de appliquerFonction J'ai fourni quatre arguments :
listeDans – une liste d'un ou plusieurs nombres, chaînes, vecteurs ou listes de chaînes.
fn – le nom cité de la fonction à appliquer listeDans . La fonction peut être intégrée ou fournie par l'utilisateur.
argListe – une liste d’arguments pour fn qui apparaissent après listeDans ou une liste de listes d'arguments. Si une liste de listes est fournie, elle doit avoir la même cardinalité que listeDans .
drapeau - 1 si fn est appliqué à chaque élément de la liste, 0 s'il est appliqué à la liste entière.
Une approche simple mais générale consisterait à créer les fonctions à l’aide d’expressions et de boucles. En pseudocode :
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
Le code de cette approche peut être trouvé dans applyFunction (boucles).JSL . @David_Burnham propose une approche récursive dans challenge_5_burnham.JSL . Bien qu'élégant (qui n'aime pas la récursivité ?), il rencontre des problèmes si la récursivité va trop loin.
Essayer de faire appliquerFonction une fonction sans boucle s'est avérée être tout un défi. Le principal problème est de ne pas connaître le nombre d'éléments dans argListe a priori (ou, plus difficile encore, gérer des situations où le nombre change).Au début, j'espérais pouvoir y parvenir grâce à une utilisation astucieuse de l'évaluation des expressions. Par exemple, si la fonction n’avait besoin que d’un seul argument, ce qui suit fonctionnerait :
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 magie de Remplaçant commence par générer une liste de longueur N articles ( listeDans ) avec chaque élément de la liste étant l'expression fn (listIn[i++], arg1 ) , où fn et arg1 sont donnés dans les appels de fonction et listeDans[i++] est littéral. Quand Liste d'évaluation est appliqué à liste de sortie , cependant, chacun je reçoit une valeur séquentielle distincte et l'élément de liste est évalué, produisant les résultats souhaités. J'ai appris cette approche de l'utilisation des expressions de @Joseph Morgan qui, avec @drewfoglia / @EvanMcCorkle ont donné d'excellents tutoriels Discovery couvrant ce sujet. Les tutoriels peuvent être trouvés ici et ici .
Malheureusement, je n'ai pas réussi à comprendre entièrement comment utiliser cette approche et j'ai dû m'appuyer sur une technique que j'essaie d'éviter en raison de son inefficacité et de son inélégance, en construisant une fonction générale via la manipulation de chaînes puis en utilisant l'évaluation d'expression pour obtenir le résultat. . Mon code est dans applyFunction (pas de boucles).JSL . Bien que ce code conserve une grande partie de la saveur de applyFonctionSimple , Expression régulière est utilisé pour supprimer les accolades de l'expression de argListe . Heureusement, @Craige_Hales fournit une solution purement basée sur l'expression dans Craige 5.JSL . Puisqu'il m'a été envoyé avant les clarifications ultérieures du Défi 5, il s'applique fn à chaque élément de listeDans seulement. De plus, le même ensemble d’arguments est utilisé pour chaque appel de fonction. Cette solution et la mienne partagent le même problème d'être plus lentes que nos solutions basées sur des boucles (entre 2 et 5X).
Espérons que tout cela ait du sens. Si ce n'est pas le cas, ou si vous avez des informations supplémentaires, veuillez publier vos commentaires ci-dessous.
J'ajoute simplement mes deux cents ici alors que je construis des fonctions d'assistance de type programmation fonctionnelle.
D'après mon expérience, il est trop facile de se laisser emporter et de créer une solution trop compliquée, à la fois un peu lente et un peu lourde à comprendre.
Ainsi, voici une implémentation rapide et facile pour map/filter/reduce qui est presque aussi rapide que les méthodes intégrées (soit des boucles, soit des appels intégrés directs) et a l'avantage supplémentaire de ne pas nécessiter l'utilisation de la fonction Expr(...) pour les utiliser.
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" )
);Lorsque je l'exécute sur mon ordinateur portable (plutôt lent), j'obtiens le résultat suivant :
/*<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 />*/Notez que trouver le nombre maximum dans une liste de bits (50 000 entrées) à l'aide de la fonction de réduction n'a pris que ~2 fois plus de temps que l'appel Max() intégré, alors que la somme de cette même liste était ~6 fois plus lente.Ce sont de très bons chiffres compte tenu de la flexibilité supplémentaire des fonctions Map/Réduire/Filtre.