Сравнение массивов строк на сходство

У меня есть сотни строк JSON. Каждый из них содержит массив из 15-20 слов, отсортированных по некоторому заранее заданному весу. Этот вес, если это стоит отметить, представляет собой количество раз, когда эти слова встречаются в некотором фрагменте текста. Каков наилучший способ найти сходство между массивами слов, структурированных таким образом?

Первая идея, которая пришла мне в голову, заключалась в том, чтобы создать числовой хэш всех слов вместе и в основном сравнить эти значения, чтобы определить сходство. У меня это не очень получилось, так как полученные хеш-значения очень похожих строк были не очень близки. После некоторого исследования алгоритмов сравнения строк я пришел в Stackoverflow в надежде получить дополнительные рекомендации. Заранее спасибо, и, пожалуйста, дайте мне знать, если вам нужна дополнительная информация о проблеме.

Редактировать 1: Уточнение того, что я пытаюсь сделать: я хочу определить, насколько похожи два массива в соответствии со словами, которые есть у каждого из них. Я также хотел бы принять во внимание вес каждого слова в каждом массиве. Например:

var array1 = [{"word":"hill","count":5},{"word":"head","count":5}];
var array2 = [{"word":"valley","count":7},{"word":"head","count":5}];
var array3 = [{"word":"head", "count": 6}, {"word": "valley", "count": 5}];
var array4 = [{"word": "valley", "count": 7}, {"word":"head", "count": 5}];

В этом примере массив 4 и массив 2 более похожи, чем массив 2 и массив 3, потому что, несмотря на то, что оба содержат одни и те же слова, вес для них одинаков в массивах 4 и 2. Надеюсь, это немного усложняет задачу. немного легче понять. Заранее спасибо.


person Xavier E. López    schedule 26.09.2011    source источник
comment
Итак, у вас есть N массивов по Nm слов в каждом, и вы хотите определить, что именно?   -  person Rusty Fausak    schedule 26.09.2011
comment
Я отредактировал свой исходный пост с некоторыми пояснениями. Надеюсь, что это поможет, и спасибо за интерес.   -  person Xavier E. López    schedule 26.09.2011
comment
что больше похоже на голову и аде или голову и кавесу?   -  person Itay Moav -Malimovka    schedule 26.09.2011
comment
Привет, Итай, меня не интересует, насколько похожи два слова, меня интересует, насколько похожи два массива, тем, что они разделяют некоторые слова.   -  person Xavier E. López    schedule 26.09.2011
comment
Вы ищете алгоритм для определения общего сходства двух строк, или вы ищете, как перебрать все массивы, чтобы выполнить это сравнение? Или оба?   -  person Keith Twombley    schedule 26.09.2011
comment
Да, мне пришлось бы пройтись по каждому массиву. То, что я хочу, — это способ сказать «ОК», вот эти три массива, наиболее похожие на тот, который у меня есть. Аналогично в этом случае указывает, что в массивах есть одинаковые слова. В приведенном мной примере в обоих массивах есть слова «голова» и «долина», поэтому они похожи.   -  person Xavier E. López    schedule 26.09.2011


Ответы (4)


Я думаю, что вам нужно "подобие косинуса", и вы также можете посмотреть на модели векторного пространства. Если вы программируете на Java, вы можете использовать пакет S-space с открытым исходным кодом.

(добавлено 31 октября) Каждый элемент вектора является счетчиком одной конкретной строки. Вам просто нужно преобразовать ваши массивы строк в такие векторы. В вашем примере у вас три слова - "холм", "голова", "долина". Если ваш вектор находится в таком порядке, векторы, соответствующие массивам, будут

