2014-05-19 37 views
2

我正试图为访问排列提供我的客户多种方式。我创建了下面的代码,在每个探视执行Action<T[]> output为什么编译器跳过这个方法?

public void Permute<T>(T[][] sets, Action<T[]> output) 
    { 
     Permute(sets, 0, new T[sets.Length], output); 
    } 

    private void Permute<T>(T[][] sets, int set, T[] permutation, Action<T[]> output) 
    { 
     for (int i = 0; i < sets[set].Length; ++i) 
     { 
      permutation[set] = sets[set][i]; 

      if (set < (sets.Length - 1)) 
       Permute(sets, set + 1, permutation, output); 
      else 
       output(permutation); 
     } 
    } 

和它的作品让我感动到下一个方式访问排列就是用IEnumerable<int[]>yield return模式。这是我的实施:

public IEnumerable<int[]> Permute(int[][] sets) 
    { 
     return Permute(sets, 0, new int[sets.Length]); // <--skips this 
    } 

    private IEnumerable<int[]> Permute(int[][] sets, int set, int[] permutation) 
    { 
     for (int i = 0; i < sets[set].Length; ++i) 
     { 
      permutation[set] = sets[set][i]; 

      if (set < (sets.Length - 1)) 
       Permute(sets, set + 1, permutation); 
      else 
       yield return permutation; 
     } 
    } 

但这不起作用。编译器跳过指定的代码行而不尝试执行它。

有人可以请我解释一下如何修改提供的代码,使它能够启用IEnumerable<int[]>yield return模式?

下面是客户端代码来测试它(我使用NUnit)

[Test] 
    public void PermuteThreeDifferentSetsUsingTheirIndexValues() 
    { 
     var stopwatch = new Stopwatch(); 
     stopwatch.Start(); 
     var indexSets = new[] 
          { 
           new[] {0, 1, 2}, 
           new[] {0, 1, 2}, 
           new[] {0, 1, 2}, 
          }; 

     var results = _generator.Permute(indexSets); 

     foreach (var result in results) 
     { 
      _permCounter++; 
      Console.Write(result[0]); 
      Console.Write(" "); 
      Console.Write(result[1]); 
      Console.Write(" "); 
      Console.Write(result[2]); 
      Console.WriteLine(); 
     } 
     stopwatch.Stop(); 

     Console.WriteLine("Permutation Visitor Elapsed Ticks: " + stopwatch.ElapsedTicks); 

     Assert.AreEqual(27, _permCounter); 
    } 

我的直觉是,编译器不开心,我利用递归的返回值是。但是,这只是一种预感。先谢谢你。

+3

我的猜测是,它确实* *工作,它只是不右看看在调试器中,因为''yield'创建return'的'IEnumerable'的懒惰执行。尝试在您生成的IEnumerable上调用'ToList'来强制执行,看看会发生什么。另请参阅http://brianreiter.org/2011/01/14/ienumerable-is-lazy-and-thats-cool/ –

+0

我不想转换ToList,因为可能有大量的项目和溢出RAM。 – sapbucket

+1

你只需要做足够长的时间就可以证明它确实有效。 :)你不必永远离开ToList()。调用ToList()将允许您在调试器中查看整个迭代。 –

回答

3

这就是它被设计成可以工作的原理,以及大量的代码使得自由使用:IEnumerable<T>的实现被允许是懒惰的。你的Permute不直接做任何工作:它返回一个对象,一旦你开始迭代它,开始做一些工作,并且只能确定第一个结果。当你的循环接着请求下一个项目时,你的函数将继续,但是直到它可以确定第二个结果。

这对代码如enumerable.Where(some predicate).First()非常有用,因为它避免了在找到第一个结果后评估谓词。

在最外层的函数中,您遍历_generator.Permute(indexSets)的结果,但在该函数内部,调用Permute(sets, set + 1, permutation)并放弃结果(如问题中所述)。既然你不使用这个递归调用的结果,效果就好像这个递归调用从不发生。

一般来说,当你想递归调用你自己的迭代函数时,你需要每个结果yield return。一个愚蠢的例子:

IEnumerable<int> f(int n) { 
    if (n > 0) 
     foreach (var i in f(n - 1)) 
      yield return i; 
    for (int i = 0; i != n; i++) 
     yield return i; 
} 
+0

再次 - 正如我刚刚对我的客户代码中的Mike发表的评论 - 我正在用foreach迭代结果 - 是不是应该让yield return? – sapbucket

+0

@sapbucket啊,我现在看到的,会编辑。 – hvd