2017-12-01 4 views
3

궁금 왜 라인for 루프에서 ToList 메서드를 사용하지 않으면 왜 실패합니까?

"sub = sub.SelectMany(x => x.Next(i)).ToList();" 

"sub = sub.SelectMany(x => x.Next(i));" 

로 변경하면 나는 오류를 얻을

행 48 : System.IndexOutOfRangeException : 인덱스의 범위를 벗어난 배열 "을 입력하면 SolveNQueens 메서드에 4가 입력됩니다.

나는 게으른 평가와 관련이 있다고 생각합니다.

전체 코드 샘플은 아래에 나열되어 있으며 n queens 문제에 대한 유효한 해결책입니다. 이것이 실패

public class Solution { 
     public IList<IList<string>> SolveNQueens(int n) 
     { 
      IEnumerable<PartialQueens> sub = new List<PartialQueens>(){ 
       new PartialQueens(n)}; 

      for(int i=0;i<n;i++) 
      { 
       sub = sub.SelectMany(x => x.Next(i)).ToList(); 
      } 

      return sub.Select(x => x.ToPosition()).ToList(); 
     } 
    } 

    public class PartialQueens 
    { 
    public byte FREE = 0; 
    public byte BLOCKED = 1; 
    public byte QUEEN = 2; 

    public byte[,] fill; 
    int n; 

    public PartialQueens(int n) 
    { 
     this.n = n; 
     fill = new byte[n,n]; 
    } 

    public PartialQueens(byte[,] fill, int n) 
    { 
     this.fill = fill; 
     this.n = n; 
    } 

    public PartialQueens Fill(int row, int column) 
    { 
     byte[,] newFill = fill.Clone() as byte[,]; 

     newFill[row,column] = QUEEN; 

     Action<int,int> f = (x,y) => 
     { 
      if(y >= 0 && y < n) 
       newFill[x,y] = BLOCKED; 
     }; 

     for(int i=1;i<n-row;i++) 
     { 
      f(row+i,column+i); 
      f(row+i,column-i); 
      f(row+i,column); 
     } 

     return new PartialQueens(newFill,n); 
    } 

    public IEnumerable<PartialQueens> Next(int row) 
    { 
     for(int j=0;j<n;j++) 
     {    
      if(fill[row,j] == FREE) 
       yield return Fill(row,j); 
     } 
    } 

    public IList<string> ToPosition() 
    { 
     return Enumerable.Range(0,n).Select(i => ConvertRow(i)).ToList(); 
    } 

    public string ConvertRow(int i) 
    { 
     StringBuilder builder = new StringBuilder(); 

     for(int j=0;j<n;j++) 
     { 
      if(fill[i,j] == QUEEN) 
       builder.Append("Q"); 
      else 
       builder.Append("."); 
     } 

     return builder.ToString(); 
    } 
} 

답변

3

이유는 captured by a closurefor loop에 사용 반복자 변수가 평가되는 방식 때문에이다. 루프 내부에서 ToList()을 제거하면 subIEnumerablesub이 return 문 return sub.Select(x => x.ToPosition()).ToList();에서 구체화 될 때만 평가됩니다. 현재 for 루프 변수 i의 값은 n (예 : 표준 체스 보드의 경우 8)이며 배열 범위를 벗어납니다.

그러나 List을 즉시 구체화하면 다음 반복 (ToList이 구체화) 전에 i의 값이 사용되므로 부작용이 발생하지 않습니다.

작품 :

for (int i = 0; i < n; i++) 
{ 
    // Materialized here so `i` evaluated immediately 
    sub = sub.SelectMany(x => x.Next(i)).ToList(); 
} 

브로큰 :

for (int i = 0; i < n; i++) 
{ 
    var loop = i; 
    sub = sub.SelectMany(x => x.Next(loop)); // No To List - lazy evaluation 
} 

:

for (int i = 0; i < n; i++) 
{ 
    sub = sub.SelectMany(x => x.Next(i)); 
} 
return sub.Select(x => x.ToPosition()).ToList(); // `i` evaluated here 

fix에 루프 변수 평가 문제에 대한 명시 적 반복자 변수의 현재 값을 캡처 할 수 있습니다 Re : FP 패러다임에서 for 루프 방지하기 코드

영업 이익의 SolveNQueens 방법은 점진적이 아니라 재귀보다는 sub을 변경하는 루프를 사용뿐만 아니라 foreach는과 범위로 대체 할 수의 :

foreach(var i in Enumerable.Range(0, n)) 
{ 
    sub = sub.SelectMany(x => x.Next(i)); 
} 

ReSharper에서 다음 재에 제공 좌측으로 스크롤 해주기

sub = Enumerable.Range(0, n) 
    .Aggregate(sub, (current, i) => current.SelectMany(x => x.Next(i))); 

어느 쪽이든을하는 내부 루프 for 반복자 가변 지연의 평가에 결함이 회피된다.

+0

[이 문제에 대한 추가 정보] (https://blogs.msdn.microsoft.com/ericlippert/2009/11/12/closing-over-the-loop-variable-considered-harmful/) – StuartLC

+0

감사합니다. 흠뻑! 나는 closure가 C#에서 이런 방식으로 수행된다는 것에 놀랐다. –

+0

많은 사람들이이를 결함으로 간주하고 MS는 'foreach'루프에서 유사한 문제를 수정하기 위해 큰 변화를 겪었습니다. 문제는 for 루프에 있지만, 루프에 대한 FP 패러다임은 Range로 더 잘 표현 될 수 있습니다. 내가 편집 할게. – StuartLC

관련 문제