// array: #hill, #head, #valley
array1:  {5,     5,     0}
array2:  {0,     5,     7}
array3:  {0,     6,     5}
array4:  {0,     5,     7}
person kc2001    schedule 03.10.2011
comment
Спасибо за предложение. Несмотря на то, что это очень полезный и интересный материал, в данном сценарии мне не интересно сравнивать сходство самих строк. Меня волнует только, одинаковые они или нет. В данном случае я сравниваю сходство массивов строк. - person Xavier E. López; 27.10.2011
comment
@Xavier - Да, это то, что делает косинусное сходство. Каждый элемент вектора является счетчиком одной конкретной строки. Вам просто нужно преобразовать ваш массив строк в такой вектор. В вашем примере у вас три слова - холм, вершина, долина. Если ваш вектор находится в таком порядке, вектор, соответствующий массиву1, будет {5, 5, 0}. - person kc2001; 30.10.2011
comment
Интересно, кс2001. Спасибо, что вернулись ко мне. Я до сих пор не совсем понимаю, должен признать. В случае, если вы объяснили, как вектор, содержащий только счетчики, поможет мне сравнить массивы? Другими словами, где в этом векторе находится информация, содержащая фактическую строку, а не только количество строк? Я видел несколько примеров в Интернете, где они создают алфавит строк [abcde], а затем вектор, основанный на объединении символов между двумя строками. Затем эти два вектора сравниваются с использованием косинусного сходства. Вы предлагаете здесь аналогичный подход? - person Xavier E. López; 31.10.2011
comment
Думаю, теперь я понимаю. Мне нужно выполнить операцию объединения между массивами, где каждый элемент является словом. Когда у меня есть один вектор со всеми словами в обоих массивах, я преобразовываю каждый массив слов в него, заменяя слова, которых нет в массиве, на 0 и используя счетчик в противном случае. Как только я закончу с этим, у меня должно быть два вектора, на которых я могу выполнить косинусное сходство. Это правильный подход? - person Xavier E. López; 31.10.2011
comment
@Xavier - Да, в основном это так. - person kc2001; 31.10.2011

Учитывая, что каждый массив необходимо сравнивать с любым другим массивом, вы видите серьезный объем обработки по строкам, в ∑(n-1) умноженный на среднее количество «слов» в каждом массиве. Вам нужно будет сохранить оценку для каждого сравнения, а затем разобраться в ней.

e.g.

var array1 = [{"word":"hill","count":5},{"word":"head","count":5}];
var array2 = [{"word":"valley","count":7},{"word":"head","count":5}];
var array3 = [{"word":"head", "count": 6}, {"word": "valley", "count": 5}];
var array4 = [{"word": "valley", "count": 7}, {"word":"head", "count": 5}];

// Comparison score is summed product of matching word counts
function compareThings() {

  var a, b, i = arguments.length,
      j, m, mLen, n, nLen;
  var word, score, result = [];

  if (i < 2) return;

  // For each array
  while (i--) {
    a = arguments[i];
    j = i;

    // Compare with every other array
    while (j--) {
      b = arguments[j];
      score = 0;

      // For each word in array
      for (m=0, mLen = b.length; m<mLen; m++) {
        word = b[m].word

        // Compare with each word in other array
        for (n=0, nLen=a.length; n<nLen; n++) {

          // Add to score
          if (a[n].word == word) {
            score += a[n].count * b[m].count;
          }
        }
      }

      // Put score in result
      result.push(i + '-' + j + ':' + score);
    }
  }
  return result;
}

var results = compareThings(array1, array2, array3, array4);

alert('Raw results:\n' + results.join('\n'));
/*
Raw results:
3-2:65
3-1:74
3-0:25
2-1:65
2-0:30
1-0:25
*/

results.sort(function(a, b) {
  a = a.split(':')[1];
  b = b.split(':')[1];
  return b - a;
});

alert('Sorted results:\n' + results.join('\n'));
/*
Sorted results:
3-1:74
3-2:65
2-1:65
2-0:30
3-0:25
1-0:25
*/

Таким образом, 3-1 (массив4 и массив2) имеют наивысший балл. К счастью, сравнение должно быть только одним способом, вам не нужно сравнивать a с b и b с a.

person RobG    schedule 26.09.2011
comment
Спасибо РобГ. Есть какая-то конкретная причина, по которой вы вычисляете сходство, умножая веса, а не вычитая их, как в других предложениях, представленных здесь? Мне это нравится, потому что оно делает то, что я хочу для случаев, которые я тестировал, но это как если бы это число было произвольным и непредсказуемым. Например, если у вас есть два массива с одним идентичным словом, но с огромным весом в одном из массивов, он будет более похож на тот, в котором больше похожих слов с меньшим весом. Тем не менее, это хорошее начало, и я благодарю вас за ваши усилия. - person Xavier E. López; 27.10.2011
comment
Я полагаю, добавляются ли веса или умножаются, зависит от вашего фона. В работе по статистическому анализу, которую я проделал, веса подобны вероятностям, поэтому значения умножаются на них. Некоторыми примерами из реального мира являются гандикапы в парусном спорте (где гонки различаются по продолжительности и условиям, поэтому истекшее время умножается на гандикап) и настройка сетей управления съемками, где каждое измерение имеет разную точность (например, +-10 мм) и, следовательно, имеет разный вес. в регулировке. - person RobG; 28.10.2011
comment
Я понимаю, это, безусловно, зависит от подхода, который я хочу использовать. Спасибо, РобГ. - person Xavier E. López; 29.10.2011

