web-dev-qa-db-ja.com

ネストされたループが悪い習慣と見なされるのはなぜですか?

講師は本日、Javaでループに「ラベルを付ける」ことができ、ネストされたループを処理するときに参照できるようにしたので、私は知らなかったので機能を調べました。それと、この機能が説明された多くの場所には警告が続き、ネストされたループを阻止しました。

理由がよくわかりませんか?それはコードの可読性に影響を与えるからですか?それとももっと「技術的」なのでしょうか?

33
DSF

ネストされたループは、正しいアルゴリズムを記述している限り問題ありません。

ネストされたループにはパフォーマンスの考慮事項があります(@ 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はかすかな コードのにおい ;です。あなたがそれを見るとき、特別な注意を払ってください。

61
9000

ネストされたループは頻繁に(常にではありませんが)悪い習慣です。なぜなら、それらは頻繁に(常にではありませんが)やりたいことをやりすぎているためです。多くの場合、達成しようとしている目標を達成するためのはるかに高速で無駄のない方法があります。

たとえば、リストAに100個のアイテムがあり、リストBに100個のアイテムがあり、リストAの各アイテムについて、リストBにそれに一致するアイテムが1つあることを知っている(「一致」の定義が意図的に不明瞭になっている)ここ)、ペアのリストを作成したい場合、簡単な方法は次のとおりです:

_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)に基づくようになり、コードがより高速に実行されます。

24
Mason Wheeler

ループのネストを回避する1つの理由は、ループであるかどうかに関係なく、ブロック構造を深くネストしすぎることは悪い考えであるためです。

各関数またはメソッドは、その目的(名前はその機能を表す必要があります)とメンテナー(内部を理解しやすいもの)の両方を理解しやすくする必要があります。関数が複雑すぎて簡単に理解できない場合は、通常、一部の内部関数を個別の関数に分解して、(現在は小さい)メイン関数で名前で参照できるようにする必要があります。

ネストされたループは比較的迅速に理解することが難しくなりますが、一部のループのネストは問題ありません。他の人が指摘しているように、極端に(そして不必要に)遅いアルゴリズムを使用してパフォーマンスの問題を引き起こしているわけではありません。

実際には、パフォーマンスの限界を極端に遅くするためにループをネストする必要はありません。たとえば、各反復でキューから1つのアイテムを取得し、場合によっては複数のアイテムを戻す単一のループについて考えてみます。迷路の幅優先探索。パフォーマンスは、ループのネストの深さ(1のみ)によって決定されるのではなく、最終的に使い果たされる前にそのキューに入れられるアイテムの数によって決まります(if使い果たされる)-迷路の到達可能な部分の大きさ。

11
Steve314

多くのネストされたループの場合を考えると、多項式時間になります。たとえば、次の疑似コードがあるとします。

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)時間と見なされ、次のようなグラフになります。 enter image description here

ここで、y軸はプログラムの終了にかかる時間で、x軸はデータの量です。

取得するデータが多すぎると、プログラムが非常に遅くなり、誰もそれを待つことはありません。そして、それは私がそれがあまりに長くかかるだろうと私が信じるほど多くの1,000データエントリではありません。

7
Travis Pessetto

小型乗用車の代わりに30トンのトラックを運転することは悪い習慣です。あなたが20または30トンのものを輸送する必要があるときを除いて。

ネストされたループを使用する場合、それは悪い習慣ではありません。それはまったく愚かであるか、まさに必要なものです。あなたが決める。

しかし、誰かがlabellingループについて不満を言っています。その答え:質問する必要がある場合は、ラベルを使用しないでください。自分を決めるのに十分知っているなら、自分を決めます。

0
gnasher729