如果我有一个如下序列(假设它是一个IEnumerable<T>
):
[A, B, C, D, E]
那么计算给定长度的所有可能(连续和非连续)subsequences的最简洁方法是什么?对结果集中的结果进行排序并不重要,但不应包含重复项。
e.g。如果我想计算长度为3的所有可能子序列,结果集将为:
[A, B, C]
[A, B, D]
[A, B, E]
[A, C, D]
[A, C, E]
[A, D, E]
[B, C, D]
[B, C, E]
[B, D, E]
[C, D, E]
为了记录,下面接受的答案给了我一个很好的起点,这里是我已经使用的代码,它被更新为使用一些新的.NET 3.5扩展方法:
public static IEnumerable<IEnumerable<T>> Subsequences<T>(
this IEnumerable<T> source,
int count)
{
if (count == 0)
{
yield return Enumerable.Empty<T>();
}
else
{
var skip = 1;
foreach (var first in source)
{
foreach (var rest in source.Skip(skip).Subsequences(count - 1))
{
yield return Enumerable.Repeat(first, 1).Concat(rest);
}
skip++;
}
}
}
答案 0 :(得分:5)
我在IanG的PermuteUtils
课上取得了成功:
char[] items = new char[] { 'A', 'B', 'C', 'D', 'E' };
foreach (IEnumerable<char> permutation in PermuteUtils.Permute(items, 3)) {
Console.Write("[");
foreach (char c in permutation) {
Console.Write(" " + c);
}
Console.WriteLine(" ]");
}
结果:
[ A B C ] [ A B D ] [ A B E ] [ A C B ] [ A C D ] [ A C E ] [ A D B ] [ A D C ] [ A D E ] [ A E B ] [ A E C ] [ A E D ] [ B A C ] [ B A D ] [ B A E ] [ B C A ] [ B C D ] [ B C E ] [ B D A ] [ B D C ] ...
答案 1 :(得分:1)
类似的东西:
static void Main()
{
string[] data = { "A", "B", "C", "D", "E" };
WalkSubSequences(data, 3);
}
public static void WalkSubSequences<T>(IEnumerable<T> data, int sequenceLength)
{
T[] selected = new T[sequenceLength];
WalkSubSequences(data.ToArray(), selected, 0, sequenceLength);
}
private static void WalkSubSequences<T>(T[] data, T[] selected,
int startIndex, int sequenceLength)
{
for (int i = startIndex; i + sequenceLength <= data.Length; i++)
{
selected[selected.Length - sequenceLength] = data[i];
if (sequenceLength == 1)
{
ShowResult(selected);
}
else
{
WalkSubSequences(data, selected, i + 1, sequenceLength - 1);
}
}
}
private static void ShowResult<T>(T[] selected)
{
StringBuilder sb = new StringBuilder();
sb.Append(selected[0]);
for (int j = 1; j < selected.Length; j++)
{
sb.Append(';').Append(selected[j]);
}
Console.WriteLine(sb.ToString());
}
答案 2 :(得分:0)
我建议使用递归算法。对不起,但是我已经有一段时间了,因为我在C#中做了什么,所以我只会在这里给出伪代码。
function allPossible(iterator, length, currSubSeq, allResults) {
// Add the current sub sequence to the results if it is the correct length.
if (currSubSeq.length == length) {
copy = currSubSeq.copy();
allResults.add(copy);
}
// If it is too long, return early.
else if (currSubSeq.length > length) {
return allResults;
}
// Get the next item from the iterator and handle both cases:
// I.E. when it is, and when it isn't in a sub sequence.
item = iterator.getNext();
allPossible(iterator, currSubSeq, allResults);
currSubSeq.add(item);
allPossible(iterator, currSubSeq, allResults);
return allResults;
}
然后通过调用allPossible
调用所有可能的子序列,其中iterator
生成原始序列中的所有元素,length
您想要子序列,空序列currSubSeq
的项目,以及allResults
的空序列序列。我假设所有参数都是传递引用语义。很抱歉,我无法为您提供正确的C#实现,但我相信您已经知道了我的算法草图并将其转换为代码。
最后一件事。因为此算法是递归的,所以如果在具有大length
参数的非常长的序列上运行它,则可能会发生堆栈溢出,因为堆栈使用为O(2 ^ N),其中N = length
。我不认为这是一个大问题,因为该算法具有O(2 ^ N)运行时,因为问题的性质,所以你不应该尝试用足够大的length
来运行它。无论如何都要溢出堆栈!
CAVEAT 我实际上没有测试过这个伪代码,所以可能会有一些我没有想过的微妙。
答案 3 :(得分:0)
这是一个将状态存储在bool数组中的解决方案。它的工作原理是在每个Next()
调用上创建以下状态(n = 5,k = 3)。
1 1 1 . . Move last 1 right once. 1 1 . 1 . Move last 1 right once. 1 1 . . 1 Move last 1 right once. 1 . 1 1 . Move the second last 1 right once and all 1s from the right back. 1 . 1 . 1 Move last 1 right once. 1 . . 1 1 Move the second last 1 right once (and all 1s from the right back.) . 1 1 1 . Move the third last 1 right once and all 1s from the right back. . 1 1 . 1 Move last 1 right once. . 1 . 1 1 Move the second last 1 right once (and all 1s from the right back.) . . 1 1 1 Move the third last 1 right once (and all 1s from the right back.)
然后可以使用此状态从每个州的所提供序列中选择相应的项目。
首先是初始化。
public static Boolean[] Initialize(Int32 n, Int32 k)
{
return Enumerable.Concat(Enumerable.Repeat(true, k),
Enumerable.Repeat(false, n - k)).ToArray();
}
移动到下一个组合的代码(子序列)。
public static Boolean Next(this Boolean[] list)
{
Int32 lastOneIndex = Array.LastIndexOf(list, true);
if (lastOneIndex == -1)
{
return false; // All zeros. 0000000
}
else if (lastOneIndex < list.Length - 1)
{
// Move the last one right once. 1100X00 => 11000X0
list.MoveBlock(lastOneIndex, lastOneIndex, lastOneIndex + 1);
}
else
{
Int32 lastZeroIndex = Array.LastIndexOf(list, false, lastOneIndex);
if (lastZeroIndex == -1)
{
return false; // All ones. 1111111
}
else
{
Int32 blockEndIndex = Array.LastIndexOf(list, true, lastZeroIndex);
if (blockEndIndex == -1)
{
// Move all ones back to the very left. 0000XXX => XXX0000
list.MoveBlock(lastZeroIndex + 1, lastOneIndex, 0);
return false; // Back at initial position.
}
else
{
// Move the block end right once. 11X0011 => 110X011
list.MoveBlock(blockEndIndex, blockEndIndex, blockEndIndex + 1);
// Move the block of ones from the very right back left. 11010XX => 1101XX0
list.MoveBlock(lastZeroIndex + 1, lastOneIndex, blockEndIndex + 2);
}
}
}
return true;
}
最后一些辅助方法。
public static void MoveBlock(this Boolean[] list, Int32 oldStart, Int32 oldEnd, Int32 newStart)
{
list.ClearBlock(oldStart, oldEnd);
list.SetBlock(newStart, newStart + oldEnd - oldStart);
}
public static void SetBlock(this Boolean[] list, Int32 start, Int32 end)
{
list.SetBlockToValue(start, end, true);
}
public static void ClearBlock(this Boolean[] list, Int32 start, Int32 end)
{
list.SetBlockToValue(start, end, false);
}
public static void SetBlockToValue(this Boolean[] list, Int32 start, Int32 end, Boolean value)
{
for (int i = start; i <= end; i++)
{
list[i] = value;
}
}
使用字符串而不是列表的用法示例。
var sequence = "ABCDE";
var state = Initialize(sequence.Count(), 5);
do
{
Console.WriteLine(new String(sequence.Where((_, idx) => state[idx]).ToArray()));
}
while (state.Next());