У меня есть проект, который нужно сравнить с текстовыми документами и найти степень сходства между каждым отдельным предложением и общим сходством текстов. Я сделал некоторые преобразования в текстах, такие как уменьшение всех слов, удаление повторяющихся слов, удаление знаков препинания, кроме точек. После выполнения некоторых операций у меня было 2 массива, которые включают предложения и слова, разделенные. Это выглядит как
[["привет","мир"],["добро пожаловать","здесь"]]
Затем я отсортировал каждое предложение по алфавиту. После всего этого я сравниваю все слова одно за другим, выполняя линейный поиск, но если слово, которое я ищу, больше, чем я ищу (ASCII первого символа, такого как мир> бургер ), я не смотрю оставшуюся часть, прыгаю другим словом. Это кажется сложным, но мне нужен ответ: «Есть ли более быстрые и эффективные общие алгоритмы, такие как Бойер Мур, хеширование или другие?» . Я не прошу мира кода, но мне нужны теоретические советы. Спасибо.
Обновлено: Я должен был сказать основную цель проекта. На самом деле это своего рода детектор плагиата. Есть два текстовых файла: main.txt и sub.txt. Программа сравнит их и выдаст что-то вроде этого:
Output:
Similarity rate of two texts is: %X
{The most similar sentence}
{The most similar 2nd sentence}
{The most similar 3d sentence}
{The most similar 4th sentence}
{The most similar 5th sentence}
Поэтому мне нужно выяснить степень сходства sub.txt с файлом main.txt. Я подумал, что мне нужно сравнить все предложения в двух файлах друг с другом.
Например, в файле main.txt 10 предложений, а в файле sub.txt 5 предложений. будет 50 сравнений и будет рассчитана 50 степень сходства и хранится.
Наконец, я сортирую показатели сходства и печатаю самые 5 предложений. На самом деле я выполнил проект, но он неэффективен. Он имеет 4 вложенных цикла for и сравнивает все слова бесчисленное количество раз, а сложность становится равной O (n ^ 4) (может быть, не так много), но она действительно огромна даже в худшем случае. Я нашел алгоритм расстояния Левенштейна и алгоритмы подобия косинуса, но я не уверен в них. Спасибо за любое предложение!
РЕДАКТИРОВАТЬ2: Для моего случая сходство между двумя предложениями выглядит так:
main_sentence:"Hello dude how are you doing?"
sub_sentence:"Hello i'm fine dude."
Since intersection is 2 words ["hello","dude"]
The similarity is : (length of intersected words)*100/(length of main text)
For this case it's: 2*100/6 = %33,3
@sprinter Вы правы. Я добавил часть EDIT к моему вопросу. Спасибо за ваш отзыв!
Я думаю, что нам нужно более строгое определение подобного, чтобы решить эту проблему.
@Surt Я добавил строгое определение в EDIT2. Спасибо.
Я считаю, что информации достаточно для того, чтобы, по крайней мере, начать решать проблему. Не закрывайте это, так как может быть интересная тема. Пожалуйста.
Привет @аран. Если ваш последний комментарий был для меня, я работаю над этим проектом, спасибо за ваши действительно полезные предложения. Также у меня есть другие проекты в настоящее время, и я работаю над ними :)
@NaregBoynukalın было направлено пользователям с ограниченными привилегиями. Этот вопрос был проголосован за закрытие, поэтому я сделал комментарий. Да будет тебе удача мой друг! В качестве предложения... здесь будет работать распознавание изображений; )
@aran Большое спасибо! Я думаю, что эта тема поможет людям в будущем вашей и чужой помощью, было бы нехорошо закрывать. Знаешь что, я как бы работаю над машинным обучением, и я действительно думал о распознавании изображений :) Но это не наш случай, по крайней мере, сейчас :)
@NaregBoynukalın Раньше я работал с машинным обучением, так как последний проект моей степени магистра был сосредоточен на распознавании усталости на основе вариабельности частоты сердечных сокращений водителя автомобиля. Я люблю этот мир, но знаю, что работаю архитектором данных (правила денег). Удачи и удачи, мой друг :)
В качестве предложения, и даже если это не «полный ответ» на вашу проблему, сравнение строк обычно является «тяжелой» операцией (даже если вы сначала проверяете их длину, что, по сути, является одной из первых вещей, которые equals() метод уже работает при сравнении строк)
Что я предлагаю сделать дальше: создать метод dummy hashcode()-like. Это будет не настоящий hashcode(), а число, связанное с порядком, в котором это слово было прочитано вашим кодом. Что-то вроде криптографического метода, но намного проще.
Обратите внимание, что string.hashCode() не будет работать, так как слово «Привет» из первого документа не вернет тот же хэш-код, что и слово «Привет» из второго документа.
Представьте, что у вас есть общий HashMap<String,Integer> (myMap), ключ которого является строкой, а значение — целым числом. Обратите внимание, что хеширование HashMap в java со строковыми ключами менее 10 символов (что обычно бывает в английском языке) происходит невероятно быстро. Без какой-либо проверки просто поместите каждое слово с его значением счетчика:
myMap.put(yourString, ++counter);
Допустим, у вас есть 2 документа:
1.txt- Welcome mate what are you doing here
2.txt- Mate I was here before are you dumb
Я предполагаю, что вы уже перевели все слова в нижний регистр и удалили дубликаты. Вы начинаете читать первый документ и присваиваете каждому слову номер. Карта будет выглядеть так:
KEY VALUE
welcome 1
mate 2
what 3
are 4
you 5
doing 6
here 7
Теперь со вторым документом. Если ключ повторяется, метод put() обновит его значение. Так:
KEY VALUE
welcome 1
mate 8
what 3
are 13
you 14
doing 6
here 11
I 9
was 10
before 12
dumb 15
После завершения вы создаете еще один HashMap<Integer,String> (reverseMap) в обратном порядке:
KEY VALUE
1 welcome
8 mate
3 what
13 are
14 you
6 doing
11 here
9 I
10 was
12 before
15 dumb
Вы конвертируете оба документа в список целых чисел, поэтому они выглядят так:
1.txt- Welcome mate what are you doing here
2.txt- Mate I was here before are you dumb
К:
listOne - [1, 8, 3, 13, 14, 6, 11]
listTwo - [8, 9, 10, 11, 12, 13, 14, 15]
Чтобы найти дубликаты в обоих документах:
Сначала создайте глубокий клон одного из списков, например, listTwo. Глубокий клон List из Integers относительно легко выполнить. Назвать его listDuplicates так и будет его целью.
List<Integer> listDuplicates = new ArrayList<>();
for (Integer i:listTwo)
listDuplicates.add(new Integer(i));
Звоните retainAll:
ListDuplicates.retainAll(listOne);
Результат будет:
listDuplicates- [8,11,13,14]
Итак, из listOne.size()+listTwo.size() = 15 слов, найденных в 2 документах, 4 являются дубликатами, а 11 уникальны.
Чтобы получить преобразованные значения, просто вызовите:
for (Integer i : listDuplicates)
System.out.println(reverseMap.get(i)); // mate , here, are, you
Теперь, когда дубликаты идентифицированы, listOne и listTwo теперь также можно использовать, чтобы:
Если следующий элемент имеет значение -1, это означает, что [8] и [11] также будут последовательными:
doc1 doc2 difDoc1 difDoc2
[8] 2 1 -1 (0-1) -1 (0-1)
[11] 7 4 -5 (2-7) -3 (1-4)
[13] 4 6 3 (7-4) -2 (4-6)
[14] 5 7 -1 (4-5) -1 (6-7)
В этом случае расстояние, показанное в [14] с его предыдущим дубликатом (разница между [13] и [14]), одинаково в обоих документах: -1: это означает, что не только дубликаты, но и оба, следовательно, помещены в оба документа.
Следовательно, мы нашли не только повторяющиеся слова, но и повторяющуюся последовательность из двух слов между этими строками:
[13][14]--are you
Тот же механизм (определение разницы -1 для одной и той же переменной в обоих документах) также поможет найти полную повторяющуюся последовательность из 2 или более слов. Если все дубликаты показывают разницу -1 в обоих документах, это означает, что мы нашли полную повторяющуюся строку:
В этом примере это показано яснее:
doc1- "here i am" [4,5,6]
doc2- "here i am" [4,5,6]
ListDuplicates - [4,5,6]
doc1 doc2 difDoc1 difDoc2
[4] 1 1 -1 (0-1) -1 (0-1)
[5] 2 2 -1 (1-2) -1 (1-2)
[6] 3 3 -1 (2-3) -1 (2-3)
Все различия равны -1 для одной и той же переменной в обоих документах -> все дубликаты находятся рядом друг с другом в обоих документах -> Предложение точно такое же в обоих документах. Итак, на этот раз мы нашли полную повторяющуюся строку из 3 слов.
[4][5][6] -- here i am
Помимо этого поиска повторяющихся последовательностей, эта таблица различий также была бы полезна при вычислении дисперсии, медианы,... из повторяющихся слов, чтобы получить некий фактор «сходства» (что-то вроде базового ориентировочного значения равенства между документы. Ни в коем случае не окончательные, но как-то полезные)
Подобные механизмы будут использоваться для получения этих уникальных значений. Например, удалив дубликаты из reverseMap:
for (Integer i: listDuplicates)
reverseMap.remove(i);
Теперь reverseMap содержит только уникальные значения. reverseMap.size() = 11
KEY VALUE
1 welcome
3 what
6 doing
9 I
10 was
12 before
15 dumb
Чтобы получить уникальные слова:
ReverseMap.values() = {welcome,what,doing,I,was,before,dumb}
Если вам нужно знать, какие уникальные слова из какого документа, вы можете использовать reverseMap (поскольку списки могут быть изменены после того, как вы выполните для них такие методы, как continueAll):
{welcome,what,doing}
{I,was,before,dumb}
Фактор уникальности может быть и другим показательным, таким образом, отрицательным (поскольку мы здесь ищем сходство). Все еще может быть очень полезным.
Поскольку метод hashcode() для строк не будет возвращать одно и то же значение для двух одинаковых слов (только для двух одинаковых ссылок на строковые объекты), здесь он работать не будет. String.equals() метод работает путем сравнения символов (также проверяется длина, как вы делаете вручную), что было бы полным излишеством при использовании для больших документов:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
Мое мнение состоит в том, чтобы максимально избегать этого, особенно hashCode() никогда не следует использовать, так как:
String one = "hello";
String two = "hello";
one.hashCode() != two.hashCode()
Есть исключение, но только когда компилятор интернирует строки; Как только вы загрузите тысячи из них, они больше никогда не будут использоваться компилятором. В тех редких случаях, когда оба объекта String ссылаются на один и тот же адрес кэшированной памяти, это также будет верно:
one.hashCode() == two.hashCode() --> true
one == two --> true
Но это действительно необычные исключения, и как только стажировка строк не сработает, эти hashCodes не будут равны, и оператор == для сравнения строк вернет false, даже если строки содержат одно и то же значение (как обычно, потому что он работает, сравнивая их адреса памяти).
Спасибо за вашу помощь. Я уверен, что преобразование каждого уникального слова в целые числа, например присвоение им идентификаторов, значительно сократит время работы, или я надеюсь на это :) Также я добавил часть EDIT к моему вопросу. Можешь проверить :)
Привет @NaregBoynukalın, просто вопрос... Актуальна ли позиция каждого текста? Я имею в виду, если у вас есть две страницы из двух документов... Учитываете ли вы, если "Hello there" повторяется в первых строках обоих документов....
Или это важно, если одно из «привет» находится в начале страницы в первом документе, а другое «привет» находится в конце второго документа? Надеюсь, я объясняю здесь
Привет @aran. На самом деле нет. Заявление «Hello there» может быть в первой строке первого документа, а также может быть в 5-й строке 2-го документа. Затем я говорю, общее утверждение между первой строкой первого документа. и 5-я строка 2-го документа «Привет». Затем я вычисляю коэффициент сходства между этими предложениями.
Считаете ли вы строку текста «элементом» для сравнения? Например, разделить целые строки и сравнить их?
По крайней мере, как первый фильтр, пока не ища сходства слов
@NaregBoynukalın дополнен дополнительной информацией об идентификации повторяющихся последовательностей с помощью выявленных повторяющихся слов. Во всяком случае, я хотел предложить что-то.... совсем другое. Если бы вы могли рассматривать всю линию как «узел», у меня есть предложение сделать
Здравствуйте @aran, я проверил ваше обычное обнаружение последовательности и в основном смог реализовать его на Java. Это работает как шарм, когда каждое предложение имеет все уникальные значения, такие же, как вы дали listOne - [1, 8, 3, 13, 14, 6, 11] и listTwo - [8, 9, 10, 11, 12, 13, 14, 15]. Но мне нужны гениальные идеи о том, что если в списках есть повторяющиеся значения. Например, я добавляю последовательность [8,10] в оба списка, и они становятся listOne - [1, 8, 3, 13, 14,8,10 6, 11] и listTwo - [8, 9, 10, 11, 12, 13, 14,8,10 15]. Поскольку значения 8 и 10 дублируются, это вызывает проблему. Следующий комментарий ->>
Когда я хочу найти индекс вторых 8 и 10, он находит индексы первых 8 и 10. Таким образом, разница не становится «-1» для последовательности [8,10]. Мне снова нужна ваша помощь :)
Серьезно, я застрял. Я пытался отделить все бессмысленные подпоследовательности, а затем сравнить их друг с другом. Я сделал это, но сложность стала O(n^4). Мне нужно что-то более эффективное. Пожалуйста помоги :)
@NaregBoynukalın извините за поздний ответ. Я обещаю прийти снова с чем-то (но я должен знать...). Добавил в закладки, чтобы не потерять путь вопроса
Важнейший метод заключается в том, чтобы рассматривать это как многоэтапный процесс. Суть в том, что вы не пытаетесь сравнить каждый документ с каждым другим документом, а скорее у вас есть первый проход, который идентифицирует небольшие кластеры вероятных совпадений, по сути, в однопроходном процессе:
(1) индексировать или группировать документы таким образом, чтобы можно было идентифицировать возможные совпадения;
(2) Определить документы-кандидаты, которые могут быть совпадением, на основе этих индексов/кластеров;
(3) Для каждого совпадения кластера или индекса иметь алгоритм оценки, который оценивает сходство данной пары документов.
Существует несколько способов решения (1) и (3), в зависимости от характера и количества документов. Варианты для рассмотрения:
Как вы, наверное, догадались из всего этого, не существует единого алгоритма, который подарит вам именно то, что вам нужно на тарелке. (Вот почему этой проблеме занимаются целые компании и исследовательские отделы...) Но, надеюсь, вышеизложенное даст вам некоторые подсказки.
Большое спасибо. Я думаю, что в таких случаях, как «плагиат», необходимо реализовать самую длинную общую последовательность. Если я правильно понял, это означает найти самые длинные общие последовательности между двумя предложениями. Это может быть последовательность из 2 или 3 общих слов, например, между «Сегодня хороший день» и «Сегодня плохой день» имеет общую последовательность из 3 слов «Сегодня есть». Затем мы вычисляем балл в зависимости от длины последовательности. Я прав до сих пор?
Обычно определение LCS заключается в том, что он может «пропускать» несоответствия, поэтому «Я хороший день» и «Я плохой день» будут совпадать в подпоследовательности «Я (...) день» ( то есть измерение количества символов, которые встречаются в одном и том же порядке в двух строках, пропуская промежуточные символы.) Но вы также можете определить его так, как вы предлагаете (иногда делается разница между «подпоследовательностью» и «подстрокой», чтобы обозначить эту разницу ). Более сложные алгоритмы также попытаются создать некоторую устойчивость к ошибкам (например, это можно назвать «самой длинной общей подпоследовательностью с k несовпадениями»).
Пожалуйста, предоставьте некоторую информацию о том, какие вы ожидаете результаты сравнения. Вы ищете количество слов, которые одинаковы в каждом списке? Или просто проверить, содержат ли они точно такие же слова? В вашем вопросе непонятно.