Как найти подпоследовательность в строке

def is_subseq(x, y):
    it = iter(y)
    return all(any(c == ch for c in it) for ch in x)

assert is_subseq('india', 'indonesia')
assert is_subseq('oman', 'romania')
assert is_subseq('mali', 'malawi')
assert not is_subseq('mali', 'banana')
assert not is_subseq('ais', 'indonesia')
assert not is_subseq('ca', 'abc')

Also works for any iterables:

assert is_subseq(['i', 'n', 'd', 'i', 'a'],
                 ['i', 'n', 'd', 'o', 'n', 'e', 's', 'i', 'a'])

UPDATE

Stefan Pochmann suggested this.

def is_subseq(x, y):
    it = iter(y)
    return all(c in it for c in x)

Both versions uses iterators; Iterator yields items that was not yielded in previous iteration.

For example:

>>> it = iter([1,2,3,4])
>>> for x in it:
...     print(x)
...     break
...
1
>>> for x in it:  # `1` is yielded in previous iteration. It's not yielded here.
...     print(x)
...
2
3
4

0 / 0 / 0

Регистрация: 19.11.2017

Сообщений: 6

1

Строки: найти все подпоследовательности, не учитывая повторения

17.01.2019, 20:43. Показов 1827. Ответов 6


Студворк — интернет-сервис помощи студентам

Всем привет. Столкнулся с задачей, не могу запрогать свои мысли. Надеюсь на вашу помощь.

Подпоследовательностью строки s называется строка, которую можно получить из s удалением произвольного числа символов, не обязательно идущих по порядку.
А-ля, у строки «abba» такие подпоследовательности:» «a», «b», «ab», «ba», «bb», «abb», «bba», «abba», «aa», «aba». На вход дается строка s, и нужно найти все её подпоследовательности, не учитываю повторения (типа если одну и ту же подпоследовательность можно получить несколькими способами).

Заранее всем спасибо!



0



610 / 415 / 151

Регистрация: 11.01.2019

Сообщений: 1,746

17.01.2019, 22:55

2

Комбинаторная задача. Если сроки не горят, завтра могу решить (сегодня уже баиньки охота))))



0



0 / 0 / 0

Регистрация: 19.11.2017

Сообщений: 6

19.01.2019, 15:56

 [ТС]

3

Ну так что? Сможешь помочь?



0



610 / 415 / 151

Регистрация: 11.01.2019

Сообщений: 1,746

19.01.2019, 16:02

4

Цитата
Сообщение от bitxzibit3
Посмотреть сообщение

Ну так что? Сможешь помочь?

Да, конечно. Я просто ждал, пока ты откликнешься. Задача, кстати, не так проста.



0



jugu

610 / 415 / 151

Регистрация: 11.01.2019

Сообщений: 1,746

20.01.2019, 12:28

5

Вот такой вариант:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>
#include <cstring>
#include <vector>
#include <set>
#include <algorithm>
 
// факториал n
long long F(int n) {
    if (n < 2) return 1LL;
    long long f = 1LL;
    for (int i = 2; i <= n; ++i) f *= i;
    return f;
}
 
// число бесповторных сочетаний из n по m
long long C(int n, int m) {
    if (m < 1 || n < 1 || m > n) return 0LL;
    return F(n) / (F(m) * F(n - m));
}
 
// получить подстроку с порядковым номером i
void ith_substring(long long i, int m, int n, const char * src_string, std::string & substring ) {
    if (m == 0 || !*src_string) return;
    if (m == n) {
        while (*src_string) substring.push_back(*src_string++);
        return;
    }
    if (long long c = C(n - 1, m); i <= c) 
        ith_substring(i, m, n - 1, src_string + 1, substring);
    else {
        substring.push_back(*src_string);
        ith_substring(i - c, m - 1, n - 1, src_string + 1, substring);
    }
}
 
// получить все подстроки без учета повторений (возврат - число полученных подстрок)
long long all_substrings(const std::string & str, std::set<std::string> & substrings) {
    std::string substr;
    substrings.clear();
    long long c_total = 1LL;
    int n = str.size(), m = 1;
    while (m <= n) {
        long long c = C(n, m), i = 1;
        while (i <= c) {
            ith_substring(i, m, n, str.c_str(), substr);
            if (substrings.insert(substr).second) ++c_total;
            substr.clear();
            ++i;
        }
        ++m;
    }
    return c_total;
}
 
