Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Сравнение двух текстов (https://javascript.ru/forum/misc/40968-sravnenie-dvukh-tekstov.html)

tsigel 26.08.2013 16:58

Сравнение двух текстов
 
Здравствуйте! Помогите пожалуйста с алгоритмом сравнения двух текстов.

Есть 2 текста, по идее эти тексты должны не сильно отличаться друг от друга. Задача найти изменения в этом тексте и выделить цветом.

Для чего это нужно: Делаем что-то типа просмотра версий документа (на странице 2 десятка разных элементов форм, необходимо сравнить значения элементов форм старого документа и нового и выделить цветом). Для того чтобы можно было делать выделение цветом сделал что элементы форм, содержимое которых различно заменяются на дивы, выглядящие как инпуты, текстари.. Осталось только подсветить слова.

Первое что пришло в голову - разбить строки на массивы и сравнивать элементы массива.

Вялые попытки.. (выделяет дописанное в конец слово не учитывая что слово можно дописать и в начало :cray: )
var text1 = "мама мыла раму";
var text2 = "мама мыла раму долго";

var arr1 = text1.split(' ');
var arr2 = text2.split(' ');

if (arr2.length > arr1.length) {
  for (var i = arr1.length; i < arr2.length; i++) {
    arr2[i] = "<span class='changedText'>" + arr2[i] + "</span>";
  }
}

alert(arr1.join(' '));
alert(arr2.join(' '));

tsigel 26.08.2013 17:15

По идее - надо при сравнении массивов при нахождении первого отличия - сравнивать также со всеми последующими элементами, но это будет подходить только для добавления слова.

Неработающие наброски...
if (arr1.length >= arr2.length) {
  for (var i = 0; i < arr1.length; i++) {
    if (arr1[i] != arr2[i]){
      arr2[i] += "<span class='changedText'>"
      for (var j = i; j < arr2.length; j++) {
        if (arr1[i] == arr2[j]) {
          arr2[j] = "</span>" + arr2[j]
          break;
        }
      }
    }    
  }
}

tsigel 26.08.2013 17:20

Другой подход, который так же работает не корректно:
var text2 = "мама мыла раму долго";
        var text1 = "мама мыла кривую раму";
        var result = [];
        var a1 = text1.split(' ');
        var a2 = text2.split(' ');

        var changes = false;
        for (var i = 0; i < Math.min(a1.length,a2.length); i++) {
            var text = a1[i];
            if (!a2[i] || text != a2[i]) {
                if (!changes) {
                    changes = true;
                    result.push('<span class="changedText">');
                }
            } else {
                if (changes) {
                    changes = false;
                    result.push('</span>');
                }
            }
            result.push(text);
        }
        if (changes) result.push("</span>");
        alert(result.join(' '));


Если добавляем слово в начало 0 он считает измененным весь текст.

Demath 26.08.2013 19:20

tsigel, нужно найти все слова во 2-ом массиве, которых нет в 1-ом массиве?

function DiffArrays(A,B)
{
    var M = A.length, N = B.length, C = [];
    for (var i=0; i<M; i++)
     { var j = 0;
       while (B[j]!==A[i] && j<N) j++;
       if (j==N) C[C.length] = A[i];
     }
   return C;
}

var text1 = "мама мыла кривую раму";
var text2 = "мама мыла раму долго";

var arr1 = text1.split(' ');
var arr2 = text2.split(' ');

alert( DiffArrays(arr2, arr1) );

keen 26.08.2013 19:20

ну это вопрос не языка (подфорум "Общие вопросы Javascript"), а алгоритмизации.

если уж делать через массивы (которые, вообще говоря, лучше разбивать ещё знаками препинания), то можно сделать всё буквально за один проход, сравнивая поэлементно; плюс забегая вперёд/назад, проверяя наличие/отсутствие дополнительных элементов.

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

Demath 26.08.2013 19:30

Цитата:

Сообщение от keen
если уж делать через массивы (которые, вообще говоря, лучше разбивать ещё знаками препинания), то можно сделать всё буквально за один проход, сравнивая поэлементно; плюс забегая вперёд/назад, проверяя наличие/отсутствие дополнительных элементов.

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

keen 26.08.2013 19:35

Цитата:

Сообщение от Demath (Сообщение 269419)
Я думаю, если массивы не упорядочены, то буквально за один проход не получится.

а нахрена нам их упорядочивать??
мы ж тогда получим равенство текстов 'a b c' и 'c a b'

одним циклом идём по первому "эталонному" массиву, сравниваем элементы со 2м, если не совпадают, делаем пару-тройку дополнительных шагов "в стороны". если совпадений нет, значит у нас во 2м тексте появились/пропали слова.

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

ну говорю ж, от задачи надо идти.

Serg_pnz 26.08.2013 19:42

Имхо нужен метод шинглов http://ru.wikipedia.org/wiki/%D0%90%...BB%D0%BE%D0%B2
Но это уже потянет за собой серверное решение.

keen 26.08.2013 19:45

Цитата:

Сообщение от Serg_pnz (Сообщение 269427)
Имхо нужен метод шинглов http://ru.wikipedia.org/wiki/%D0%90%...BB%D0%BE%D0%B2
Но это уже потянет за собой серверное решение.

:D
перечитай формулировку задачи, нафига ему такой фундаментальный метод?)

