중첩 루프가 나쁜 습관으로 간주되는 이유는 무엇입니까?


33

내 강사는 오늘 중첩 루프를 다룰 때 참조 할 수 있도록 Java에서 루프에 "레이블"을 지정할 수 있다고 언급했습니다. 그래서 나는 알지 못했던 기능 과이 기능이 설명 된 많은 곳에서 경고를 보냈으며 중첩 루프를 낙담했습니다.

왜 그런지 모르겠어요? 코드의 가독성에 영향을 미치기 때문입니까? 아니면 더 "기술적"인가?


4
CS3 과정을 올바르게 기억하면 지수 시간이 걸리기 때문에 큰 데이터 세트를 얻는 경우 응용 프로그램을 사용할 수 없게됩니다.
Travis Pessetto

21
CS 강사에 대해 알아야 할 한 가지는 그들이 말하는 모든 것이 실제 세계에 100 % 적용되는 것은 아니라는 것입니다. 루프가 몇 개 이상 깊숙이 중첩되어 있지는 않지만 문제를 해결하기 위해 m x n 요소를 처리 해야하는 경우 많은 반복을 수행하게됩니다.
Blrfl

8
@TravisPessetto 사실 그것은 여전히 ​​다항식 복잡성입니다-O (n ^ k), k는 지수 O (k ^ n)가 아니라 중첩 된 수입니다. 여기서 k는 상수입니다.
m3th0dman

1
@ m3th0dman 수정 해 주셔서 감사합니다. 선생님은이 주제에서 가장 훌륭하지 않았습니다. 그는 O (n ^ 2)와 O (k ^ n)를 동일하게 취급했습니다.
트래비스 페 셋토

2
일부 사람들에 따르면 중첩 루프는 순환 복잡성을 증가시키고 ( 여기 참조 ) 프로그램의 유지 관리 성을 저하시킵니다.
Marco

답변:


62

중첩 루프는 올바른 알고리즘을 설명하는 한 괜찮습니다.