int main()
{
    // тест
    std::string str = "abbcdeefg";
    std::set<std::string> substrings;
    std::cout << "Source string: " << str.c_str() << std::endl;
    std::cout << all_substrings(str, substrings) << " substrings were found: " << std::endl;
    for (const auto & s : substrings) std::cout << s.c_str() << std::endl;
    return 0;
}

Добавлено через 3 минуты
Работать будет со строками ограниченной длины, пока факториалы и число сочетаний влезают в разрядную сетку long long.

PS: На Лиспе я бы мог сделать версию гораздо мощнее, т.к. там число значащих цифр практически не ограничено и можно легко находить 100! и даже 1000! Увы, С++ не всесилен. Надеюсь, что в С++20 будет возможность работы с реально большими числами, а не long long.



0



447 / 333 / 172

Регистрация: 01.07.2015

Сообщений: 1,162

20.01.2019, 14:50

6

Цитата
Сообщение от jugu
Посмотреть сообщение

Надеюсь, что в С++20 будет возможность работы с реально большими числами, а не long long.

Не по теме:

что-то я не слышал, чтобы в стандарт планировали добавлять длинную арифметику



0



610 / 415 / 151

Регистрация: 11.01.2019

Сообщений: 1,746

20.01.2019, 15:09

7

Цитата
Сообщение от ReDoX
Посмотреть сообщение

что-то я не слышал, чтобы в стандарт планировали добавлять длинную арифметику

Да вроде ведется дискуссия. Но не уверен, что добавят. Ну, в любом случае, я комбинаторные и рекурсивные проги пишу только на Лиспе ))



0



Автор оригинала: Shubham Sayon.

🏢 Компании, которые задавали эту проблему: Accolite, Tesco, Google

Постановка проблемы

Описание

Учитывая две строки str1 и str2 , проверьте, если str1 это подпоследовательность str2 Отказ

А подпоследовательность строки – это новая строка, которая формируется из исходной строки, удаляя некоторые (может быть ни одного) символов, не нарушая относительные позиции оставшихся символов. (I.e., «Туз» – это подпоследовательность «ABCDE» в то время как «AEC» не).

Ограничения :

  • 0 волн
  • 0 волн 4
  • str1 и str2 состоят только в строчных английских буквах.

Пример

Input: str1 = "abc", str2 = "ahbgdc"
Output: True

Input: str1 = "axc", str2 = "ahbgdc"
Output: False

Пограничные случаи

  • Если str1 и str2 Оба пустые, затем выходные → Правда , как пустая строка подпоследовательность другой пустой строки.
  • Если str1 → Пусто и str2 → Не пусто, то вывод → Правда Как пустая строка также является подпоследовательностью любой данной строки.
  • Если str1 → Не пусто и str2 → Пусто, затем вывод → Ложь , как не пустая строка не может быть подпоследовательностью пустой строки.
Input: str1 = "", str2 = ""
Output: True

Input: str1 = "", str2 = "ahbgdc"
Output: True

Input: str1 = "abc", str2 = ""
Output: False

Предлагаемый обзор решений

Пройти через каждый символ в данных строках. Есть два случая, когда вы делаете это:

  1. Текущие символы обеих строк равны. Итак, перейдите к следующему следующему индексу/символу str1 и str2 Отказ
  2. Текущие символы обеих строк не равны. Итак, перейдите к следующему индексу/символу str2 Отказ Однако индекс str1 Остается фиксированным в этом случае, поскольку соответствующий персонаж не найден.

Вышеуказанный процесс повторяется до тех пор, пока не выполнены один из следующих критериев:

  1. Все персонажи str1 найдены, чтобы присутствовать в str2 Отказ Длина str1 и текущее значение index_str1 будет равным в этом случае. Это обозначает, что мы успешно нашли подпоследовательность в данной строке. Другими словами, str1 является подпоследовательностью str2 Отказ
  2. Все персонажи str2 были пройдены. Это означает, что либо последний символ str1 и str2 равны или str2 не подпоследовательность str1 Отказ

Диаграмматическое представление:

Решение

def isSubSequence(str1, str2):
    len_str1 = len(str1)
    len_str2 = len(str2)
    index_str1 = 0  
    index_str2 = 0  
    # Traverse both str1 and str2
    while index_str1 < len_str1 and index_str2 < len_str2:
        # Compare current character of str2 with str1
        if str1[index_str1] == str2[index_str2]:
            # If matched, then move to next character in str1
            index_str1 = index_str1 + 1
        index_str2 = index_str2 + 1
    return index_str1 == len_str1


val_1 = 'abc'
val_2 = 'ahbgdc'
print(isSubSequence(val_1, val_2))

Выход:

