내 강사는 오늘 중첩 루프를 다룰 때 참조 할 수 있도록 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에 일치하는 항목이 하나 있다는 것을 알고 있습니다 ( "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를 사용하는 경우 정렬 설정에 사소한 시간이 걸리지 않는다는 경고가 있습니다.
O(n^2)
N이 아닌 작은 가치 보다 훨씬 적습니다 .
<
연산자 를 통해 비교할 수 있다고 가정 하는데, 이는 일반적으로 연산자에서 파생 될 수 없습니다 matches
. 둘째, X와 Y가 모두 숫자 인 경우에도 2 번째 알고리즘은 여전히 잘못된 결과를 생성 할 수 있습니다 (예 : X matches Y
is) X + Y == 100
.
중첩 루프를 피하는 한 가지 이유는 루프 구조인지 여부에 관계없이 블록 구조를 너무 깊게 중첩하는 것이 좋지 않기 때문입니다.
각 기능이나 방법은 목적 (이름이하는 것을 표현해야 함)과 관리자 (내부를 이해하기 쉬워야 함) 모두 이해하기 쉬워야합니다. 함수가 이해하기에 너무 복잡하면 일반적으로 내부의 일부를 별도의 함수로 분해하여 이름으로 (더 작은) 주 함수에서 참조 할 수 있음을 의미합니다.
중첩 루프는 비교적 빨리 이해하기 어려울 수 있지만, 일부 중첩 루프는 괜찮습니다. 즉, 다른 알고리즘에서 알 수 있듯이 매우 느린 알고리즘을 사용하여 성능 문제를 발생시키는 것은 아닙니다.
실제로 성능이 저하되는 중첩 루프가 필요하지 않습니다. 예를 들어, 각 반복에서 하나의 항목을 대기열에서 가져 와서 여러 개의 백을 넣을 수있는 단일 루프를 고려하십시오 (예 : 미로의 너비 우선 검색). 성능은 루프의 중첩 깊이 (단지 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)로 구성됩니다. 네 스팅이 커지면 필요한 시간이 기하 급수적으로 늘어납니다. 문제는 이것으로 당신의 시간이나 공간의 복잡성이 확실하지 않다는 것입니다 (종종 a * b * c이지만 모든 네스트 루프가 항상 실행될 수있는 것은 아닙니다). 그래도 성능 문제가 있습니다.
많은 사람들, 특히 학생, 작가, 강사는 솔직하거나 일상 생활을 위해 거의 프로그램을하지 않으며 루프를 매일 사용하는 것도 익숙하지 않은 것일 수도 있고, 초기 만남에서 너무 많은 인지력을 불러 일으킬 수도 있습니다. 이것은 항상 학습 곡선이 있고 학생들을 프로그래머로 전환시키는 데 효과적이지 않기 때문에 문제가되는 측면입니다.
중첩 루프는 거칠어 질 수 있습니다. 즉, 매우 깊이 중첩 될 수 있습니다. 각 대륙을 통과 한 다음 각 국가, 각 도시, 각 상점, 각 선반, 선반을 거쳐 각 콩을 통해 콩 통조림 인 경우 각 제품을 통해 평균을 얻기 위해 크기를 측정하면 매우 깊게 중첩 될 것입니다. 피라미드와 왼쪽 여백에서 많은 낭비 공간이 생깁니다. 당신은 결국 페이지를 떠날 수도 있습니다.
이것은 화면이 작고 해상도가 낮은 역사적으로 더 중요한 문제입니다. 이 경우 몇 단계의 네 스팅조차도 많은 공간을 차지할 수 있습니다. 중첩이 충분할 경우 여전히 문제가 될 수 있지만 임계 값이 더 높은 오늘날에는 덜 걱정입니다.
미학 논증과 관련이 있습니다. 많은 사람들은보다 일관된 정렬을 가진 레이아웃과 달리 미묘하게 만족하는 중첩 된 for 루프를 발견하지 못합니다. 이는 사람들이 익숙한 것, 시선 추적 및 기타 관심사와 연결되거나 연결되지 않을 수 있습니다. 그러나 자체 강화하는 경향이 있으며 코드 블록을 깨고 함수와 같은 추상화 뒤에 루프를 캡슐화하여 코드를 읽기가 더 어려워 질 수 있다는 점에서 문제가 있습니다.
사람들에게 익숙한 경향이 있습니다. 가장 간단한 방법으로 무언가를 프로그래밍하는 경우 중첩이 필요하지 않을 확률이 가장 높고 한 레벨이 필요할 확률이 한 단계 씩 떨어지고 다른 레벨의 확률이 다시 떨어집니다. 주파수가 떨어지고 본질적으로 중첩이 깊어 질수록 인간의 감각이 훈련을 덜 받는다는 것을 의미합니다.
이와 관련하여 중첩 루프를 고려할 수있는 복잡한 구성에서는 루프를 덜 필요로하는 누락 된 솔루션이있을 가능성이 있으므로 가능한 가장 간단한 솔루션을 항상 물어보아야합니다. 아이러니 한 사실은 중첩 된 솔루션이 최소한의 노력, 복잡성 및인지 부하로 작동하는 무언가를 생성하는 가장 간단한 방법이라는 것입니다. for 루프를 중첩하는 것이 자연 스럽습니다. 예를 들어 중첩 된 for 루프보다 훨씬 빠른 방법이 훨씬 더 복잡하고 훨씬 더 많은 코드로 구성된 위의 답변 중 하나를 고려하십시오.
루프를 추상화하거나 평평하게 만드는 것이 종종 가능하기 때문에 상당한주의가 필요합니다. 결과적으로 특히 예를 들어 노력으로 측정 가능하고 상당한 성능 향상을 얻지 못하는 경우 궁극적으로 질병보다 치료법이 악화됩니다.
컴퓨터가 동작을 여러 번 반복하도록 지시하는 루프와 관련하여 성능 문제가 자주 발생하는 경우가 많으며 본질적으로 성능 병목 현상이 종종 발생합니다. 불행히도 이것에 대한 반응은 매우 피상적 일 수 있습니다. 사람들이 루프를 보지 않고 성능 문제를 확인한 다음 루프를 보이지 않게 숨기면 실제 효과가없는 것이 일반적입니다. 이 코드는 "빠르게"보이지만 도로에 놓고 점화 키를 누르고 가속기를 바닥에 놓고 속도계를 살펴보면 여전히 노부인이 짐머 프레임을 걷는 것만 큼 빠르다는 것을 알 수 있습니다.
이런 종류의 숨기기는 경로에 10 개의 머그잔이있는 경우와 유사합니다. 당신이 가고 싶은 곳으로 똑 바른 경로를 갖는 대신에 모든 구석 뒤에 구부러진 부분이 있도록 배열하면 구부러진 곳이 없다는 여행을 시작할 때 환상을줍니다. 눈에서 멀어지면 마음에서도 멀어진 다. 여전히 열 번이나 머뭇거릴 것이지만 이제는 오지 않을 것입니다.
당신의 질문에 대한 대답은 둘 다이지만 걱정은 절대적이지 않다는 것입니다. 그것들은 전적으로 주관적이거나 맥락 상 객관적입니다. 불행하게도 때로는 전적으로 주관적이거나 오히려 의견이 우선적이고 지배적입니다.
일반적으로 중첩 루프가 필요하거나 다음 단계처럼 보이는 경우 의도적으로 수행하지 않는 것이 가장 좋습니다. 그러나 의심이 남아 있으면 나중에 검토해야합니다.
경험의 또 다른 규칙은 항상 카디널리티를 확인하고 스스로에게 물어보아야한다는 것입니다.이 루프는 문제가 될 것입니다. 이전 예에서 나는 도시를 갔다. 테스트를 위해 10 개 도시 만 통과 할 수 있지만 실제 사용에서 예상되는 합리적인 최대 도시 수는 얼마입니까? 그런 다음 대륙에 대해서도 똑같이 곱할 수 있습니다. 루프를 고려할 때 특히 동적으로 (가변) 여러 번 반복하여 줄 아래로 변환하는 것을 고려하는 것이 일반적입니다.
항상 먼저 작동하는 것을 수행하십시오. 최적화 기회를 확인할 수있는 방법은 최적화 된 솔루션을 가장 손쉬운 작업과 비교하여 예상되는 이점을 얻었음을 확인할 수 있습니다. 측정이 시작되고 YAGNI 또는 많은 시간 낭비와 마감 기한을 놓치기 전에 너무 빨리 최적화하는 데 많은 시간을 할애 할 수 있습니다.
n
, 그것의 기하학적 또는 다항식 .