Вот попытка. Алгоритм не очень умен (разница > 20 означает отсутствие одинаковых слов), но может быть полезным для начала:

var wordArrays = [
    [{"word":"hill","count":5},{"word":"head","count":5}]
  , [{"word":"valley","count":7},{"word":"head","count":5}]
  , [{"word":"head", "count": 6}, {"word": "valley", "count": 5}]
  , [{"word": "valley", "count": 7}, {"word":"head", "count": 5}]
]

function getSimilarTo(index){
    var src = wordArrays[index]
      , values

    if (!src) return null;

    // compare with other arrays
    weighted = wordArrays.map(function(arr, i){
        var diff = 0
        src.forEach(function(item){
            arr.forEach(function(other){
                if (other.word === item.word){
                    // add the absolute distance in count
                    diff += Math.abs(item.count - other.count)
                } else {
                    // mismatches
                    diff += 20
                }
            })
        })
        return {
            arr   : JSON.stringify(arr)
          , index : i
          , diff  : diff
        }
    })

    return weighted.sort(function(a,b){
        if (a.diff > b.diff) return 1
        if (a.diff < b.diff) return -1
        return 0
    })
}

/*
getSimilarTo(3)
[ { arr: '[{"word":"valley","count":7},{"word":"head","count":5}]',
    index: 1,
    diff: 100 },
  { arr: '[{"word":"valley","count":7},{"word":"head","count":5}]',
    index: 3,
    diff: 100 },
  { arr: '[{"word":"head","count":6},{"word":"valley","count":5}]',
    index: 2,
    diff: 103 },
  { arr: '[{"word":"hill","count":5},{"word":"head","count":5}]',
    index: 0,
    diff: 150 } ]
*/
person Ricardo Tomasi    schedule 26.09.2011

Отсортируйте массивы по словам перед попыткой сравнения. Как только это будет завершено, для сравнения двух массивов потребуется ровно 1 проход по каждому массиву.

После сортировки массивов вот алгоритм сравнения (psuedo-java):


int compare(array1, array2)
{
  returnValue = 0;
  array1Index = 0
  array2Index = 0;

  while (array1Index < array1.length)
  {
    if (array2Index < array2.length)
    {
      if (array1[array1Index].word == array2[array2Index].word) // words match.
      {
        returnValue += abs(array1[array1Index].count - array2[array2Index].count);
        ++array1Index;
        ++array2Index;
      }
      else // account for the unmatched array2 word.
      {
        // 100 is just a number to give xtra weight to unmatched numbers.
        returnValue += 100 + array2[array2Index].count;
        ++array2Index;
      }
    }
    else // array2 empty and array1 is not empty.
    {
      // 100 is just a number to give xtra weight to unmatched numbers.
      returnValue += 100 + array1[array1Index].count;
    }
  }

  // account for any extra unmatched array 2 values.
  while (array2Index < array2.length)
  {
      // 100 is just a number to give xtra weight to unmatched numbers.
      returnValue += 100 + array2[array2Index].count;
  }

  return returnValue;
}

person DwB    schedule 03.10.2011
comment
DwB, спасибо за ответ! Ваш метод интригует в том смысле, что он позволяет алгоритму проходить каждый массив только один раз. Но чего я не вижу в этой реализации, так это того, что происходит, когда вы не находите слово в массиве2? Вы будете продолжать обращаться к внутреннему оператору else до тех пор, пока не будет выполнено первое условие if, и вы не выйдете из цикла while, не найдя совпадения, даже если вы не пробовали ни одно из других слов в массиве1. Фактически, в этом случае это сравнение не работает, потому что оно останется в бесконечном цикле. Спасибо за ваше предложение на данный момент, это очень полезное начало. - person Xavier E. López; 27.10.2011