중첩 루프는 성능을 고려해야하지만 (@ 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레이블이있는 희미한의 인 코드 냄새 ; 당신이 그것을 볼 때 특히주의하십시오.


2
사이드 노트 그래픽과 마찬가지로 모든 매트릭스 조각에 액세스해야하는 알고리즘을 사용합니다. 그러나 GPU는이를 시간 효율적으로 처리하도록 전문화되어 있습니다.
Travis Pessetto

예, GPU는이 작업을 병렬로 수행합니다. 문제는 단일 실행 스레드에 관한 것이라고 생각합니다.
9000

2
레이블이 의심되는 이유 중 하나는 종종 대안이 있기 때문입니다. 이 경우 대신 돌아올 수 있습니다.
jgmjgm

22

중첩 루프는 종종 (그러나 항상 그런 것은 아닙니다) 나쁜 습관입니다. 대부분의 경우 달성하려는 목표를 달성하는 데 훨씬 빠르고 낭비가 적은 방법이 있습니다.

예를 들어, 목록 A에 100 개의 항목이 있고 목록 B에 100 개의 항목이 있고 목록 A의 각 항목에 대해 목록 B에 일치하는 항목이 하나 있다는 것을 알고 있습니다 ( "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)을 두게되므로 코드가 훨씬 빠르게 실행됩니다.


10
O(n log n)Quicksort를 사용하는 경우 정렬 설정에 사소한 시간이 걸리지 않는다는 경고가 있습니다.
Robert Harvey

@RobertHarvey : 물론입니다. 그러나 그것은 여전히 O(n^2)N이 아닌 작은 가치 보다 훨씬 적습니다 .
Mason Wheeler

10
알고리즘의 두 번째 버전은 일반적으로 올바르지 않습니다. 먼저, X와 Y는 <연산자 를 통해 비교할 수 있다고 가정 하는데, 이는 일반적으로 연산자에서 파생 될 수 없습니다 matches. 둘째, X와 Y가 모두 숫자 인 경우에도 2 번째 알고리즘은 여전히 ​​잘못된 결과를 생성 할 수 있습니다 (예 : X matches Yis) X + Y == 100.
Pasha

9
@ user958624 : 분명히 이것은 일반적인 알고리즘에 대한 매우 높은 수준의 개요입니다. "matchs"연산자와 마찬가지로 "<"는 비교할 데이터의 컨텍스트에서 올바른 방식으로 정의되어야합니다. 올바르게 수행하면 결과가 정확합니다.
메이슨 휠러

PHP는 마지막으로 생각했던 배열과는 다른 것으로 생각했습니다. 사람들은 대신 해시 기반의 PHP 배열을 사용하고 훨씬 빠릅니다.
jgmjgm

11

중첩 루프를 피하는 한 가지 이유는 루프 구조인지 여부에 관계없이 블록 구조를 너무 깊게 중첩하는 것이 좋지 않기 때문입니다.

각 기능이나 방법은 목적 (이름이하는 것을 표현해야 함)과 관리자 (내부를 이해하기 쉬워야 함) 모두 이해하기 쉬워야합니다. 함수가 이해하기에 너무 복잡하면 일반적으로 내부의 일부를 별도의 함수로 분해하여 이름으로 (더 작은) 주 함수에서 참조 할 수 있음을 의미합니다.

중첩 루프는 비교적 빨리 이해하기 어려울 수 있지만, 일부 중첩 루프는 괜찮습니다. 즉, 다른 알고리즘에서 알 수 있듯이 매우 느린 알고리즘을 사용하여 성능 문제를 발생시키는 것은 아닙니다.

실제로 성능이 저하되는 중첩 루프가 필요하지 않습니다. 예를 들어, 각 반복에서 하나의 항목을 대기열에서 가져 와서 여러 개의 백을 넣을 수있는 단일 루프를 고려하십시오 (예 : 미로의 너비 우선 검색). 성능은 루프의 중첩 깊이 (단지 1)에 의해 결정되는 것이 아니라 결국 소진되기 전에 해당 큐에 배치되는 항목 수 (소진 된 경우 )에 의해 결정됩니다. 미로는


1
중첩 루프를 평평하게 만들면 여전히 같은 시간이 걸립니다. 0에서 너비로, 0에서 높이로; 대신 너비의 높이에 0을 곱하면됩니다.
jgmjgm

@jgmjgm-예, 루프 내부의 코드를 복잡하게 할 위험이 있습니다. 평탄화는 때때로이를 단순화 할 수 있지만, 실제로는 실제로 원하는 인덱스를 복구하는 복잡성을 추가하는 경우가 더 많습니다. 하나의 트릭은 특별한 복합 인덱스를 증가시키기 위해 모든 루프를 논리에 중첩시키는 인덱스 유형을 사용하는 것입니다. 하나의 루프에 대해서는 그렇게하지 않을 수도 있지만 구조가 비슷한 여러 개의 루프가 있거나 어쩌면 보다 유연한 일반 버전을 작성할 수 있습니다. 해당 유형 (있는 경우)을 사용하기위한 오버 헤드는 명확성을 위해 가치가 있습니다.
Steve314

나는 그것을 좋은 일로 제안하지는 않지만 두 개의 루프를 하나로 바꾸는 것이 얼마나 놀랍도록 간단하지만 여전히 시간 복잡성에 영향을 미치지는 않습니다.
jgmjgm

7

많은 중첩 루프의 경우 다항식 시간으로 끝납니다. 예를 들어이 의사 코드는 다음과 같습니다.

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 개 정도의 데이터 항목이 너무 오래 걸리지 않을 것이라고 생각합니다.


10
그래프를 다시 선택하고 싶을 수도 있습니다. 그것은 2 차 곡선이 아닌 지수 곡선입니다. 또한 재귀는 O (n ^ 2)에서 O (n log n)를 만들지 않습니다.
ratchet freak

5
재귀를 위해 "stack"과 "overflow"라는 두 단어가 있습니다.
Mateusz

10
재귀가 O (n ^ 2) 연산을 O (n log n)으로 줄일 수 있다는 진술은 크게 부정확합니다. 반복적으로 구현 된 알고리즘과 반복적으로 구현되는 알고리즘은 정확히 동일한 big-O 시간 복잡성을 가져야합니다. 또한 각 재귀 호출마다 새 스택 프레임을 작성해야하고 반복에는 분기 / 비교 만 필요하므로 재귀가 더 느려질 수 있습니다 (언어 구현에 따라 다름). 함수 호출은 일반적으로 저렴하지만 무료는 아닙니다.
dckrooney

2
@TravisPessetto 재귀 또는 순환 객체 참조로 인해 C # 응용 프로그램을 개발하는 동안 지난 6 개월 동안 3 개의 스택 오버플로가 발생했습니다. 그것이 충돌하고 당신이 무엇을 치는지 모른다는 것이 재미있는 것입니다. 중첩 루프를 보면 잘못된 것이 발생할 수 있으며 잘못된 색인에 대한 예외를 쉽게 볼 수 있습니다.
Mateusz

2
@Mateusz, Java와 같은 언어를 사용하면 스택 오버플로 오류를 잡을 수 있습니다. 스택 추적을 통해 어떤 일이 발생했는지 확인할 수 있습니다. 나는 경험이 많지 않지만 스택 오버플로 오류를 본 유일한 시간은 무한 재귀를 일으키는 버그가 있고 PHP에 할당 된 512MB의 메모리가 부족한 PHP입니다. 재귀에는 최종 종료 값이 있어야하며 무한 루프에는 적합하지 않습니다. CS의 모든 것과 마찬가지로 모든 것을위한 시간과 장소가 있습니다.
트래비스 페 셋토

0

소형 승용차 대신 30 톤 트럭을 운전하는 것은 좋지 않습니다. 20 ~ 30 톤의 물건을 운송해야하는 경우를 제외하고.

중첩 루프를 사용할 때는 나쁜 습관이 아닙니다. 그것은 완전히 어리 석거나 정확히 필요한 것입니다. 당신이 결정합니다.

그러나 누군가 루프 라벨링 에 대해 불평했습니다 . 이에 대한 답변 : 질문을해야하는 경우 라벨을 사용하지 마십시오. 자신을 결정할만큼 충분히 알고 있다면 스스로 결정합니다.


스스로 결정하기에 충분한 지식이 있다면 레이블이있는 루프를 사용하지 않을만큼 충분히 알고 있습니다. :-)
user949300

아닙니다. 충분히 배운 후에는 라벨이 붙은 사용을 사용하지 않도록 배웠습니다. 충분히 알고 있으면 교리를 초월하여 올바른 일을합니다.
gnasher729

1
레이블의 문제는 거의 필요하지 않으며 많은 사람들이 레이블을 사용하여 흐름 제어 실수를 해결하기 위해 초기에 사용한다는 것입니다. 사람들이 흐름 제어를 잘못 받았을 때 어떻게 사람들이 출구를 빠져 나가는 것과 같습니다. 기능은 또한 크게 중복됩니다.
jgmjgm

-1

중첩 루프에 대해 본질적으로 잘못되거나 필연적으로 나쁜 것은 없습니다. 그러나 특정 고려 사항과 함정이 있습니다.

간결성의 이름으로 또는 불에 타는 것으로 알려진 심리적 과정으로 인해 당신이 이끌어 낸 기사는 세부 사항을 건너 뜁니다.

화상을 입는 것은 연루된 존재에 대해 부정적인 경험을했을 때 피하는 것입니다. 예를 들어 날카로운 칼로 야채를 자르고 스스로자를 수 있습니다. 나는 날카로운 칼이 나쁘다고 말할 수 있습니다. 그 나쁜 경험이 다시는 불가능 해 지도록 야채를 자르려고하지 마십시오. 그것은 분명히 매우 비현실적입니다. 실제로는 조심해야합니다. 다른 사람에게 야채를 자르라고하면 더 강한 감각이 있습니다. 아이들에게 야채를 자르라고 지시한다면, 특히 내가 그들을 면밀히 감독 할 수 없다면 날카로운 칼을 사용하지 말라고 강하게 느낄 것입니다.

프로그래밍에서 문제는 항상 안전을 최우선으로 생각한다면 최고 효율을 달성하지 못할 것이라는 점입니다. 이 경우 아이들은 부드러운 야채 만자를 수 있습니다. 다른 것들과 대면하고 그들은 무딘 칼을 사용하여 그것을 엉망으로 만들 것입니다. 중첩 루프를 포함하여 루프를 올바르게 사용하는 방법을 배우는 것이 중요하며, 불량으로 간주되어 절대 사용하려고 시도하지 않으면 그렇게 할 수 없습니다.

여기서 많은 답변이 중첩 된 for 루프는 각 중첩이 기하 급수적으로 악화 될 수있는 프로그램의 성능 특성을 나타냅니다. 즉, O (n), O (n ^ 2), O (n ^ 3) 등은 깊이가 중첩 된 루프 수를 나타내는 O (n ^ depth)로 구성됩니다. 네 스팅이 커지면 필요한 시간이 기하 급수적으로 늘어납니다. 문제는 이것으로 당신의 시간이나 공간의 복잡성이 확실하지 않다는 것입니다 (종종 a * b * c이지만 모든 네스트 루프가 항상 실행될 수있는 것은 아닙니다). 그래도 성능 문제가 있습니다.

많은 사람들, 특히 학생, 작가, 강사는 솔직하거나 일상 생활을 위해 거의 프로그램을하지 않으며 루프를 매일 사용하는 것도 익숙하지 않은 것일 수도 있고, 초기 만남에서 너무 많은 인지력을 불러 일으킬 수도 있습니다. 이것은 항상 학습 곡선이 있고 학생들을 프로그래머로 전환시키는 데 효과적이지 않기 때문에 문제가되는 측면입니다.

중첩 루프는 거칠어 질 수 있습니다. 즉, 매우 깊이 중첩 될 수 있습니다. 각 대륙을 통과 한 다음 각 국가, 각 도시, 각 상점, 각 선반, 선반을 거쳐 각 콩을 통해 콩 통조림 인 경우 각 제품을 통해 평균을 얻기 위해 크기를 측정하면 매우 깊게 중첩 될 것입니다. 피라미드와 왼쪽 여백에서 많은 낭비 공간이 생깁니다. 당신은 결국 페이지를 떠날 수도 있습니다.

이것은 화면이 작고 해상도가 낮은 역사적으로 더 중요한 문제입니다. 이 경우 몇 단계의 네 스팅조차도 많은 공간을 차지할 수 있습니다. 중첩이 충분할 경우 여전히 문제가 될 수 있지만 임계 값이 더 높은 오늘날에는 덜 걱정입니다.

미학 논증과 관련이 있습니다. 많은 사람들은보다 일관된 정렬을 가진 레이아웃과 달리 미묘하게 만족하는 중첩 된 for 루프를 발견하지 못합니다. 이는 사람들이 익숙한 것, 시선 추적 및 기타 관심사와 연결되거나 연결되지 않을 수 있습니다. 그러나 자체 강화하는 경향이 있으며 코드 블록을 깨고 함수와 같은 추상화 뒤에 루프를 캡슐화하여 코드를 읽기가 더 어려워 질 수 있다는 점에서 문제가 있습니다.

사람들에게 익숙한 경향이 있습니다. 가장 간단한 방법으로 무언가를 프로그래밍하는 경우 중첩이 필요하지 않을 확률이 가장 높고 한 레벨이 필요할 확률이 한 단계 씩 떨어지고 다른 레벨의 확률이 다시 떨어집니다. 주파수가 떨어지고 본질적으로 중첩이 깊어 질수록 인간의 감각이 훈련을 덜 받는다는 것을 의미합니다.

이와 관련하여 중첩 루프를 고려할 수있는 복잡한 구성에서는 루프를 덜 필요로하는 누락 된 솔루션이있을 가능성이 있으므로 가능한 가장 간단한 솔루션을 항상 물어보아야합니다. 아이러니 한 사실은 중첩 된 솔루션이 최소한의 노력, 복잡성 및인지 부하로 작동하는 무언가를 생성하는 가장 간단한 방법이라는 것입니다. for 루프를 중첩하는 것이 자연 스럽습니다. 예를 들어 중첩 된 for 루프보다 훨씬 빠른 방법이 훨씬 더 복잡하고 훨씬 더 많은 코드로 구성된 위의 답변 중 하나를 고려하십시오.

루프를 추상화하거나 평평하게 만드는 것이 종종 가능하기 때문에 상당한주의가 필요합니다. 결과적으로 특히 예를 들어 노력으로 측정 가능하고 상당한 성능 향상을 얻지 못하는 경우 궁극적으로 질병보다 치료법이 악화됩니다.

컴퓨터가 동작을 여러 번 반복하도록 지시하는 루프와 관련하여 성능 문제가 자주 발생하는 경우가 많으며 본질적으로 성능 병목 현상이 종종 발생합니다. 불행히도 이것에 대한 반응은 매우 피상적 ​​일 수 있습니다. 사람들이 루프를 보지 않고 성능 문제를 확인한 다음 루프를 보이지 않게 숨기면 실제 효과가없는 것이 일반적입니다. 이 코드는 "빠르게"보이지만 도로에 놓고 점화 키를 누르고 가속기를 바닥에 놓고 속도계를 살펴보면 여전히 노부인이 짐머 프레임을 걷는 것만 큼 빠르다는 것을 알 수 있습니다.

이런 종류의 숨기기는 경로에 10 개의 머그잔이있는 경우와 유사합니다. 당신이 가고 싶은 곳으로 똑 바른 경로를 갖는 대신에 모든 구석 뒤에 구부러진 부분이 있도록 배열하면 구부러진 곳이 없다는 여행을 시작할 때 환상을줍니다. 눈에서 멀어지면 마음에서도 멀어진 다. 여전히 열 번이나 머뭇거릴 것이지만 이제는 오지 않을 것입니다.

당신의 질문에 대한 대답은 둘 다이지만 걱정은 절대적이지 않다는 것입니다. 그것들은 전적으로 주관적이거나 맥락 상 객관적입니다. 불행하게도 때로는 전적으로 주관적이거나 오히려 의견이 우선적이고 지배적입니다.

일반적으로 중첩 루프가 필요하거나 다음 단계처럼 보이는 경우 의도적으로 수행하지 않는 것이 가장 좋습니다. 그러나 의심이 남아 있으면 나중에 검토해야합니다.

경험의 또 다른 규칙은 항상 카디널리티를 확인하고 스스로에게 물어보아야한다는 것입니다.이 루프는 문제가 될 것입니다. 이전 예에서 나는 도시를 갔다. 테스트를 위해 10 개 도시 만 통과 할 수 있지만 실제 사용에서 예상되는 합리적인 최대 도시 수는 얼마입니까? 그런 다음 대륙에 대해서도 똑같이 곱할 수 있습니다. 루프를 고려할 때 특히 동적으로 (가변) 여러 번 반복하여 줄 아래로 변환하는 것을 고려하는 것이 일반적입니다.

항상 먼저 작동하는 것을 수행하십시오. 최적화 기회를 확인할 수있는 방법은 최적화 된 솔루션을 가장 손쉬운 작업과 비교하여 예상되는 이점을 얻었음을 확인할 수 있습니다. 측정이 시작되고 YAGNI 또는 많은 시간 낭비와 마감 기한을 놓치기 전에 너무 빨리 최적화하는 데 많은 시간을 할애 할 수 있습니다.


무딘 나이프는 일반적으로 절단에 위험하므로 날카로운 <-> 무딘 나이프 예제는 그리 좋지 않습니다 . 그리고, O (N) -> O (N ^ 2) -> O (N ^ 3)되지 지수 에서 n, 그것의 기하학적 또는 다항식 .
Caleth

둔한 칼이 더 나쁘다는 것은 도시 신화의 일부입니다. 실제로는 다양하며 일반적으로 둔한 나이프가 특히 부적합한 경우가 많으며 일반적으로 많은 힘이 필요하고 미끄러짐이 많이 발생합니다. 당신은 그렇습니다. 숨겨 지지만 본질적인 한계가 있으며, 부드러운 야채 만자를 수 있습니다. 나는 n ^ depth 지수를 고려할 것이지만 당신은 그 자체로 그 예가 맞지 않습니다.
jgmjgm

@jgmjgm : n ^ depth는 다항식이고 depth ^ n은 지수입니다. 실제로 해석의 문제는 아닙니다.
Roel Schroeven

x ^ y는 y ^ x와 다른가요? 실수는 당신이 답을 읽지 못한다는 것입니다. 당신은 지수를 위해 gre 다가 그 자체로 지수가 아닌 방정식을 pp습니다. 당신이 읽는다면 중첩의 각 레이어에 대해 기하 급수적으로 증가한다는 것을 알 수 있습니다. 그리고 그것을 직접 테스트에 넣으면 for (a = 0; a <n; a ++); for (b = 0; b <n ; b ++); for (c = 0; c <n; c ++); 루프를 추가하거나 제거하면 실제로는 지수임을 알 수 있습니다. n ^ 1에서 하나의 루프 수행, n ^ 2에서 2 개 수행, n ^ 3에서 3 개 수행을 찾을 수 있습니다. 중첩 된 : D를 이해하지 못합니다. Gemoetric 하위 집합 지수.
jgmjgm

이것이 사람들이 실제로 중첩 된 구조로 어려움을 겪고 있다는 점을 증명한다고 생각합니다.
jgmjgm
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.