Polygon
2次元の情報point
、行ベクトル、その他の変数がスカラーである場合、これが新しい関数の最初のバージョンです(下にスクロールして、この猫のスキンを作成する方法がたくさんあることを確認してください)。
function [result] = newHitTest (point,Polygon,r,tol,stepSize)
result = 0;
linDiff = Polygon-repmat(point,size(Polygon,1),1);
testLogicals = sqrt( sum( ( linDiff ).^2 ,2 )) < tol*r;
if any(testLogicals); result = circleTest (point,Polygon,r,tol,stepSize); end
Matlabでのベクトル化の思考プロセスには、単一のコマンドを使用して可能な限り多くのデータを操作しようとすることが含まれます。基本的な組み込みのMatlab関数のほとんどは、多次元データに対して非常に効率的に動作します。ループの使用for
はこれとは逆です。データを処理のために小さなセグメントに分割し、それぞれを個別に解釈する必要があるためです。ループを使用したデータ分解に頼ることによりfor
、Matlab組み込み関数の背後にある高度に最適化されたコードに関連するパフォーマンス上の大きな利点の一部を失う可能性があります。
この例で最初に考えることは、メインループの条件付きブレークです。ベクトル化されたプロセスから抜け出すことはできません。代わりに、すべての可能性を計算し、データの各行の結果の配列を作成してから、キーワードを使用して、関数を呼び出す必要があるany
ことを示す行があるかどうかを確認します。circleTest
注:Matlabでの計算を条件付きで効率的に分割することは簡単ではありません。ただし、ループ内のユークリッド距離の形式を計算しているだけなので、ベクトル化されたバージョンを使用してすべての可能性を計算することで、パフォーマンスが向上する可能性があります。ループ内の計算がより高価で、入力データが大きく、特定の条件に達したらすぐにブレークアウトしたい場合、コンパイルされた言語で作成されたmatlab拡張機能は、ベクトル化されたバージョンよりもはるかに高速になる可能性があります。不必要な計算を実行している可能性があります。ただし、これは、ネイティブコードにコンパイルされる言語でMatlabビルトインのパフォーマンスに一致するコードをプログラムする方法を知っていることを前提としています。
トピックに戻る...
最初に行うことは、と行ベクトルlinDiff
の間の線形差(コード例)を取得することです。これをベクトル化して行うには、2つの変数の次元が同一である必要があります。これを実現する1つの方法は、の各行をコピーして、と同じサイズにすることです。ただし、通常はrepmatの優れた代替手段であり(この最近のSOの質問で説明されているように)、コードを作成します...Polygon
point
repmat
point
Polygon
bsxfun
function [result] = newHitTest (point,Polygon,r,tol,stepSize)
result = 0;
linDiff = bsxfun(@minus, Polygon, point);
testLogicals = sqrt( sum( ( linDiff ).^2 ,2 )) < tol*r;
if any(testLogicals); result = circleTest (point,Polygon,r,tol,stepSize); end
2番目の軸で合計することにより、d
値をの列にロールインしました(コマンドからの配列インデックスの削除とコマンドへの追加に注意してください)。次に、さらに進んで、距離測度の計算に沿って論理配列を評価しました。重いベクトル化の欠点は、Matlabに慣れていない人にとってコードが読みにくくなる可能性があることですが、パフォーマンスの向上はそれだけの価値があることがすぐにわかります。コメントはかなり必要です。d
Polygon
,2
sum
testLogicals
さて、完全に夢中になりたいのであれば、テスト関数は非常に単純なので、完全な関数定義ではなく、「無名関数」または「ラムダ」の使用が必要であると主張することができます。実行する価値があるかどうかのテストにも引数circleTest
は必要ありませんstepSize
。これは、おそらく無名関数を使用するもう1つの理由です。テストを無名関数にロールしてからcircleTest
、呼び出し元のスクリプトで使用して、コードをある程度自己文書化することができます。。。
doCircleTest = @(point,Polygon,r,tol) any(sqrt( sum( bsxfun(@minus, Polygon, point).^2, 2 )) < tol*r);
if doCircleTest(point,Polygon,r,tol)
result = circleTest (point,Polygon,r,tol,stepSize);
else
result = 0;
end
これですべてがベクトル化されました。関数ハンドルを使用すると、別のアイデアが得られます。。。
コード内の複数のポイントでこれを実行することを計画している場合、if
ステートメントの繰り返しは少し醜くなります。乾いた状態を保つには、元の投稿で行ったように、条件関数を使用したテストを1つの関数にまとめることが賢明なようです。ただし、その関数の有用性は非常に狭くなりますcircleTest
。関数を実行する必要があるかどうかのみをテストし、必要に応じて実行します。
ここで、しばらくすると、のような他の条件関数があり、circleTest
それ自体が。に相当するものがあると想像してくださいdoCircleTest
。多分条件付きスイッチングコードを再利用するといいでしょう。このために、デフォルト値をとるオリジナルのような関数、計算上安価なテスト関数のブール結果、および関連する引数を持つ高価な条件関数の関数ハンドルを作成します...
function result = conditionalFun( default, cheapFunResult, expensiveFun, varargin )
if cheapFunResult
result = expensiveFun(varargin{:});
else
result = default;
end
end %//of function
この関数は、メインスクリプトから次のように呼び出すことができます。。。
result = conditionalFun(0, doCircleTest(point,Polygon,r,tol), @circleTest, point,Polygon,r,tol,stepSize);
...そしてその美しさは、任意のテスト、デフォルト値、および高価な関数を使用できることです。この単純な例では多分少しやり過ぎかもしれませんが、関数ハンドルを使用するというアイデアを思いついたとき、それは私の心がさまよった場所です。