tsigel 27.08.2013 10:29

Всем большое спасибо, сейчас изучу все вышеописанное.

tsigel 27.08.2013 15:19

var text1 = "Мама мыла очень кривую раму";
var text2 = "Мама долго мыла кривую раму";
        var arr1 = text1.split(' ');
        var arr2 = text2.split(' ');

        var result1 = [], result2 = [];

        for (var i = 0; i < arr1.length; i++) {

            if (arr2[i] == undefined) {

                for (var j = i; j < arr1.length; j++) {
                    arr1[j] = "<span class='changedText'>" + arr1[j] + "</span>";
                }
                break;

            }

            if (arr1[i] != arr2[i]) {

                if (findInArray(arr1[i], arr2)) {

                    arr1.splice(i, 0, arr1[i]);
                    arr2[i] = "<span class='changedText'>" + arr2[i] + "</span>";

                    if (result1[result1.length - 1] != arr1[i])
                        result1.push(arr1[i]);

                    result2.push(arr2[i]);
                } else {

                    arr2.splice(i, 0, arr2[i]);
                    arr1[i] = "<span class='changedText'>" + arr1[i] + "</span>";
                    result1.push(arr1[i]);
                }

            } else {
                if (result1[result1.length - 1] != arr1[i])
                    result1.push(arr1[i]);
                result2.push(arr2[i]);
            }
        }

        function findInArray (element, array) {
            var find = false;

            for (var i = 0; i < array.length; i++) {
                if (array[i] == element)
                    find = true;
            }

            return find;
        }

alert(result1.join(' '));
alert(result2.join(' '));


Вроде работает, но есть косяки:

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

keen,
Как Вы и говорили косяки со знаками препинания. Вроде это можно сделать регулярными выражениями, но в них я не силен, как бы мне выделить в отдельные элементы массива знаки препинания так, чтобы потом оно нормально склеилось в строку и не потерять при этом знаки?

И ещё, мой алгоритм не выводит в первом массиве 2 одинаковых слова :(

Demath 27.08.2013 15:49

Цитата:

Сообщение от tsigel
И ещё, мой алгоритм не выводит в первом массиве 2 одинаковых слова

А, вообще, почему из первого массива должно что-то выводится?

____________

Цитата:

Сообщение от tsigel
Есть 2 текста, по идее эти тексты должны не сильно отличаться друг от друга. Задача найти изменения в этом тексте и выделить цветом.

"в этом тексте" - в каком именно: в 1-ом или 2-ом или в обоих сразу?

Приведите примеры.

tsigel 27.08.2013 15:52

Не совсем. На странице представлены 2 версии документа. text1 текст данного поля из более старой версии, text2 - соответственно более новой. Необходимо цветом выделить изменения в обоих документах. И в старом и в новом. Чтобы оператор при работе с ними не искал читая одинаковые документы.

tsigel 27.08.2013 15:54

У документов есть поля для редактирования, так что функция не должна быть рассчитана на обработку больших количеств текста за 1 заход.

Оптимизация и скорость работы тоже не волнует, т.к. Функция запускается однократно при загрузке страницы.

Demath 27.08.2013 15:59

Например, для

var text1 = "Мама мыла очень кривую раму";
var text2 = "Мама долго мыла кривую раму";

какие слова нужно вывести?

tsigel 27.08.2013 16:07

Demath,
Для данных текстов:
text1 = "Мама мыла <span class='changedText'>очень</span> кривую раму"
text2 = "Мама <span class='changedText'>долго</span> мыла кривую раму"


И из первого исчезло "очень", мы его выделяем, а во втором добавилось "долго", его мы тоже выделяем.

Удивительно, но скрипт выше правильно отрабатывает данные тексты :blink: :)

По идее, если бы тексты были такие:
var text1 = "Мама мыла кривую раму";
var text2 = "Мама мыла раму";

То необходимо было бы в первом тексте выделить "кривую", а второй оставить неизменным.

tsigel 27.08.2013 16:23

Цитата:

Сообщение от Demath
Цитата:

Сообщение от tsigel
И ещё, мой алгоритм не выводит в первом массиве 2 одинаковых слова

