LINUX.ORG.RU

Найти все совпадения элементов из 2-х списков (или 2-х строк)

 


1

1

Всех с Новым Годом! :)

Необходимо создать список из максимально длинных общих фрагментов 2 строк. Например, есть 2 строки:

1: 'Дорогой жидкокристаллический монитор. Он может быть изготовлен'

2: 'Дорогой жидкокристаллический монитор, который может быть изготовлен'

Результат должен быть такой:

['Дорогой жидкокристаллический монитор','может быть изготовлен']

Желательно, чтобы фрагменты делились по словам.

Нашел на wiki такой код:

def longest_common_substring(s1, s2):
    m = [[0] * (1 + len(s2)) for i in range(1 + len(s1))]
    longest, x_longest = 0, 0
    for x in range(1, 1 + len(s1)):
        for y in range(1, 1 + len(s2)):
            if s1[x - 1] == s2[y - 1]:
                m[x][y] = m[x - 1][y - 1] + 1
                if m[x][y] > longest:
                    longest = m[x][y]
                    x_longest = x
            else:
                m[x][y] = 0
    return s1[x_longest - longest: x_longest]

Но он возвращает лишь одну самую длинную подстроку. Можно ли как-нибудь подстроить этот код под мою задачу, а то я что-то не соображаю? :)

Deleted

Последнее исправление: Deleted (всего исправлений: 1)

Ну все правильно. Он возвращает самую длинную подстроку. Как ты себе алгоритмически представляешь «список из максимально длинных общих фрагментов 2 строк»

что такое «максимально длинные общие фрагменты»? А минимально длинные есть?

Nicholass ★★★
()
Ответ на: комментарий от Nicholass

return s1[x_longest - longest: x_longest]

А почему ты присваиваешь одному вычитаемому значение элемента, а второму его порядковый номер? Или это имеет в питоне смысл?

ziemin ★★
()
Ответ на: комментарий от ziemin

Тогда это другая задача - построение всех пересечений строк

Nicholass ★★★
()
Ответ на: комментарий от ziemin

переменная longest получает значение некого элемента матрицы, а переменная x_longest номер строки из этой матрицы.

Nicholass ★★★
()
Ответ на: комментарий от Nicholass

Ладно. Хрен с ним. Может в твоём этом питоне есть специальная операция - двоеточие. Которая превращает элемент в его индекс.

Вернёмся к твоему вопросу. Как ты хочешь получить список, индексируя массив одним индексом? Или опять в этом твоём питоне это особая операция?

ziemin ★★
()
Ответ на: комментарий от Nicholass

Как вы ниже уже указали, надо найти все пересечения строк. Дадите какие-нибудь наводки?

Deleted
()

Анонимус мне кажется здравую мысль высказал — распилить текст на слова и отправить в обычный дифф через ентеры, а разницы просто выкинуть.

arturpub ★★
()
Ответ на: комментарий от anonymous

difflib ищет разницу последовательно, как если бы был некий третий текст, от которого берут начало 1-й и 2-й тексты, вводимые пользователем. А мне нужно это делать параллельно. Например:

1: 'Дорогой жидкокристаллический монитор. Он может быть изготовлен'
2: 'Дорогой жидкокристаллический монитор, который может быть изготовлен'

Либо:
1: 'Он может быть изготовлен. Дорогой жидкокристаллический монитор'
2: 'Дорогой жидкокристаллический монитор, который может быть изготовлен'

Во 2-м случае difflib выдаст только 'может быть изготовлен', а нужно ['Дорогой жидкокристаллический монитор','может быть изготовлен'] или ['может быть изготовлен','Дорогой жидкокристаллический монитор']

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Deleted

«Может быть изготовлен, может не быть»:

Может -> ((1, быть), (4, не))
Быть -> ((2, изготовлен), (6, $))
Изготовлен -> ((3, может))
Не -> ((5, быть))

Это каждое слово и массив слов, которые идут за ним, когда оно в к-л своей позиции. Строим две таких карты для обоих текстов и сортируем их по алфавиту. Теперь для каждого стартового слова можно найти общую длиннейшую последовательность и положить ее в результаты. Но может возникнуть ситуация, когда мы из-за сортировки начали не с того слова (с середины например), тогда можно при поиске последовательности проверять результаты на пересечение границ, и дописывать к результату с головы.

Не уверен, что это вообще правильный путь, но вроде должно сработать — мы всегда при выборке очередного стартового слова попадаем либо в начало общей посл-ти, либо в середину. Результаты можно отсортировать по индексу первого слова из к-л карты например, тогда будет асимметричный дифф с оригинальным порядком. Что делать с дубликатами фраз во входной строке непонятно, они точно помешают и/или перемножатся, дальше мысль пока путается.

arturpub ★★
()
Ответ на: комментарий от arturpub

Да, забыл сказать зачем циферки нужны. При поиске последоваиельности нам нужно же не по цепи маркова пройти, а по конкретным словам последовательно, поэтому при поиске следующий индекс должен быть равен предыдущему плюс один, то есть фильтр такой на массив. То же для другой карты, то есть между картами индексы разные, но в каждой возрастают.

arturpub ★★
()
# -*- coding: utf-8 -*-
#!/usr/bin/env python

from itertools import ifilter, product, imap

def longest_common_substring(s1, s2):
    m = [[0] * (1 + len(s2)) for i in range(1 + len(s1))]
    longest, x_longest = 0, 0
    for x in range(1, 1 + len(s1)):
        for y in range(1, 1 + len(s2)):
            if s1[x - 1] == s2[y - 1]:
                m[x][y] = m[x - 1][y - 1] + 1
                if m[x][y] > longest:
                    longest = m[x][y]
                    x_longest = x
            else:
                m[x][y] = 0
    return s1[x_longest - longest: x_longest]

def two_longest_common_substrings(s1, s2):
    common_substr = longest_common_substring(s1, s2)

    s1fragments = ifilter(lambda s: len(s) > 0, s1.split(common_substr))
    s2fragments = ifilter(lambda s: len(s) > 0, s2.split(common_substr))

    pairs = product(s1fragments, s2fragments)

    substrs = sorted(imap(lambda pair: longest_common_substring(pair[0], pair[1]), pairs),
                     key=lambda s: len(s), reverse=True)

    return (common_substr, substrs[0] if len(substrs) > 0 else None)
theNamelessOne ★★★★★
()

Ищем самую длинную общую подстроку (для этого кстати есть библиотечная функция), разбиваем по ней исходные строки и ошметки обрабатываем аналогичным образом, так до посинения

AIv ★★★★★
()

Необходимо создать список из максимально длинных общих фрагментов 2 строк

проверяй по буквам. Т.е. ищи первую букву в строке, как найдёшь проверь вторую, и т.п.

другого способа нет. Можно применить хеширование или вот это: http://en.wikipedia.org/wiki/Bitap_algorithm Но как это на питоне — не знаю.

emulek
()

да, есть ещё способ, если строка короткая:

1. для строки N символов составляем матрицу N*N, а которой все строки сдвинуты

жопажо
опажож
пажожо
ажожоп
жожопа
ожопаж

2. сортируем матрицу

3. дальше сам догадайся.

PS: не я придумал, гуглить BWT

emulek
()
Ответ на: комментарий от theNamelessOne

У меня ifilter вообще не находится в itertools. Python 3.1.3. В Python 2.6.6 вроде работает, но работает очень медленно и выдает

(' \xd0\xb6\xd0\xb8\xd0\xb4\xd0\xba\xd0\xbe\xd0\xba\xd1\x80\xd0\xb8\xd1\x81\xd1\x82\xd0\xb0\xd0\xbb\xd0\xbb\xd0\xb8\xd1\x87\xd0\xb5\xd1\x81\xd0\xba\xd0\xbe\xd0\xb9 ', ' \xd0\xbc\xd0\xbe\xd0\xb4\xd0\xb8\xd1\x84\xd0\xb8\xd1\x86\xd0\xb8\xd1\x80\xd0\xbe\xd0\xb2\xd0\xb0\xd0\xbd\xd0\xbd\xd0')
(это и причина, почему я пересел на тройку - в нем не надо держать все время такие случаи в голове). Но в любом случае, количество совпадений может быть произвольный, а не обязательно 2.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Miguel

Желательно, обрабатывать пословно, а не побуквенно, иначе при обработке реального текста теряется смысл. ab != ba. Соответственно, должен возвращаться пустой массив.

Deleted
()
Ответ на: комментарий от xeiph

Похоже, единственный рациональный вариант. Спасибо. Буду копать в этом направлении.

P. S. Похоже, получается.

s1='Дорогой жидкокристаллический монитор. Он может быть изготовлен незнакомым завиеватым способом'
s2='Дорогой жидкокристаллический монитор, который может быть изготовлен неизвестными завиеватым методомами'
str_match=longest_common_substring(s1,s2)
print(str_match)
s1=s1.replace(str_match,'')
s2=s2.replace(str_match,'')
str_match=longest_common_substring(s1,s2)
print(str_match)
s1=s1.replace(str_match,'')
s2=s2.replace(str_match,'')
str_match=longest_common_substring(s1,s2)
print(str_match)
Дорогой жидкокристаллический монитор
 может быть изготовлен не
 завиеватым

Осталось только включить отсечение по границам слов.

Deleted
()
Ответ на: комментарий от Deleted

У меня ifilter вообще не находится в itertools

В третьем питоне используй filter вместо itertools.ifilter и map вместо itertools.imap

В Python 2.6.6 вроде работает, но работает очень медленно и выдает

a, b = two_longest_common_substrings(...)
print a
print b

Но в любом случае, количество совпадений может быть произвольный, а не обязательно 2.

Ну так применяй алгоритм, пока есть совпадения. Если что, сам алгоритм неплохо описал AIv:

Ищем самую длинную общую подстроку (для этого кстати есть библиотечная функция), разбиваем по ней исходные строки и ошметки обрабатываем аналогичным образом, так до посинения

theNamelessOne ★★★★★
()
Ответ на: комментарий от Deleted

Кстати, заодно придумал, как выйти из цикла - когда алгоритм находит не слово целиком, а только его отдельные буквы или не находит ничего.

Deleted
()
Ответ на: комментарий от Deleted

Желательно, обрабатывать пословно, а не побуквенно

Уговорил. Пусть будут строки «день ночь» и «ночь день». Что должно возвращаться?

Miguel ★★★★★
()
Ответ на: комментарий от Deleted

В принципе, насколько я понимаю, это то же, что предложил xeiph.

Да, то же самое.

theNamelessOne ★★★★★
()
Ответ на: комментарий от Deleted

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

bzip2 работает со строками в 1048576 байт. Именно этим методом. У тебя длиннее, да?

emulek
()
Ответ на: комментарий от Deleted

Похоже, единственный рациональный вариант.

с сортировкой тоже самое. Просто сортировка в питоне есть, а вот такой поиск — вряд-ли. Сортировка и есть поиск, у тебя строчки рядышком оказываются. Т.е. искать их уже проще, просто следующую взять.

emulek
()
Ответ на: комментарий от emulek

Ты же сам написал

если строка короткая

Deleted
()
Ответ на: комментарий от emulek

зачем тебе вообще нужны границы какие-то?

Я работаю с реальным текстом и применяю свою программу в реальной жизни. Конкретно, 1-й текст сопоставляется со 2-м. 2-й текст переведен. Моя задача - облегчить переводчику (то бишь себе) работу над 1-м текстом. Соответственно, программа указывает уже переведенные куски текста. Не спрашивайте про Google Translate или CAT, здесь все несколько сложнее. Зачем мне надо, чтобы программа возвращала не цельные куски текста, а оторванные фрагменты слов, которые бесполезны будут в переводе?

Deleted
()
Последнее исправление: Deleted (всего исправлений: 2)
Ответ на: комментарий от emulek

Да, но я могу посмотреть, что стоит в исходном предложении перед/после фрагментом текста. Если " - значит, это начало/конец строки. Если что-то другое - значит, это ненужный кусок. В этом случае ищется первый/предпоследний пробел и удаляется текст до/после него.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Deleted

Да, но я могу посмотреть, что стоит в исходном предложении перед/после фрагментом текста.

мне лень что-то доказывать. Попробуй оба варианта, и убедись сам. В данном случае более простой код более быстро работает потому, что простой код проще распараллелить. Ну и сам по себе простой код работает очевидно быстрее. А твои границы — лишняя работа.

Ты же сам написал «если строка короткая»

если строка короткая, ты можешь применить BWT as is. Если строка длинная, тебе придётся применить оптимизации, что-бы засунуть матрицу N*N хотя-бы в 9*N. В питоне это наверное невозможно (в bzip2 это делается указателями C).

А вообще задача довольно сложная сама по себе, и простейший способ конечно работает, но слишком медленный даже для коротких строчек. Ну а сортировка есть в питоне, и реализована очень хорошо(часто вообще на асме, в расчёте на конкретную архитектуру), потому такой «поиск» будет работать на порядок быстрее твоих велосипедов.

emulek
()
Ответ на: комментарий от emulek

Спасибо. Почитаю про этот алгоритм.

Deleted
()
Ответ на: комментарий от emulek

А этот «матричный» алгоритм быстро будет работать? Я дописал скрипт на основе wiki, но сравнение 1-го текста длиной 3 стр. и 2-го текста длиной 7 стр. заняло 10 минут.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Deleted

А этот «матричный» алгоритм быстро будет работать?

сортировка N строк длинной N символов имеет сложность O(N*N*log(N)). Использование постфиксного метода сравнения уменьшают сложность до O(N*log(N)*log(N)), но и так норм будет для строк ~100 символов. Если юзать встроенные методы сортировок строк.

emulek
()

Для моей задачи оказалось достаточным разбить 1-й и 2-й текст на слова (split(' ')) и сравнить эти 2 списка. Потом помечаем совпадения и несовпадения, объединяем смежные совпадения и вытаскиваем их. Хотя в этом случае это не строгие совпадения, скорость алгоритма гораздо выше на больших текстах.

Deleted
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.