Пояснение кода:

  • len_str1 и Len_str2 Хранить длину str1 и str2 соответственно.
  • index_str1 и index_str2 используются для хранения показателей каждого персонажа str1 и str2 соответственно.
  • в то время как Цикл используется для прохождения сквозь строки до тех пор, пока совпадение не найдена или все индексы STR2 были пройдены в случае отсутствия совпадения.

    • Если заявление сравнивает текущий символ str1 и str2 Отказ

      • Если совпадение найдено, индекс следующего символа в str1 учитывается.
    • Значение index_str1 увеличивается на каждой итерации, чтобы пройти через все доступные буквы в str1 до тех пор, пока подпоследовательность найден.
  • Наконец, если подпоследовательность Был найден ценность, хранящаяся index_str1 будет равен длине str1 Отказ

Пробный прогон:

В следующей таблице иллюстрирует операцию на каждой итерации в цикле пока не найдено.

📹 Видео-решение

Я профессиональный Python Blogger и Content Creator. Я опубликовал многочисленные статьи и создал курсы в течение определенного периода времени. В настоящее время я работаю полный рабочий день, и у меня есть опыт в областях, таких как Python, AWS, DevOps и Networking.

Вы можете связаться со мной @:

  • Заработка
  • Linkedin.


  • Метки



    string, subsequence

Нахождение максимальной общей подпоследовательности

Время на прочтение
6 мин

Количество просмотров 37K

В настоящей статье я хотел бы сделать обзор популярных алгоритмов для решения задачи нахождения максимальной общей подпоследовательности или LCS (longest common sequense). Так как акцент сделан на обучении, а не на реальном использовании, в качестве языка для реализации выбран Python, что позволит сократить количество кода и сконцентрироваться на основных идеях.

Определения

Последовательность представляет собой упорядоченный набор элементов. Строка — это частный случай последовательности, дальнейшие примеры будут для простоты рассматривать именно строки, но без изменений их можно использовать и для произвольного текста или чего-нибудь последовательного еще.
Пусть имеется последовательность x, состоящая из элементов x1x2…xm и последовательность y, состоящая из элементов y1y2…yn. z — подпоследовательность x в том случае, если существует строго возрастающий набор индексов элементов x, из которых получается z.
Общей подпоследовательностью для x и y считаем такую последовательность z, которая является одновременно подпоследовательностью x и подпоследовательностью y.
Максимальная общая подпоследовательность — это общая подпоследовательность с максимальной длинной. Далее по тексту будем использовать сокращение LCS.
В качестве примера, пусть x=HABRAHABR, y=HARBOUR, в этом случае LCS(x, y)=HARBR. Можно уже переходить непосредственно к алгоритму вычисления LCS, но, хорошо бы понять, для чего нам может это может понадобиться.

Применение на практике

Наиболее частое применение — использование в программах для сравнения файлов, например GNU diff. Имея найденную для двух текстов LCS, составить список элементарных изменений для превращения x в y или обратно задача тривиальная. В качестве бонуса, на основе длины общей подпоследовательности можно задать метрику для определения схожести двух последовательностей. Все, теперь точно можно переходить к делу.

Первый подход или народное творчество

Для начала пара наблюдений:

  1. Если для последовательностей x и y нами уже вычислена LCS(x, y)=z, то, LCS для последовательностей полученных из x и y путем добавления одинакового элемента, будет состоять из z и этого добавленного элемента
  2. Если мы добавим к последовательностям x и y по одному разному элементу, то для полученных таким образом xa и yb, LCS должна быть большая из двух: LCS(x,yb) или LCS(xa,y)

Этих наблюдений уже достаточно, чтобы реализовать рекурсию:

def LCS_RECURSIVE(x, y):
    if len(x) == 0 or len(y) == 0:
        return []
    if x[-1] == y[-1]:
        return LCS_RECURSIVE(x[:-1], y[:-1]) + [x[-1]]
    else:
        left = LCS_RECURSIVE(x[:-1], y)
        right = LCS_RECURSIVE(x, y[:-1])
        return left if len(left) > len(right) else right

Теперь можно подумать, что с этой реализацией не так. В наихудшем случае, когда между x и y нет одинаковых элементов LCS_RECURSIVE вызовется 2len(x)+len(y) раз, при уровне рекурсии len(x)+len(y). Даже если закрыть глаза на производительность, код:

from uuid import uuid4
x = [uuid4().hex for _ in xrange(500)]
y = [uuid4().hex for _ in xrange(500)]
print LCS_RECURSIVE(x,y)

без дополнительного вызова sys.setrecursionlimit удачно не выполнится. Не самая удачная реализация.

Динамическое программирование или 64 кб хватит всем

Рассматриваемый алгоритм также известен как алгоритм Нидлмана—Вунша (Needleman-Wunsch).
Весь подход сводится к поэтапному заполнению матрицы, где строки представляют собой элементы x, а колонки элементы y. При заполнении действуют два правила, вытекающие из уже сделанных наблюдений:
1. Если элемент xi равен yj то в ячейке (i,j) записывается значение ячейки (i-1,j-1) с добавлением единицы
2. Если элемент xi не равен yj то в ячейку (i,j) записывается максимум из значений(i-1,j) и (i,j-1).
Заполнение происходит в двойном цикле по i и j с увеличением значений на единицу, таким образом на каждой итерации нужные на этом шаге значения ячеек уже вычислены:

def fill_dyn_matrix(x, y):
    L = [[0]*(len(y)+1) for _ in xrange(len(x)+1)]
    for x_i,x_elem in enumerate(x):
        for y_i,y_elem in enumerate(y):
            if x_elem == y_elem:
                L[x_i][y_i] = L[x_i-1][y_i-1] + 1
            else:
                L[x_i][y_i] = max((L[x_i][y_i-1],L[x_i-1][y_i]))
    return L

Иллюстрация происходящего:

Ячейки, в которых непосредственно происходило увеличение значения подсвечены. После заполнения матрицы, соединив эти ячейки, мы получим искомый LCS. Соединять при этом нужно двигаясь от максимальных индексов к минимальным, если ячейка подсвечена, то добавляем соответствующий элемент к LCS, если нет, то двигаемся вверх или влево в зависимости от того где находится большее значение в матрице:

def LCS_DYN(x, y):
    L = fill_dyn_matrix(x, y)
    LCS = []
    x_i,y_i = len(x)-1,len(y)-1
    while x_i >= 0 and y_i >= 0:
        if x[x_i] == y[y_i]:
            LCS.append(x[x_i])
            x_i, y_i = x_i-1, y_i-1
        elif L[x_i-1][y_i] > L[x_i][y_i-1]:
            x_i -= 1
        else:
            y_i -= 1
    LCS.reverse()
    return LCS

Сложность алогоритма — O(len(x)*len(y)), такая же оценка по памяти. Таким образом, если я захочу построчно сравнить два файла из 100000 строк, то нужно будет хранить в памяти матрицу из 1010 элементов. Т.е. реальное использование грозит получением MemoryError почти на ровном месте. Перед тем как перейти к следующему алгоритму заметим, что при заполнении матрицы L на каждой итерации по элементам x нам нужна только строка, полученная на предыщем ходу. Т.е. если бы задача ограничивалась только нахождением длины LCS без необходимости вычисления самой LCS, то можно было бы снизить использование памяти до O(len(y)), сохраняя одновременно только две строки матрицы L.

Борьба за место или Алгоритм Хишберга (Hirschberg)

Идея в основе этого алгоритма проста: если разделить входную последовательность x=x1x2…xm на две произвольные части по любому граничному индексу i на xb=x1x2…xi и xe=xi+1xi+2…xm, то найдется способ аналогичного разбиения y (найдется такой индекс j, разбивающий y на yb=y1y2…yj и ye=yj+1yj+2…yn), такой, что LCS(x,y) = LCS(xb,yb) + LCS(xe,ye).
Для того, чтобы найти это разбиение y предлагается:

  1. Заполнить динамическую матрицу L с помощью fill_dyn_matrix для xs и y. L1 приравняем последней строке вычисленной матрицы L
  2. Заполнить матрицу L для *xe и *y (обратные последовательности для xe и y, в терминах Python: list(reversed(x)), list(reversed(y))). Приравняем *L2 последнюю строку вычисленной матрицы L, L2 это риверс от *L2
  3. Принять j равным индексу, для которого сумма L1[j]+L2[j] максимальна

Это можно представить, как заполнение матрицы L с двух противоположных концов:

Заметим, что раз есть необходимость только в последней строке матрицы L, то при вычислении хранить всю матрицу не нужно. Немного изменив реализацию fill_dyn_matrix получим:

def lcs_length(x, y):
    curr = [0]*(1 + len(y))
    for x_elem in x:
        prev = curr[:]
        for y_i, y_elem in enumerate(y):
            if x_elem == y_elem:
                curr[y_i + 1] = prev[y_i] + 1
            else:
                curr[y_i + 1] = max(curr[y_i], prev[y_i + 1])
    return curr

