Алгоритм проверки, была ли строка построена из списка подстрок - PullRequest
15 голосов
/ 13 мая 2011

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

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

EDIT Читая какой-то ответ, я заметил, что это, вероятно, проблема NP-Complete. Даже поиск подмножества строк, которые вместе будут иметь одинаковую длину, поскольку данная строка является классической проблемой суммы подмножеств.

Так что я думаю, что нет простого ответа на этот вопрос.

EDIT

Теперь кажется, что это не проблема NP-Complete в конце концов. Вот так круче: -)

EDIT

Я нашел решение, которое проходит несколько тестов:

def can_build_from_substrings(string, substrings):
    prefixes = [True] + [False] * (len(string) - 1)
    while True:
        old = list(prefixes)
        for s in substrings:
            for index, is_set in enumerate(prefixes):
                if is_set and string[index:].startswith(s):
                    if string[index:] == s:
                        return True
                    prefixes[index + len(s)] = True
        if old == prefixes: # nothing has changed in this iteration
            return False

Я считаю, что время O(n * m^3), где n - это длина substrings, а m - это длина string. Что ты думаешь?

Ответы [ 10 ]

9 голосов
/ 13 мая 2011

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

Это проблема динамического программирования. (И отличный вопрос!)

Давайте определим composable(S, W) как истинное, если строка S может быть записана с использованием списка подстрок W.

S компонуется тогда и только тогда, когда:

  1. S начинается с подстроки w в W.
  2. Остальная часть S после w также может быть составной.

Давайте напишем какой-нибудь псевдокод:

COMPOSABLE(S, W):
  return TRUE if S = "" # Base case
  return memo[S] if memo[S]

  memo[S] = false

  for w in W:
    length <- LENGTH(w)
    start  <- S[1..length]
    rest   <- S[length+1..-1]
    if start = w AND COMPOSABLE(rest, W) :
      memo[S] = true # Memoize

  return memo[S]

Этот алгоритм имеет O (m * n) время выполнения, предполагая, что длина подстрок не является линейной w / r / t для самой строки, и в этом случае время выполнения будет O (m * n ^ 2) (где m это размер списка подстрок, а n это длина рассматриваемой строки). Для запоминания используется пространство O (n).

(N.B. Как написано, псевдокод использует O (n ^ 2) пробел, но хеширование ключей памятки уменьшило бы это.)

EDIT

Вот рабочая реализация Ruby:

def composable(str, words)
  composable_aux(str, words, {})
end

def composable_aux(str, words, memo)
  return true if str == ""                # The base case
  return memo[str] unless memo[str].nil?  # Return the answer if we already know it

  memo[str] = false              # Assume the answer is `false`

  words.each do |word|           # For each word in the list:
    length = word.length
    start  = str[0..length-1]
    rest   = str[length..-1]

    # If the test string starts with this word,
    # and the remaining part of the test string
    # is also composable, the answer is true.
    if start == word and composable_aux(rest, words, memo)
      memo[str] = true           # Mark the answer as true
    end
  end

  memo[str]                      # Return the answer
end
2 голосов
/ 13 мая 2011

Это определенно не быстро, но вот вам идея:

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

Остановитесь, когда у вас останется целевая строка 0 длины.

Как я уже говорил ранее, это определенно не быстро, но должно дать вам базовую линию («не должно быть намного хуже, чем это»).

РЕДАКТИРОВАТЬ

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

  • Когда вы обнаружите, что строка является головкой цели, поместите ее в список.После построения списка вы, естественно, попробуете самую большую «головку» цели
  • Когда вы обнаружите, что пробованная вами голова не соответствует тому, что осталось, попробуйте следующую лучшую голову

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

1 голос
/ 15 мая 2011

То, что вы ищете, это парсер. Парсер проверит, относится ли определенное слово к определенному языку. Я не уверен в точной вычислительной сложности вашей проблемы. Некоторые из вышеперечисленных представляются правильными (нет необходимости в исчерпывающем поиске). Одно можно сказать наверняка, это не NP-Complete.

Алфавит вашего языка - это все маленькие подстроки. Слово, которое вы ищете, это строка, которая у вас есть. Регулярное выражение может быть простой звездой Клини или очень простой контекстно-свободной грамматикой, которая не что иное, как «И».

Основная проблема в алгоритме: что, если некоторые из подстрок на самом деле являются подстроками для других подстрок ... то есть, что если у нас есть подстроки: "ab", "abc", "abcd", .. ., В этом случае порядок проверки подстрок изменит сложность. Для этого у нас есть LR-парсеры. Я думаю, они лучшие в решении таких проблем.

Я скоро найду вам точное решение.

1 голос
/ 13 мая 2011

