Вычисление всех возможных подпоследовательностей данной длины (C#)

Если у меня есть последовательность следующим образом (скажем, это IEnumerable):

[A, B, C, D, E]

Затем, что самый чистый путь состоит в том, чтобы вычислить всех возможных (непрерывный и ненепрерывный) подпоследовательности данной длины? Упорядочивание результатов в наборе результатов не важно, но это не должно включать дубликаты.

например, Если бы я хочу вычислить все возможные подпоследовательности длины 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> Subsequences(
    this IEnumerable source, 
    int count)
{
    if (count == 0)
    {
        yield return Enumerable.Empty();
    }
    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++;
        }
    }
}

5
задан Greg Beech 21 August 2012 в 07:34
поделиться

4 ответа

Я добился успеха с 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 ]
...
5
ответ дан 14 December 2019 в 13:46
поделиться

Что-то вроде:

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());
}
1
ответ дан 14 December 2019 в 13:46
поделиться

Вот решение, сохраняющее состояние в массиве логических значений. Он работает, создавая следующие состояния при каждом вызове 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());
0
ответ дан 14 December 2019 в 13:46
поделиться

Я бы предложил для этого рекурсивный алгоритм. Мне очень жаль, но я давно ничего не делал на 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 с итератор , который производит все элементы в исходной последовательности, длину , которую вы хотите использовать в подпоследовательностях, пустую последовательность элементов для currSubSeq и пустую последовательность последовательностей элементов для allResults . Я предполагаю семантику передачи по ссылке для всех параметров. Извините, что я не смог дать вам правильную реализацию C #, но я уверен, что вы знаете более чем достаточно, чтобы взять мой набросок алгоритма и превратить его в код.

И последнее. Поскольку этот алгоритм рекурсивен, у вас может возникнуть переполнение стека, если вы запустите его в очень длинной последовательности с большим параметром length , поскольку используется стек O (2 ^ N), где N = length . Я не думаю, что это большая проблема, потому что алгоритм имеет время выполнения O (2 ^ N) из-за характера проблемы, поэтому вам не следует пытаться запустить его с достаточно большой длиной ], чтобы все равно переполнить стек!

CAVEAT Я на самом деле не тестировал этот псевдокод, поэтому может быть что-то тонкое, о чем я не подумал.

0
ответ дан 14 December 2019 в 13:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: