関数プログラミング型のヘルパー関数をいくつか構築しているので、ここに 2 セントを追加するだけです。
私の経験では、あまりにも簡単に夢中になって、時間がかかりすぎて理解するのが少し面倒な、過度に複雑なソリューションを構築してしまいがちです。
したがって、ここでは、組み込みメソッド (ループまたは直接組み込み呼び出し) とほぼ同じくらい高速で、Expr(...) 関数を使用する必要がないという追加の利点を持つ、map / filter / reduce の迅速かつ簡単な実装を示します。それらを活用するために。
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" )
);これを (かなり遅い) ラップトップで実行すると、次の出力が得られます。
/*<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 />*/Reduce 関数を使用してビット リスト (50,000 エントリ) 内の最大数を見つけるには、組み込みの Max() 呼び出しの約 2 倍の時間がかかるだけでしたが、同じリストの合計の場合は約 6 倍遅くなったことに注意してください。Map / Reduce / Filter 関数の柔軟性を考慮すると、これらはかなり良い数字です。
この書き込みが遅れたことをお詫びします。 の準備などでとても忙しかったです。 Functional Data Explorer に関する講演でJMP ディスカバリー サミット。 私がこのチャレンジにスタッフ以外からの投稿を受け取ったのは 2 件だけだったので、常連の投稿者たちも同様に夢中になっていたようです。
5 回目のチャレンジ後の合計ポイントは次のとおりです。
Discovery Summit について言えば、私の同僚の Stan Koprowski が主催しています。 JMPスクリプトアンセッション。 詳細については、リンクをクリックしてください。ぜひお会いしましょう。
課題 5 では、関数の関数であるメタ関数を書くように求めました。 私の動機は、単一の数値または文字列にのみ適用できる関数を取得し、それらを拡張して複合値、ベクトル、リストに適用できるようにすることでした。 このタイプの操作はある程度定期的に発生し、多くの場合、JSL 関数がまだ存在しない場合にデータ テーブル式を使用して実装できることがわかりました。 この機能をデータテーブルの外で利用できるようにしたいと考えていました。
項目 1 と 2 は比較的簡単なはずです。 ベクトルを扱う場合、既存の JSL 関数または行列演算を通じて JSL がすでに実装しているものに依存すると便利です。 中央絶対偏差 (MAD) を計算する最も簡単な方法は、次の 1 行のコードです。
中央値(絶対値(インベック- 中央値(インベック) ) )
どこインベックは入力ベクトルです。 この解決策は、組み込みの JSL 関数から中央値を正しく生成できることを知っているかどうかに依存します。 コードは MAD の定義から直接続きます。
トリミング平均の計算は少し難しく、正確な関数定義を提供することの重要性についての洞察が得られます (これについては後ほど詳しく説明します)。 一般的なアプローチは、関連する分位数間の境界値の平均を計算することでした。 pct低いそして高pct 、どこpct低いそしてpctこんにちはは、チャレンジ 5 の投稿で定義されている引数です。 例えば:
loQ = 分位数( pct低い、インベック);
hiQ = 分位数(1- pctこんにちは、インベック);
平均(インベック[Loc(loQ <インベック< hiQ)]);
ただし、このソリューションでは、に過ぎませんpct低い低い値から、そしてに過ぎませんpctこんにちは高い値から。 このため、トリミングされる合計パーセントは以下になる可能性があります。 pcLow + pctこんにちはしかし、それ以上は決してない。 このアプローチは、分布このプラットフォームは、両方の尾部から均等にトリミングするだけでなく、より重要なことに、トリミングされた合計パーセントが確実にトリミングされることを保証します。少なくとも要求された量 (場合によってはそれ以上)。 次のコードは、この定義と一致する結果を生成します。
loRank = 天井( pctLo *N行(インベック));
hiRank = 天井((1- pctこんにちは)*N 行(インベック));
GoodVals = (loRank < ランキング(インベック) <= hiRank);
良いヴァルス`*インベック/Sum(goodVals);
項目 3 については、文字列のリスト内のすべての項目で、指定された文字列が出現する箇所をすべて検索する関数を探していました。 指定された文字列が「a」と文字列のリストの場合
{「カルカネオアストラガラ」、「キツネ」、「ベイビー」}
返される値は次のようになります
{[2, 5, 9, 13, 15, 17],.,[2]}
正しく表現された例は、関数をより正確に定義するのに役立つでしょう。 ループを使用した私の解決策:
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;
);
チャレンジ 1 の辞書からランダムに選択された 100 万語のリストを処理するには、2 ~ 3 秒かかります。
私の原動力はapply関数他のプログラミング言語で使用していた機能を複製することで、集合データ構造 (通常は文字列または行列のリスト) に関数を適用 (またはマッピング) することができました。 次のようなものを使用できるようにしたいと思いました。
長さ(文字列のリスト)
ループや列の式を使用するのではなく、リスト内の各項目の長さを取得します。
チャレンジを投稿した後、関数が入力リスト内の個々の項目または入力リスト全体に適用できる状況など、考慮していなかった微妙な点があることに気付きました。 さらに、異なる関数引数を各リスト要素に適用できるかどうかについては何も述べませんでした。
これを念頭に置き、チャレンジ 5 エントリのコメントと一貫性を保ちながら、 apply関数私は 4 つの引数を指定しました。
リストイン– 1 つ以上の数値、文字列、ベクトル、または文字列のリストのリスト。
ふん– 適用する関数の引用符付きの名前リストイン。 この関数は組み込みまたはユーザーが提供することができます。
引数リスト– 引数のリストふん後に現れるリストインまたは引数のリストのリスト。 リストのリストが指定される場合、そのリストは次と同じカーディナリティを持つ必要があります。リストイン。
フラグ- 1 の場合ふんはリストの各要素に適用され、リスト全体に適用される場合は 0 になります。
シンプルだが一般的なアプローチは、式とループを使用して関数を構築することです。 疑似コードでは:
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
このアプローチのコードは次の場所にあります。 applyFunction (ループ).JSL 。 @David_Burnhamで再帰的アプローチを提供しますチャレンジ_5_バーナム.JSL 。 エレガントではありますが (再帰が嫌いな人がいるでしょうか?)、再帰が深すぎると問題が発生します。
作ろうとしているapply関数ループレス関数は非常に難しいことが判明しました。 主な問題は、アイテムの数がわからないことです。引数リスト先験的(あるいは、さらに困難なのは、数値が変化する状況に対処すること)。当初、私は式の評価を巧妙に使用することでこれを達成できると期待していました。 たとえば、関数に必要な引数が 1 つだけの場合は、次のように機能します。
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)
);
の魔法代わりの長さのリストを生成することから始まりますN アイテム(リストイン)リストの各要素が式になりますふん(リスト[i++],引数1 ) 、 どこふんそして引数1関数呼び出しで指定され、リストイン[i++]文字通りです。 いつ評価リストに適用されますアウトリストただし、それぞれ私には個別の連続値が与えられ、リスト項目が評価されて、望ましい結果が得られます。 私はこの表現の使い方のアプローチを次から学びました。 @ジョセフ・モーガン誰と一緒に@drewfoglia / @EvanMcorkleは、このトピックをカバーする優れた Discovery チュートリアルを提供しました。 チュートリアルが見つかりますここそしてここ。
残念ながら、私はこのアプローチの使い方を完全に理解できず、文字列操作によって一般的な関数を構築し、式の評価を使用して出力を取得するという非効率性とエレガントさの理由で避けようとしている手法に頼らざるを得ませんでした。 。 私のコードは次のとおりですapplyFunction (ループなし).JSL 。 このコードには、 applyFunctionシンプル、正規表現の式から中括弧を取り除くために使用されます。引数リスト。 幸いなことに、 @Craige_Hales純粋に式ベースのソリューションを提供しますクレイジ 5.JSL 。 後のチャレンジ 5 の説明の前に送信されたため、適用されますふんの各要素にリストインのみ。 さらに、すべての関数呼び出しに同じ引数のセットが使用されます。 このソリューションと私のソリューションはどちらも、ループベースのソリューションよりも遅い (2 ~ 5 倍) という同じ問題を共有しています。
これがすべて意味があることを願っています。 そうでない場合、または追加の洞察がある場合は、以下にコメントを投稿してください。