【问题标题】:Can you write a permutation function just as elegantly in C#?你能像在 C# 中一样优雅地编写置换函数吗?
【发布时间】:2009-04-16 13:53:49
【问题描述】:

我非常喜欢这个 6 行解决方案,并试图在 C# 中复制它。基本上,它会置换数组的元素:

def permute(xs, pre=[]):
  if len(xs) == 0:
     yield pre
  for i, x in enumerate(xs):
     for y in permute(xs[:i] + xs[i+1:], pre + [x]):
        yield y

【问题讨论】:

  • 缺少元组和相当庞大的列表解析(即 LINQ)几乎肯定会使 C# 代码更加笨拙,但绝对可以将上面的代码逐行翻译成 C#。
  • 这其实很难读..

标签: c# python algorithm


【解决方案1】:

嗯,我可能不会这样写,但是:

static IEnumerable<T[]> Permute<T>(this T[] xs, params T[] pre) {
    if (xs.Length == 0) yield return pre;
    for (int i = 0; i < xs.Length; i++) {
        foreach (T[] y in Permute(xs.Take(i).Union(xs.Skip(i+1)).ToArray(), pre.Union(new[] { xs[i] }).ToArray())) {
            yield return y;
        }
    }
}

请回复您的评论;我对这个问题不是完全清楚;如果您的意思是“为什么这有用?” - 除其他外,有一系列蛮力场景,您希望尝试不同的排列 - 例如,对于像旅行销售人员这样的小订单问题(不足以保证更复杂的解决方案),您可能想检查是否最好走 {base,A,B,C,base}, {base,A,C,B,base},{base,B,A,C,base} 等。

如果您的意思是“我将如何使用这种方法?” - 未经测试,但类似:

int[] values = {1,2,3};
foreach(int[] perm in values.Permute()) {
   WriteArray(perm);
}

void WriteArray<T>(T[] values) {
    StringBuilder sb = new StringBuilder();
    foreach(T value in values) {
        sb.Append(value).Append(", ");
    }
    Console.WriteLine(sb);
}

如果您的意思是“它是如何工作的?” - 迭代器块 (yield return) 本身就是一个复杂的主题 - Jon 有一个免费的章节 (6) in his book。其余代码与您最初的问题非常相似 - 只是使用 LINQ 来提供与 + (对于数组)的道德等价物。

【讨论】:

  • 干得好!但是,您缺少一行 :) 您将如何编写它?只是好奇,因为我也更喜欢可读性而不是简洁:)))
  • 嗯,它将涉及更少的 LINQ,并且可能涉及两种方法(一种用于公共调用者,一种用于递归)和一个重用缓冲区。或者我会查看另一篇文章中的链接;-p
  • Marc,我“非常”喜欢你的解决方案,但是,我不明白它在做什么......你或其他人能否解释如何对数组进行置换以及它如何使用?非常感谢
【解决方案2】:

C# 有一个 yield 关键字,我认为它的工作方式与您的 python 代码所做的几乎相同,因此获得大部分直接翻译应该不会太难。

然而,这是一个递归解决方案,所以尽管它很简洁,但它不是最理想的。我个人并不了解所涉及的所有数学,但为了获得高效的数学排列,您想使用factoradics。这篇文章应该会有所帮助:
http://msdn.microsoft.com/en-us/library/aa302371.aspx

[更新]:另一个答案提出了一个很好的观点:如果您只是使用排列来进行洗牌,那么还有更好的选择。具体来说,Knuth/Fisher-Yatesshuffle

【讨论】:

    【解决方案3】:

    虽然您不能在保持简洁的同时移植它,但您可以非常接近。

    public static class IEnumerableExtensions
    {
      public static IEnumerable<IEnumerable<T>> Permutations<T>(this IEnumerable<T> source)
      {
        if (source == null)
          throw new ArgumentNullException("source");
    
        return PermutationsImpl(source, new T[0]);
      }
    
      private static IEnumerable<IEnumerable<T>> PermutationsImpl<T>(IEnumerable<T> source, IEnumerable<T> prefix)
      {
        if (source.Count() == 0)
          yield return prefix;
    
        foreach (var x in source)
          foreach (var permutation in PermutationsImpl(source.Except(new T[] { x }),
                                                       prefix.Union(new T[] { x }))))
            yield return permutation;
      }
    }
    

    【讨论】:

    • 使用 Equals 的方法对于任何有重复或空值的东西都是危险的。
    • 正则排列不会重复(应该检查),但你对空值是正确的。
    【解决方案4】:

    在一些 cmets 之后我必须承认这一点并不完全,但下面的代码可用于生成有限序列的随机排列。这是Fisher-Yates shuffle algorithm 的变体。该示例使用int 的序列,但您当然可以使用任何Enumerable&lt;T&gt;

    var ints = Enumerable.Range(0, 51);
    var shuffledInts = ints.OrderBy(a => Guid.NewGuid());
    

    您按一个随机值(在本例中为Guid)排序,该值实质上会排列您的列表。 NewGuid 是否是随机性的良好来源值得商榷,但它是一种优雅而紧凑的解决方案(尽管是另一个问题,但问题实际上是关于)。

    取自Jeff Atwood (Coding Horror)

    【讨论】:

    • 这如何执行任何排列...?
    • +1 试试... rwwilden 你也会用输出来做吗?
    • 如果您按随机值(在本例中为 Guid)排序,您实际上是在排列您的列表。
    • @rwwilden: permute != shuffle
    • shuffle 就像一个特殊的置换情况,因为你可以使用置换来准确地混洗(如果比使用专门的算法更慢)。甚至上面的维基百科文章也提到费舍尔-耶茨是“基于相同的原则”。
    猜你喜欢
    • 2013-10-17
    • 1970-01-01
    • 2011-10-27
    • 2021-06-19
    • 1970-01-01
    • 1970-01-01
    • 2022-08-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多