А, вообще, почему из первого массива должно что-то выводится?

Есть документ, пользователь может посмотреть изменения этого документа. При просмотре у него на экране 2 версии одного документа, слева старая, справа - новая.

Цитата:

Сообщение от Demath
Цитата:

Сообщение от tsigel
Есть 2 текста, по идее эти тексты должны не сильно отличаться друг от друга. Задача найти изменения в этом тексте и выделить цветом.

"в этом тексте" - в каком именно: в 1-ом или 2-ом или в обоих сразу?

Изменения надо подсвечивать в 2х документах.

tsigel 27.08.2013 16:24

Прошу прощения если непонятно объясняю.

Demath 27.08.2013 16:26

Почитайте перед сном симметрическая разность :)


function DiffArrays(A,B)
{
    var M = A.length, N = B.length, C = [];
    for (var i=0; i<M; i++)
     { var j = 0;
       while (B[j]!==A[i] && j<N) j++;
       if (j==N) C[C.length] = A[i];
     }
   return C;
}

function SymmDiffArrays(A,B)
{
   return DiffArrays(A,B).concat( DiffArrays(B,A) );
}
 
var text1 = "Мама мыла очень кривую раму",
    text2 = "Мама долго мыла кривую раму",
    arr1 = text1.split(' '),
    arr2 = text2.split(' ');
 
alert( SymmDiffArrays(arr1, arr2) );

tsigel 27.08.2013 16:30

Demath,
Спасибо за ответ, учу матчасть! :write:

devote 27.08.2013 16:43

function difference(a, b) {
    a = a.split(' ');
    b = b.split(' ');
    var d1 = a.filter(function(i) {return !(b.indexOf(i) > -1);});
    var d2 = b.filter(function(i) {return !(a.indexOf(i) > -1);});
    return d1.concat(d2);
}
alert(difference("Мама мыла очень кривую раму", "Мама долго мыла кривую раму"));

tsigel 27.08.2013 16:47

Началось, кто короче) Я только-только 1-й вариант прожевал)

Спасибо :)

Вы не подскажете, как знаки препинания тоже в отдельные элементы массива положить? Потому что иначе с ними тоже косяк.

С регулярками, увы, не дружу совсем. :(

devote 27.08.2013 17:01

function difference(a, b) {
    var re = /([~!$%^&*()_+|`\-=\\\[\]{};':",\.\/<>?@#])/g;
    a = a.replace(re, ' $1').split(' ');
    b = b.replace(re, ' $1').split(' ');
    var d1 = a.filter(function(i) {return !(b.indexOf(i) > -1);});
    var d2 = b.filter(function(i) {return !(a.indexOf(i) > -1);});
    return d1.concat(d2);
}
alert(difference("Мама! мыла очень кривую раму", "Мама? долго мыла кривую раму"));

tsigel 27.08.2013 17:05

devote,
Спасибо большое!

devote 27.08.2013 17:17

tsigel,
Обратите внимание indexOf и filter не доступны в IE до IE9.

devote 27.08.2013 17:19

вот полифил filter:
if (!Array.prototype.filter) {
  Array.prototype.filter = function(fun /*, thisp*/) {
    'use strict';

    if (!this) {
      throw new TypeError();
    }

    var objects = Object(this);
    var len = objects.length >>> 0;
    if (typeof fun !== 'function') {
      throw new TypeError();
    }

    var res = [];
    var thisp = arguments[1];
    for (var i in objects) {
      if (objects.hasOwnProperty(i)) {
        if (fun.call(thisp, objects[i], i, objects)) {
          res.push(objects[i]);
        }
      }
    }

    return res;
  };
}

И соответственно indexOf:
if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
    'use strict';
    if (this == null) {
      throw new TypeError();
    }
    var n, k, t = Object(this),
        len = t.length >>> 0;

    if (len === 0) {
      return -1;
    }
    n = 0;
    if (arguments.length > 1) {
      n = Number(arguments[1]);
      if (n != n) { // shortcut for verifying if it's NaN
        n = 0;
      } else if (n != 0 && n != Infinity && n != -Infinity) {
        n = (n > 0 || -1) * Math.floor(Math.abs(n));
      }
    }
    if (n >= len) {
      return -1;
    }
    for (k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); k < len; k++) {
      if (k in t && t[k] === searchElement) {
        return k;
      }
    }
    return -1;
  };
}

Demath 27.08.2013 17:37

Цитата:

Сообщение от devote
вот полифил filter:
...

И соответственно indexOf:
...

Страшно :blink: , а вот преимущества в данном случае не очевидны (во всяком случае для меня).

Serg_pnz 27.08.2013 17:52

оффтоп: winmerge изобретаем, понятно...


Часовой пояс GMT +3, время: 01:47.