Теперь непосредственно, о самом алгоритме. Он представляет собой классический divide and conquer: пока в последовательности x есть элементы, мы делим x пополам, находим подходящее разбиение для y и возвращаем сумму рекурсивных вызовов для пар последовательностей (xb,yb) и (xe,ye). Заметим, что в тривиальном случае, если x состоит из одного элемента и встречается в y мы просто возвращаем последовательность из этого единственного элемента x.

def LCS_HIRSHBERG(x, y):
    x_len = len(x)
    if x_len == 0:
        return []
    elif x_len == 1:
        if x[0] in y:
            return [x[0]]
        else:
            return []
    else:
        i = x_len // 2
        xb, xe = x[:i], x[i:]
        L1 = lcs_length(xb, y)
        L2 = reversed(lcs_length(xe[::-1], y[::-1]))
        SUM = (l1 + l2 for l1,l2 in itertools.izip(L1, L2))
        _, j = max((sum_val, sum_i) for sum_i, sum_val in enumerate(SUM))
        yb, ye = y[:j], y[j:]
        return LCS_HIRSHBERG(xb, yb) + LCS_HIRSHBERG(xe, ye)

Теперь требования по памяти у нас O(len(y)), время, необходимое для вычисления, удвоилось, но асимптотически по-прежнему O(len(x)len(y)).

Алгоритм Ханта-Шуманского (Hunt-Szymanski Algorithm)

Первое что нам потребуется это создание хэш таблицы matchlist, которая будет содержать индексы элементов второй последовательностей. Время необходимое для этого O(len(y)). Следующий код на питоне делает это:

y_matchlist = {}
for index, elem in enumerate(y):
    indexes = y_matchlist.setdefault(elem, [])
    indexes.append(index)
    y_matchlist[elem] = indexes

Для последовательности «HARBOUR» хэш будет следующим {‘A’: [1], ‘B’: [3], ‘H’: [0], ‘O’: [4], ‘R’: [2, 6], ‘U’: [5]}.

Далее, перебирая элементы последовательности x, заполняем массив THRESH соответсвующими индексами из заготовленного matchlist, таким образом, что значением k-го элемента THRESH должен быть индекс y_index, при условии что THRESH[k-1] < y_index и y_index < THRESH[k]. Таким образом, в любой момент времени массив THRESH отсортирован и для нахождения подходящего k мы можем использовать бинарный поиск. При обновлении элемента THRESH мы также добавляем соответствующий индексу y_index элемент последовательности к нашей LCS. Внести ясность может следующий код:

x_length, y_length = len(x), len(y)
min_length = min(x_length, y_length)
THRESH  = list(itertools.repeat(y_length,  min_length+1))
LINK_s1 = list(itertools.repeat(None,      min_length+1))
LINK_s2 = list(itertools.repeat(None,      min_length+1))
THRESH[0], t = -1, 0

for x_index,x_elem in enumerate(x):
   y_indexes = y_matchlist.get(x_elem, [])
   for y_index in reversed(y_indexes):               
       k_start = bisect.bisect_left(THRESH, y_index, 1, t)
       k_end   = bisect.bisect_right(THRESH, y_index, k_start, t)
       for k in xrange(k_start, k_end+2):
           if THRESH[k-1] < y_index and y_index < THRESH[k]:
               THRESH[k] = y_index
               LINK_x[k] = (x_index, LINK_x[k-1])
           if k > t:
               t = k

После этого нам остается только собрать из LINK_x саму последовательность.
Время работы этого алгоритма равно O((r + n) log n), где n — длина большей последовательности, а r — количество совпадений, в худшем случае при r = n*n, мы получаем время работы хуже чем в предыдущем варианте решения. Требования по памяти остаются порядка O(r+n).

Итого

Алгоритмов решающих данную проблему довольно много, асимптотически, самый эффективный алгоритм (Masek and Paterson) имеет оценку по времени O(n*n/log n). Учитывая общую небольшую эффективность при вычислениях LCS, на практике перед работой алгоритма выполняются простейшие подготовки, вроде отбрасывания одинаковых элементов в начале и в конце последовательностей и поиск тривиальных отличий между последовательностями. Также, существуют некоторые оптимизации с использованием битовых операций, не влияющие на асимптотику времени работы.
скачать весь код с примерами

Понравилась статья? Поделить с друзьями:
  • Как найти копии текст в интернете
  • Как найти телефон на ноутбуке через блютуз
  • Как найти ортогональный вектор в пространстве
  • Как найти течение озера
  • Как найти геев в питере