私の講師は、ネストされたループを処理するときに参照できるように、Javaでループに「ラベル付け」することが可能であると今日述べました。それで、私はそれについて知らなかったので機能を調べました、そして、この機能が説明された多くの場所に警告が続き、ネストされたループを思いとどまらせました。
どうしてか分からないの?それはコードの可読性に影響するからですか?それとももっと「技術的」なものですか?
私の講師は、ネストされたループを処理するときに参照できるように、Javaでループに「ラベル付け」することが可能であると今日述べました。それで、私はそれについて知らなかったので機能を調べました、そして、この機能が説明された多くの場所に警告が続き、ネストされたループを思いとどまらせました。
どうしてか分からないの?それはコードの可読性に影響するからですか?それとももっと「技術的」なものですか?
回答:
ネストされたループは、正しいアルゴリズムを記述している限り問題ありません。
ネストされたループにはパフォーマンスの考慮事項があります(@ Travis-Pesettoの回答を参照)が、マトリックスのすべての値にアクセスする必要がある場合など、正確なアルゴリズムである場合があります。
Javaのループにラベルを付けると、他の方法でこれが面倒な場合に、いくつかのネストされたループから途中で抜け出すことができます。たとえば、一部のゲームには次のようなコードが含まれている場合があります。
Player chosen_one = null;
...
outer: // this is a label
for (Player player : party.getPlayers()) {
for (Cell cell : player.getVisibleMapCells()) {
for (Item artefact : cell.getItemsOnTheFloor())
if (artefact == HOLY_GRAIL) {
chosen_one = player;
break outer; // everyone stop looking, we found it
}
}
}
上記の例のようなコードは特定のアルゴリズムを表現する最適な方法である場合がありますが、通常、このコードを小さな関数に分割し、おそらくのreturn
代わりに使用する方が適切ですbreak
。だから、break
ラベル付きはかすかなコードの匂いです; あなたがそれを見るとき、特別な注意を払います。
ネストされたループは、頻繁に(常にではありませんが)悪い習慣です。なぜなら、それらはあなたがやろうとしていることに対してしばしば(しかし常にではない)過剰すぎるからです。多くの場合、達成しようとしている目標を達成するためのはるかに高速で無駄の少ない方法があります。
たとえば、リストAに100個のアイテムがあり、リストBに100個のアイテムがあり、リストAの各アイテムに一致するリストBのアイテムが1つあることがわかっている場合(「match」の定義は意図的に曖昧にここで)、ペアのリストを作成したい場合、簡単な方法は次のとおりです:
for each item X in list A:
for each item Y in list B:
if X matches Y then
add (X, Y) to results
break
各リストに100個のアイテムがある場合、これには平均100 * 100/2(5,000)matches
操作がかかります。より多くのアイテムがある場合、または1:1の相関が保証されていない場合は、さらに高価になります。
一方、次のような操作を実行するはるかに高速な方法があります。
sort list A
sort list B (according to the same sort order)
I = 0
J = 0
repeat
X = A[I]
Y = B[J]
if X matches Y then
add (X, Y) to results
increment I
increment J
else if X < Y then
increment I
else increment J
until either index reaches the end of its list
あなたは、このようにそれを行う場合は、代わりの数のmatches
上で基準とする操作length(A) * length(B)
、今に基づいているlength(A) + length(B)
あなたのコードははるかに高速に実行することを意味しています。
O(n log n)
Quicksortが使用されている場合、並べ替えのセットアップに2回の取るに足らない時間がかかることに注意してください。
O(n^2)
N.の非小さな値のため
<
演算子を介して比較可能であると想定していますが、通常はmatches
演算子から導出することはできません。第二に、XとYの両方が数値であっても、2番目のアルゴリズムが間違った結果を生成する場合X matches Y
がありX + Y == 100
ます(例:is)。
ループのネストを回避する理由の1つは、ループであるかどうかに関係なく、ブロック構造を深くネストしすぎるのは悪い考えだからです。
各関数またはメソッドは、目的(名前がそれを表すもの)とメンテナー(内部構造を理解しやすい)の両方で、理解しやすいものでなければなりません。関数が複雑すぎて簡単に理解できない場合、通常、内部の一部を個別の関数に分解して、名前で(現在はより小さい)メイン関数で参照できるようにする必要があることを意味します。
入れ子になったループは比較的簡単に理解するのが難しくなる場合がありますが、いくつかのループの入れ子は問題ありません-他の人が指摘するように、それは極端に(そして不必要に)遅いアルゴリズムを使用してパフォーマンスの問題を引き起こしているという意味ではありません。
実際、パフォーマンスの限界を驚くほど遅くするために、ネストされたループは必要ありません。たとえば、各反復でキューから1つの項目を取得し、場合によってはいくつかの項目を戻す単一のループ(迷路の幅優先探索など)を考えてください。パフォーマンスは、ループのネストの深さ(1のみ)ではなく、最終的に使い果たされる前にキューに入れられるアイテムの数(使い果たされた場合)によって決定されます-到達可能な部分の大きさ迷路です。
多くのネストされたループの場合、多項式時間になります。たとえば、次の擬似コードがあります。
set i equal to 1
while i is not equal to 100
increment i
set j equal to 1
while j is not equal to i
increment j
end
end
これはO(n ^ 2)時間と見なされ、次のようなグラフになります。
ここで、y軸はプログラムの終了にかかる時間であり、x軸はデータの量です。
データが多すぎる場合、プログラムは非常に遅くなり、誰もそれを待つことはありません。そして、私はそれがあまりにも長くかかると私が信じる約1,000のデータエントリほどではありません。
小型乗用車の代わりに30トンのトラックを運転するのは悪い習慣です。20トンまたは30トンの荷物を輸送する必要がある場合を除きます。
ネストされたループを使用する場合、悪い習慣ではありません。それはまったく馬鹿げているか、まさに必要なものです。あなたが決める。
しかし、誰かがループのラベル付けについて不満を言いました。その答えは、質問する必要がある場合は、ラベルを使用しないでください。自分を決定するのに十分な知識がある場合は、自分で決定します。
ネストされたループについて本質的に悪いことや、必ずしも悪いことはありません。ただし、特定の考慮事項と落とし穴があります。
あなたが導かれた記事は、おそらく簡潔さの名において、または燃えているとして知られている心理的なプロセスのために、詳細をスキップしました。
焦げているとは、あなたが何かを否定的に経験し、それを避けているという意味です。たとえば、野菜を鋭いナイフで切り、自分で切ります。その場合、鋭利なナイフは悪いと言うかもしれません。野菜を切るためにそれらを使用して、その悪い経験が二度と起こらないようにしようとしないでください。それは明らかに非常に非現実的です。実際には、注意する必要があります。他の人に野菜を切るように言っているなら、あなたはこれをさらに強く感じます。野菜を切るように子供たちに指示していた場合、特にそれらを厳密に監督できない場合は、鋭いナイフを使用しないように彼らに伝えることを非常に強く感じます。
プログラミングの問題は、常に最初に安全性を優先する場合、ピーク効率に達しないことです。この場合、子供は柔らかい野菜のみを切ることができます。他の何かに直面し、彼らは鈍いナイフを使用してそれを台無しにします。ネストされたループを含むループの適切な使用法を学ぶことが重要であり、それらが悪いとみなされて、決してそれらを使用しようとしないなら、あなたはそれをすることができません。
ここで多くの回答が指摘しているように、ネストされたforループは、プログラムのパフォーマンス特性を示しており、ネストごとに指数関数的に悪化する可能性があります。つまり、O(n)、O(n ^ 2)、O(n ^ 3)などは、O(n ^ depth)で構成されます。ここで、depthは、ネストしたループの数を表します。ネストが大きくなると、必要な時間が指数関数的に長くなります。問題はこれがあなたの時間または空間の複雑さがそれであるという確実性ではないことです(非常にしばしばa * b * cですが、すべてのネストループが常に実行されるわけではありません)。それであってもパフォーマンスの問題があります。
多くの人々、特に率直に言う学生、作家、講師にとっては、生活のために、または日常的にループのためにプログラムされることはめったにないことも、慣れていないものであり、早期の出会いにあまりにも多くの認知的負荷を引き起こします。学習曲線は常に存在し、それを避けることは学生をプログラマーに変えるのに効果的ではないため、これは問題のある側面です。
ネストされたループはワイルドになる可能性があります。つまり、非常に深くネストされる可能性があります。私が各大陸を通り、次に各国を通り、次に各都市を通り、次に各店を通り、次に各棚を通り、それから各製品を通り抜け、各豆に豆の缶があれば、そのサイズを測定して平均を求めます非常に深くネストすることがわかります。ピラミッドがあり、左マージンから多くの無駄なスペースがあります。場合によっては、ページを離れることもあります。
これは、画面が小さく、解像度が低い場合、歴史的に重要な問題です。これらの場合、いくつかのレベルのネストでさえ、本当に多くのスペースを占有する可能性があります。これは、しきい値が高い今日ではそれほど懸念事項ではありませんが、十分な入れ子がある場合でも問題が発生する可能性があります。
関連するのは美学の議論です。多くの人は、より一貫性のある配置のレイアウトとは対照的に、ネストされたforループが見た目が良いとは感じません。ただし、自己強化される傾向があり、コードのブロックを分割し、関数などの抽象化の背後にループをカプセル化すると、コードの実行フローへのマッピングを分割するリスクがあるため、最終的にコードを読みにくくする可能性があるという点で問題があります。
人々が慣れているものへの自然な傾向があります。ネストを必要としない確率が最も単純な方法でプログラミングしている場合、あるレベルが必要になる確率は1桁低下し、別のレベルが発生する確率は再び低下します。頻度が低下し、本質的に意味するのは、ネストが深いほど、訓練されていない人間の感覚はそれを予測することです。
これに関連するのは、ネストされたループと見なすことができる複雑な構造では、ループをあまり必要としないソリューションを逃す可能性があるため、できるだけ簡単なソリューションであるということです。皮肉なことに、ネストされたソリューションは、多くの場合、最小限の労力、複雑さ、および認知負荷で機能するものを作成する最も簡単な方法です。多くの場合、forループをネストします。たとえば、上記の回答の1つを検討すると、ネストされたforループよりもはるかに高速な方法もはるかに複雑で、かなり多くのコードで構成されています。
ループを抽象化または平坦化することはしばしば可能であるため、多くの注意が必要です。最終結果は、特に努力から測定可能な大幅なパフォーマンスの向上を受けていない場合、最終的には病気よりも悪い治療法になります。
コンピュータにアクションを何度も繰り返すように指示するループに関連して、パフォーマンスの問題が頻繁に発生することは非常に一般的であり、本質的にパフォーマンスのボトルネックに関係します。残念ながら、これに対する応答は非常に表面的です。ループが見られ、パフォーマンスの問題が発生し、何も存在しない場合にループが見えなくなり、実際の効果が得られなくなることがよくあります。このコードは高速に「見え」ますが、道路に置いて、点火キーを押し、アクセルを床に置き、速度計を見てください。ジマーフレームを歩いている老婦人と同じくらい速いことがわかります。
この種の隠蔽は、ルートに10人の強盗がいる場合に似ています。あなたが行きたい場所にまっすぐなルートを持っている代わりに、隅々に強盗がいるように手配すると、強盗がないという旅を始めるときに幻想を与えます。頭の外に見えない。あなたはまだ10回強盗されるつもりですが、今ではそれが来るのを見ることはありません。
あなたの質問に対する答えは、両方であるということですが、どちらの懸念も絶対的なものではありません。それらは完全に主観的であるか、文脈的にのみ客観的です。残念なことに、完全に主観的な意見やむしろ意見が先行して支配することがあります。
経験則として、ネストされたループが必要な場合、またはそれが次の明白なステップのように思える場合は、意図せずに単純に実行するのが最善です。ただし、疑問が残る場合は、後で確認する必要があります。
もう1つの経験則は、カーディナリティを常に確認し、このループが問題になるかどうかを自問することです。前の例では、都市を通りました。テストのために、私は10の都市を通過するだけかもしれませんが、現実世界の使用で期待される都市の合理的な最大数は何ですか?それから、大陸で同じことを掛けます。ループで常に考慮することは、経験則です。特に、動的な(可変の)量を反復することは、それが最終的に変換される可能性があります。
とにかく、常に最初に動作することを実行してください。最適化の機会を見つけた場合、最適化されたソリューションを最も簡単に機能するソリューションと比較し、期待される利点が得られたことを確認できます。また、測定が行われる前に早すぎる最適化を行うと、YAGNIが発生したり、多くの時間を無駄にしたり、期限を逃したりする可能性があります。
n
、幾何学的または多項式です。