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

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

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

UPDATE: кто-то ответил с помощью indexOf(), это меня смущает. Может быть, фраза "иметь общий символ" означает то же самое, что и "строка-это подстрока другого"? Позвольте мне привести пример того, чего я хочу:

JavaScript и Stop and stay символ S В общем. Другой пример будь please look right и break the ice у них есть символ k В общем.

9 ответов


в одну сторону,

фактически расширение вашего подхода к карте

создать два bit Arrays для вашего набора символов, скажем, для a-z создать bit array of 26, и какой бы характер вы ни столкнулись с вами set the flag to 1. Итак, Вы читаете string A по одному символу за раз и lookup in string B's flagArray чтобы увидеть, включен ли соответствующий бит, (иначе установите этот бит как " on " в флаге строки A), в той же итерации сделайте это для текущего символа строки B и флагарая A, если ни один из них не соответствует, затем установите соответствующие биты для текущего символа в обоих bitarrays


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

var common_char = function(str1, str2) {
    var map = {};
    return Array.prototype.some.call(str1, function(c) {
        if( !map[c] && str2.indexOf(c) >= 0 ) {//character c not checked and in str2
            return true;
        }
        map[c] = true;
    });
}

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

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

function anythingInCommon(a, b){
    if( b.length < a.length )
        return anythingInCommon(b, a)

    for( var i = 0, len = a.length; i < len; i++ ) 
        if(b.indexOf(a[i]) != -1)
            return true;
  
    return false
}

console.log(
  anythingInCommon("aaaaaaaaaaaaaabbbbbbccccc", "xc"),
  anythingInCommon("aaaaaaaaaaaaaabbbbbbccccc", "x")
)
anythingInCommon('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa','cddabddde')

скрипач:http://jsfiddle.net/LRxGK/4/


построить дерево суффиксов или использовать динамическое программирование может решить эту проблему
Вижу самая длинная общая проблема подстроки, это может быть полезно.


строки работают как массивы, мы хотим имитировать set.intersect в javascript, чтобы получить общие буквы обоих контейнеров, так как JS не имеет собственных наборов, вы можете проверить этот ответ:

самый простой код для пересечения массива в javascript

добавление других ответов, для быстрого способа сделать это быстро и грязно если строки уже отсортированы:

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

  2. pop самый низкий из двух первых символов

  3. если какая-либо из строк пуста, верните false

  4. Гото 1.


все будут кричать на меня за использование regexp, но это делает жизнь SOOOOOO простой

var str1 = 'Stop and stay'
,   str2 = 'JavaScript'.replace(/(.)(?=.*)|\s/g, "")
// replace(): removes space & repeated characters

,   reg = new RegExp("["+ str2 +"]", 'g')
// global flag to return a list of matched characters
;

console.log(str1.match(reg));
// array('S', 't', 'a', 'p', 't', 'a')

JSFIDDLE


этот ответ опирается на ответы gaurav5430 и megawac.

этот алгоритм создает набор, содержащий каждый символ, который появился в любой из строк.

  • он работает в O (n)
  • он не требует, чтобы строки сортировались
  • он рассматривает обе строки одновременно
  • он завершится, как только он достигнет точки, где любой символ существует в обеих строках, даже если этот символ является первым характер.
  • здесь пример JSFiddle.
function common_char(left, right) {
  var left_map = {};
  var right_map = {};
  var index = 0;

  while (index < left.length || index < right.length) {

    // Check left array
    if (index < left.length) {
      var c = left[index];

      left_map[c] = true;

      // Check if it exists in the other map
      if (right_map[c]) {
        return true;
      }
    }    

    // Check right array
    if (index < right.length) {
      var c = right[index];

      right_map[c] = true;

      // Check if it exists in the other map
      if (left_map[c]) {
        return true;
      }
    }

   index++;
  }

  return false;
}

Если вы ищете элементы в контейнерах, где элементы могут содержать произвольные значения, то, поскольку контейнеры не сортируются, вы вряд ли сможете сделать это быстрее, чем выполнять линейный поиск O(n^2), например, используя два вложенных цикла for для итерации по элементам в строках. Как вы, несомненно, заметили, это будет довольно медленно в JavaScript.

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

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


придумал собственное решение, идея состоит в том, чтобы сравнить с самой большой картой на данный момент. Это похоже на подход Густава Бертрама, но адаптивно выбирает, какой символ сравнивать дальше, из строки A или из строки B.

function haveCommonChar(a, b) {
    var mapa = [], mapb = [], mappeda = 0, mappedb = 0, x = 0, y = 0
    while(x < a.length && y < b.length) { // smart part, while both strings still have more chars
        if (mappeda >= mappedb) { //compare against the largest map
            // one way to speed up this part even further, will be to work in chunks of fixed size larger than 1(now it's 1)
            var c = b.charCodeAt(y++)
            if (mapa[c] === 1) return true
            if (mapb[c] !== 1) mapb[c] = 1, mappedb++
        } else {
            var c = a.charCodeAt(x++)
            if (mapb[c] === 1) return true
            if (mapa[c] !== 1) mapa[c] = 1, mappeda++
        }
    }
    //quickly finish the remaining chars
    while(x < a.length) if (mapb[a.charCodeAt(x++)] !== undefined) return true
    while(y < b.length) if (mapa[b.charCodeAt(y++)] !== undefined) return true
    return false
}