如何使用查询语法来创建排列
本文关键字:创建 排列 语法 何使用 查询 | 更新日期: 2023-09-27 18:16:36
我试着写一个方法,返回一个给定的枚举尽可能简单的排列。代码:
using System.Collections.Generic;
public static partial class Permutable {
static IEnumerable<IEnumerable<T>> PermuteIterator<T>(
IEnumerable<T> source, int offset) {
var count=0;
foreach(var dummy in source)
if(++count>offset)
foreach(
var sequence in
Permutable.PermuteIterator(
source.Exchange(offset, count-1), 1+offset)
)
yield return sequence;
if(offset==count-1)
yield return source;
}
public static IEnumerable<IEnumerable<T>> AsPermutable<T>(
this IEnumerable<T> source) {
return Permutable.PermuteIterator(source, 0);
}
public static IEnumerable<T> Exchange<T>(
this IEnumerable<T> source, int index1, int index2) {
// exchange elements at index1 and index2
}
}
由于代码在迭代器块内简化了,我试图使其简单地成为LINQ的单个查询表达式。
在嵌套的foreach
中存在递归,甚至在foreach
之外可能存在另一个yield;这是我用查询语法重写它的困难部分。
我读过这个答案:
c#字符串排列
但我想这不是我的解决方案…
我尝试了各种方法,认为这不是那么容易做到的。我怎样才能完成呢?
(Exchange
方法是另一个问题,我问了一个问题:
如何通过只交互一次来交换枚举项?
但我想这不是这里的问题…)
由于您正在寻找利用现有LINQ查询操作符而不是使用迭代器块的答案,这里有一个。请注意,它不会像其他解决方案那样有效;使用这些查询操作符并不像Eric Lippert的解决方案那样高效。(虽然短了很多)
还请注意,由于此解决方案使用SelectMany
和Where
的过载来接受索引,因此需要使用方法语法而不是查询语法调用这些操作符,并且其他几个操作符没有查询语法等效。我可以将一个Select
更改为查询语法,但为了一致性起见,我没有这样做。
public static IEnumerable<IEnumerable<T>> Permuatations<T>(
this IEnumerable<T> source)
{
var list = source.ToList();//becase we iterate it multiple times
return list.SelectMany((item, i) => list.Where((_, index) => index != i)
.Permuatations()
.Select(subsequence => new[] { item }.Concat(subsequence)))
.DefaultIfEmpty(Enumerable.Empty<T>());
}
那么,讨论一下这是做什么的
首先遍历源序列;对于该序列中的每个项,它创建一个序列,该序列与源序列一样,但去掉了"当前项"。(这是list.Where
方法)。
接下来,它(递归地)获得该子序列的所有排列。
之后,它在每个子序列的开头加上"被删除"的项。
所有这些子序列都被平铺在一起,因为它们都在SelectMany
内。
DefaultIfEmpty
的存在是为了确保外部序列不为空。对空序列进行置换会产生一个序列,其中包含一个空序列。这是有效的递归操作的"基本情况"。
EDIT 1:
无递归的单行解
我已经重新创建了核心方法(从以前的解决方案在这个答案下面),所以现在它不再是递归了。现在很容易从这里得到一个单行的解决方案。
我不得不使用Enumerable
方法和扩展方法来做到这一点。如果没有这些,我认为这是不可能做到的。
class Permutator
{
private static IEnumerable<IEnumerable<int>> CreateIndices(int length)
{
var factorial = Enumerable.Range(2, length - 1)
.Aggregate((a, b) => a * b);
return (from p in Enumerable.Range(0, factorial)
// creating module values from 2 up to length
// e.g. length = 3: mods = [ p%2, p%3 ]
// e.g. length = 4: mods = [ p%2, p%3, p%4 ]
let mods = Enumerable.Range(2, length - 1)
.Select(m => p % m).ToArray()
select (
// creating indices for each permutation
mods.Aggregate(
new[] { 0 },
(s, i) =>
s.Take(i)
.Concat(new[] { s.Length })
.Concat(s.Skip(i)).ToArray())
));
}
public static IEnumerable<IEnumerable<T>> Get<T>(IEnumerable<T> items)
{
var array = items.ToArray();
return from indices in CreateIndices(array.Length)
select (from i in indices select array[i]);
}
}
现在最终解
结果是这个怪物:
class Permutator
{
public static IEnumerable<IEnumerable<T>> Get<T>(IEnumerable<T> items)
{
return
from p in Enumerable.Range(0,
Enumerable.Range(2, items.Count() - 1)
.Aggregate((a, b) => a * b))
let mods = Enumerable.Range(2, items.Count() - 1)
.Select(m => p % m).ToArray()
select mods.Aggregate(
items.Take(1).ToArray(),
(s, i) =>
s.Take(i)
.Concat(items.Skip(s.Length).Take(1))
.Concat(s.Skip(i)).ToArray());
}
}
之前的解决方案我已经创建了一些可能是你正在寻找的东西:
class Permutator
{
private static IEnumerable<IEnumerable<int>> CreateIndices(int length)
{
return (from p in Enumerable.Range(0, length)
select (
from s in Permutator.CreateIndices(length - 1)
.DefaultIfEmpty(Enumerable.Empty<int>())
select s.Take(p)
.Concat(new[] { length - 1 })
.Concat(s.Skip(p))
))
.SelectMany(i => i);
}
public static IEnumerable<IEnumerable<T>> Get<T>(IEnumerable<T> items)
{
var array = items.ToArray();
return from indices in CreateIndices(array.Length)
select (from i in indices select array[i]);
}
}
使用示例:
var items = new[] { "0", "1", "2" };
var p = Permutator.Get(items);
var result = p.Select(a=>a.ToArray()).ToArray();
工作原理
核心是CreateIndices
方法。它为每个排列创建一个包含源元素索引的序列。
最好用一个例子来解释:
CreateIndices(0);
// returns no permutations
CreateIndices(1);
// returns 1 permutation
// [ 0 ]
CreateIndices(2);
// returns 2 permutations
// [ 1, 0 ]
// [ 0, 1 ]
CreateIndices(3);
// returns 6 permutations
// [ 2, 1, 0 ]
// [ 2, 0, 1 ]
// [ 1, 2, 0 ]
// [ 0, 2, 1 ]
// [ 1, 0, 2 ]
// [ 0, 1, 2 ]
这是一个递归方法,仅基于可枚举扩展和LINQ语法查询。
递归的思想是每一层都建立在前一层的基础上。
CreateIndices(n)
将元素n-1
添加到CreateIndices(n-1)
返回的所有可用位置上。
递归的根是CreateIndices(0)
,返回一个空的排列集。
一步步解释:CreateIndices(3)
1. 让我们从创建CreateIndices(0)
:
- 空
2. 然后CreateIndices(1)
的结果:
- 将元素
0
(n-1)添加到前面每个排列的位置0
[0]
3.则CreateIndices(2)
- 将元素
1
(n-1)添加到前面每个排列的位置0
[1,0] [/span>> - 将元素
1
(n-1)添加到前面每个排列的位置1
[0,1]
4. 则CreateIndices(3)
- 将元素
2
(n-1)添加到前面每个排列的位置0
[2,1,0]
[2,0,1] [/span>> - 将元素
2
(n-1)添加到前面每个排列的位置1
[1,2,0]
[0,2,1] - 将元素
2
(n-1)添加到前面每个排列的位置2
[1,0,2]
[0,1,2] [/span>>
接下来会发生什么
现在我们有了每个排列的索引,我们可以使用它们来构建值的实际排列。这就是通用的Get
方法所做的。
还请注意,Get
方法是唯一一个将源序列物化到数组中的方法。CreateIndices
只是一个枚举器,没有任何真正的对象…因此,您只需要在枚举序列时支付成本,而在调用Get
时,您只需支付创建源序列数组的成本。
这解释了为什么在示例中调用Get
之后,我必须具体化结果,以便我们可以使用它:
var items = new[] { "0", "1", "2" };
var p = Permutator.Get(items); // pay to create array from source elements
var result = p.Select(a => a.ToArray() // pay to create arrays for each of the permutations
).ToArray(); // pay to get the permutations
这允许我们只支付一半的成本,如果我们只列举一半的排列。
虽然我年纪大了,但在没有必要的时候,我从不喜欢递归。
因为无论如何都需要迭代源代码,所以我简单地将其存储在一个数组中。我跟踪一个' swappers '数组,它告诉程序用什么来交换什么;索引是交换的源位置,索引是目标位置。
如果你喜欢,你也可以做子排列。
如果你真的对大量的痛苦感到舒服,我想你也可以将交换更改为复杂的Exchange IEnumerable;我想这是一种增加复杂性的好方法,同时让一切变得更慢。: -)
我还重用了数组;每次重复所有内容似乎是愚蠢的,当'src'很大时,这会为您节省大量复制数据和递归的时间。这很有效率。
public static IEnumerable<IEnumerable<T>> Permutations<T>(this IEnumerable<T> src)
{
T[] array = src.ToArray();
// Initialize
int[] swappings = new int[array.Length];
for (int j = 0; j < array.Length; ++j)
{
swappings[j] = j;
}
yield return array;
int i = swappings.Length - 2;
while (i >= 0)
{
int prev = swappings[i];
Swap(array, i, prev);
int next = prev + 1;
swappings[i] = next;
Swap(array, i, next);
yield return array;
// Prepare next
i = swappings.Length - 1;
while (i >= 0 && swappings[i] == array.Length - 1)
{
// Undo the swap represented by permSwappings[i]
Swap(array, i, swappings[i]);
swappings[i] = i;
i--;
}
}
}
private static void Swap<T>(T[] array, int i, int next)
{
var tmp = array[i];
array[i] = array[next];
array[next] = tmp;
}