2017-12-01 10 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クイーン問題に対する有効な解決策 です。これが失敗

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 closureときfor loopで使用されるイテレータ変数が評価される方法です。ループ内のToList()を削除すると、subIEnumerableは、返信文return sub.Select(x => x.ToPosition()).ToList();subがマテリアライズされた場合にのみ評価されます。このとき、ループ変数iの配列範囲外であり、nの値(標準的なチェスボード上の例えば8)を有するであろう。あなたはすぐにListをマテリアライズするときiの値は、次の反復(ToListが具体化)前に使用されているので

しかし、副作用は、発生しません。

作品:

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ループを避けるコード

OPのSolveNQueens方法ではなく、再帰よりも、徐々にsubを変更するループを使用しますが、ためにも、foreachの範囲に置き換えることができます:ReSharperのは、その後、再しています

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

左倍と書く:いずれかの方法で

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

ありがとうとても!私はクロージャがこのようにC#で行われることに驚いています。 –

+0

多くの人がこれを欠陥とみなし、MSはforeachループで同様の問題を修正するために大きな変更を加えました。問題はforループに残っていますが、あなたの場合は、ループのFPパラダイムがRangeとしてよく表現されます。私は編集します。 – StuartLC

関連する問題