Вдохновленный @cnicutars ответ:

  • function Possible(array A, string s)
    • Если s пусто, вернуть true.
    • вычислить массив Pиз всех строк в A, которые являются префиксом s.
    • Если P пусто, вернуть false.
    • для каждой строки p в P:
      • если Possible(A with p removed, s with prefix p removed) вернуть true
    • вернуть false
1 голос
/ 13 мая 2011

Мне кажется, что проблему можно решить простым линейным обходом массива и сравнением. Однако может быть несколько проходов. Вы можете разработать стратегию минимизации пропусков. Например, построение подмассива всех подстрок исходной строки при первом проходе. Затем попробуйте различные варианты линейно.

1 голос
/ 13 мая 2011

Вот как бы я это сделал.

  1. Определите длину целевой строки.
  2. Определить длину каждой строки в массиве подстрок
  3. Определите, какая комбинация подстрок даст строку той же длины, что и целевая строка (если есть, если нет, все готово)
  4. Генерация всех перестановок комбинаций подстрок, определенных на шаге 3. Проверьте, соответствует ли какая-либо из них целевой строке.

Генерация всех перестановок - сложная задача процессора, поэтому, если вы сможете сократить свой 'n' (размер ввода), вы получите значительную эффективность.

0 голосов
/ 14 мая 2011

Позвольте мне предложить использовать Суффикс-деревья (используя онлайновый алгоритм Укконена для его построения), который кажется подходящим с точки зрения поиска общих подстрок в двух текстах.Вы можете найти больше информации в википедии / специальных источниках.Задача -

Find all z occurrences of the patterns P1..Pn of total length m
enter code hereas substrings in O(m + z) time.

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

0 голосов
/ 13 мая 2011

Если каждая подстрока должна использоваться только один раз, но не все из них должны использоваться ...

Для каждой перестановки размера N из подстрок, равных по размеру исходной строке, отметьте ее, если ее нет, делайте перестановку из N + 1 элементов, и так далее, пока не исчерпаете все перестановки.

Конечно, NP завершено, медленно, как ад, но я думаю, что нормальных решений не существует.

Чтобы объяснить, почему решения, в которых удаление подстрок из исходной строки никогда не будет работать:

Иметь строку «1234123» и массив «12», «34», «123». Если вы удалите «123» с самого начала, у вас есть ложный минус. Аналогичный пример, где удаление с конца будет: «1234123»: «23,« 41 »,« 123 ».

С возвратом в жадность: (длина строки 7 м, количество элементов 3) - возьми самое длинное: 123 - удалить его с первого раза O (3) - попробуйте два других с остальными: не ходи + O ((n-1) * (m-3)) - возврат O (1) - убрать со второго: O (м-3) - попробуйте два других O ((n-1) * m-3) = O (30)

Перестановки 1 + 2 + 3 = O (3) + O (4) + O (6) = O (13). Таким образом, для небольших подмножеств длины перестановки на самом деле быстрее, чем возврат. Это изменится, если вы попросите найти много подстрок (в большинстве случаев, но не все).

Вы можете удалить только несуществующие подстроки из массива, чтобы уменьшить число перестановок с n ^ n до n ^ (n-1) для каждой удаленной несуществующей подстроки.

0 голосов
/ 13 мая 2011

Вот грубая идея, которая должна работать.

  1. Скопировать исходную строку в новую строку
  2. Пока в строке копирования все еще есть данные, и в ней все еще есть подстроки a.Захватите подстроку, если copy.contains (substr) copy.remove (substr)
  3. Если копия теперь пуста, тогда да, вы можете создать строку
  4. Если копия не пуста,выкиньте первую подстроку, которая была удалена из строки, и повторите.
  5. Если все подстроки пропали, а копия все еще не пуста, то нет, вы не можете создать ее.

Изменить: способ, возможно, улучшить это было бы сначала перебрать все подстроки и выбросить любые, которые не содержатся в основной строке.Затем выполните вышеуказанные шаги.

0 голосов
/ 13 мая 2011

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

1) Грубая сила: сделайте это так, как если бы вы использовали генератор паролей, т.е. word1 + word1 + word1> word1 + word1 + word2> word1 +слово1 + слово3 и т. д. и т. д. и т. п.

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

2) возьмите искомую строку и выполните поиск по ней для каждого слова, которое у вас есть 1 за раз.возможно, проверьте длину и, если она больше 0, сделайте это снова.продолжайте делать это, пока не достигнете нуля, он не может найти больше результатов.если вы нажмете 0, это победа, если не проигрыш.Я думаю, что этот метод будет намного лучше, чем первый, но я думаю, что у кого-то будет лучшее предложение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...