Найти все комбинации подстрок, которые складываются в заданную строку

Я пытаюсь создать структуру данных, которая содержит все возможные комбинации подстрок, которые складываются в исходную строку. Например, если строка "java" допустимые результаты будут "j", "ava", "ja", "v", "a", недопустимый результат будет "ja", "a" или "a", "jav"

мне было очень легко найти все возможные подстроки

    String string = "java";
    List<String> substrings = new ArrayList<>();
    for( int c = 0 ; c < string.length() ; c++ )
    {
        for( int i = 1 ; i <= string.length() - c ; i++ )
        {
            String sub = string.substring(c, c+i);
            substrings.add(sub);
        }
    }
    System.out.println(substrings);

и теперь я пытаюсь построить структуру, которая содержит только действительные подстрок. Но это не так просто. Я в тумане очень уродливый код, возиться с индексами и не заканчивать, скорее всего, на неправильном пути полностью. Есть намеки?

4 ответов


вот один подход:

static List<List<String>> substrings(String input) {

    // Base case: There's only one way to split up a single character
    // string, and that is ["x"] where x is the character.
    if (input.length() == 1)
        return Collections.singletonList(Collections.singletonList(input));

    // To hold the result
    List<List<String>> result = new ArrayList<>();

    // Recurse (since you tagged the question with recursion ;)
    for (List<String> subresult : substrings(input.substring(1))) {

        // Case: Don't split
        List<String> l2 = new ArrayList<>(subresult);
        l2.set(0, input.charAt(0) + l2.get(0));
        result.add(l2);

        // Case: Split
        List<String> l = new ArrayList<>(subresult);
        l.add(0, input.substring(0, 1));
        result.add(l);
    }

    return result;
}

выход:

[java]
[j, ava]
[ja, va]
[j, a, va]
[jav, a]
[j, av, a]
[ja, v, a]
[j, a, v, a]

похоже, что это проблема поиска композиции длины строки и использования этих композиций для создания подстрок. Таким образом, есть 2^n-1 композиции числа n, что может сделать его немного трудоемким для длинных строк...


вероятно, кто-то хотел бы другое решение, которое не рекурсивно и не требует памяти для хранения списка:

public static List<List<String>> substrings(final String input) {
    if(input.isEmpty())
        return Collections.emptyList();
    final int size = 1 << (input.length()-1); 
    return new AbstractList<List<String>>() {

        @Override
        public List<String> get(int index) {
            List<String> entry = new ArrayList<>();
            int last = 0;
            while(true) {
                int next = Integer.numberOfTrailingZeros(index >> last)+last+1;
                if(next == last+33)
                    break;
                entry.add(input.substring(last, next));
                last = next;
            }
            entry.add(input.substring(last));
            return entry;
        }

        @Override
        public int size() {
            return size;
        } 
    };
}

public static void main(String[] args) {
    System.out.println(substrings("java"));
}

выход:

[[java], [j, ava], [ja, va], [j, a, va], [jav, a], [j, av, a], [ja, v, a], [j, a, v, a]]

Он просто вычисляет следующую комбинацию на основе своего индекса.


на всякий случай, если кто-то будет искать тот же алгоритм в python, вот реализация в Python:

from itertools import combinations

def compositions(s):
    n = len(s)
    for k in range(n):
        for c in combinations(range(1, n), k):
            yield tuple(s[i:j] for i, j in zip((0,) + c, c + (n,)))

пример того, как это работает:

>>> for x in compositions('abcd'):
...     print(x)
('abcd',)
('a', 'bcd')
('ab', 'cd')
('abc', 'd')
('a', 'b', 'cd')
('a', 'bc', 'd')
('ab', 'c', 'd')
('a', 'b', 'c', 'd')

С небольшой модификацией вы можете создавать композиции в различном порядке:

def compositions(s):
    n = len(s)
    for k in range(n):
        for c in itertools.combinations(range(n - 1, 0, -1), k):
            yield tuple(s[i:j] for i, j in zip((0,) + c[::-1], c[::-1] + (n,)))

он даст вам это:

>>> for x in compositions('abcd'):
...     print(x)
('abcd',)
('abc', 'd')
('ab', 'cd')
('a', 'bcd')
('ab', 'c', 'd')
('a', 'bc', 'd')
('a', 'b', 'cd')
('a', 'b', 'c', 'd')

и с еще одним небольшим добавлением вы можете генерировать только указанное количество расколов:

def compositions(s, r=None):
    n = len(s)
    r = range(n) if r is None else [r - 1]
    for k in r:
        for c in itertools.combinations(range(n - 1, 0, -1), k):
            yield tuple(s[i:j] for i, j in zip((0,) + c[::-1], c[::-1] + (n,)))
>>> for x in compositions('abcd', 3):
...     print(x)
('ab', 'c', 'd')
('a', 'bc', 'd')
('a', 'b', 'cd')