Мой ответ на эту загадку должен иметь 100%-е покрытие строки кода, который можно протестировать и 0%-е покрытие строки кода, который Вы не можете протестировать.
Моя существующая практика в Python должна разделить мои .py модули на две папки: app1/и app2/и когда рабочие модульные тесты вычисляют покрытие тех двух папок и визуально проверяют (я должны автоматизировать это когда-нибудь), что app1 имеет 100%-е покрытие, и app2 имеет 0%-е покрытие.
, Когда/если я нахожу, что эти числа отличаются от стандарта I investigage и изменяют дизайн кода так, чтобы покрытие соответствовало стандарту.
Это действительно означает, что я могу рекомендовать достигнуть 100%-го покрытия строки кода библиотеки.
я также иногда рассматриваю app2/, чтобы видеть, мог ли я возможный тест какой-либо код там, и Если я могу я перемещать его в app1 /
Теперь, я не слишком волнуюсь по поводу совокупного покрытия, потому что это может варьироваться дико в зависимости от размера проекта, но обычно я видел 70% к более чем 90%.
С Python, я должен быть в состоянии создать испытание с помощью дыма, которое могло автоматически запустить мое приложение при измерении покрытия и надо надеяться получить aggreagate 100% при объединении испытания с помощью дыма с числами unittest.
Вычислить количество комбинаций несложно. По сути, между каждыми двумя буквами стоит символ. Это может быть «эпсилон» (пустой) или пробел. Итак, для n букв у вас есть n-1 таких разделительных символов. Поскольку каждый символ может иметь только два значения, это то же самое, что двоичное число из n-1 цифр. Таким образом, у вас может быть 2 в степени n-1.
aids = 4 characters (n = 4)
n - 1 = 3
2 ** (n-1) = 8 combinations
Теперь для особых случаев. Если каждый символ может быть в нижнем или верхнем регистре, это 2 в степени n вариаций символов (если они являются буквами). Теперь КАЖДАЯ комбинация выше имеет 2 n вариантов на основе заглавных букв, поэтому конечный результат будет (2 (n-1)) * (2 ** n), что равно 2 ** (2 * п -1).
Для каждой дополнительной буквы вы либо удваиваете, либо в четыре раза (в зависимости от заглавной буквы) время, необходимое для их перечисления, как легко понять из формулы.
Общее количество символов немного сложнее, но достаточно заметить, что каждое «пространство» в половине случаев является «эпсилоном». Итак, у нас есть (2 ** (n-1)) * n (буквы) + ((2 ** (n-1)) * (n-1)) / 2 (пробелы). В примере:
(2 ** 3) * 4 = 32 characters
(2 ** 3) * 3 / 2 = 12 spaces
Total = 44 characters
Наконец, проблема расстояния связана с расстоянием Левенштейна . Я думал об использовании дерева Буркхарда-Келлера , но теперь вижу, что в этом нет необходимости, так как проблема гораздо проще.
Во-первых, минимальное количество вставок / удалений / изменений, необходимых для приравнивание одной строки к другой называется расстоянием Левенштейна . Это напрямую применимо к проблеме: вы добавляете пробелы, удаляете пробелы, при необходимости меняете нижний / верхний регистр. Обычно это лучше всего решается с помощью динамического программирования , которое в целом можно рассматривать как хранение данных о решении небольших частей проблемы, которые затем повторно используются при вычислении других частей / больших частей.
Но учитывая конкретные ограничения нашей проблемы, мы можем просто сравнить «двоичные» числа, представляющие пробелы / эпсилон.
Скажем, функция f (слово) вернет двоичное число, представляющее пробелы в этом слове. Например, он вернет «010» для «ai ds» и «111» для «help s». Количество изменений между каждой комбинацией определяется путем XOR результатов f (010 xor 111 = 101), а затем подсчета количества битов, равного 1. Пусть ' s напишите пару функций на Scala для этого:
def spacesToBinary(s: String) = {
(s zip s.drop(1)) // transform "ai ds" into ((a,i), (i, ), ( ,d), (d, s))
.foldLeft(0) { (binary, pair) => pair match {
case (' ', _) => binary // A space is always followed by a letter,
// so we ignore it
case (_, ' ') => (binary << 1) | 1 // If a letter is followed by a space, bit = 1
case _ => binary << 1 // If not, bit = 0
}}
}
def numberOfOnes(d: Int) = {
var bits = 0
var n = d
while(n != 0) {
if ((n & 1) == 1)
bits += 1
n >>= 1
}
bits
}
def spaceDistance(s1: String, s2: String) =
numberOfOnes(spacesToBinary(s1) ^ spacesToBinary(s2))
Теперь сравнивать строчные / прописные буквы довольно просто, если отбросить пробелы:
def caseDistance(s1: String, s2: String) = {
val ns1 = s1 filter (_ != ' ')
val ns2 = s2 filter (_ != ' ')
(ns1 zip ns2).foldLeft(0){(acc, pair) => acc + (if (pair._1 == pair._2) 0 else 1)}
}
Итак, расстояние Левенштейна равно:
def levenshtein(s1: String, s2: String) = spaceDistance(s1, s2) + caseDistance(s1, s2)
Мы также знаем следующие свойства о Расстояние Левенштейна. Скажем, d (x, y) - это расстояние Левенштейна между x и y. Тогда мы знаем:
d(x, y) = 0 if, and only if, x = y
d(x, y) = d(y, x)
d(x, y) + d(y, z) >= d(x, z)
Последний критерий i известен как неравенство треугольника. Проще говоря, путь от x до z по крайней мере такой же мал, как путь от x до y плюс путь от y до z (представьте себе треугольник с вершинами x, y и z).
Хорошо, давайте подумаем о бонусные вопросы.
Сколько путей между двумя словами? Что ж, если расстояние Левенштейна равно n, это означает, что у вас есть «n» уникальных модификаций, a1, a2, ..., an. Для каждого порядка этих модификаций у вас есть путь. Количество перестановок " представляющие пробелы и заглавные буквы каждой буквы, первая из которых состоит из n-1 цифр, а вторая - из n цифр.
Мы также знаем, что мы можем просто инвертировать любую из этих «цифр», чтобы получить разницу. Итак, если мы получаем одно большое двоичное число длиной 2 * n - 1 цифру и перечисляем все его цифры, комбинации на минимальном расстоянии d от него задаются комбинацией 2 * n-1 цифр в группах размера «d» без повторов. Для N = 2 * n-1 номер такой комбинации равен N! / ((Nd)! * D!).
Например, для расстояния 2 и «вспомогательных средств», n = 4, d = 2, N = 2 * 4-1 = 7, а количество комбинаций равно 7! / (5! * 2!) = 7 * 6/2 = 21.
Мы можем составить комбинации следующим образом:
def computeCombinations(n: Int, d: Int): List[List[Int]] = {
def gen(d: Int, l: List[Int]): List[List[Int]] = d match {
case 0 => List(List())
case _ => for(el <- l;
sl <- gen(d-1, l.dropWhile(_ != el).tail))
yield el :: sl
}
gen(d, (0 until n).toList)
}
Это вернет списки списков букв / пробелов для изменения. Мы указываем, какую букву или пробел нужно изменить, с помощью номера бита, который мы хотим переключить. Чтобы упростить ситуацию, мы предполагаем, что двоичное число для заглавной буквы и двоичное число для пробела / без пробела объединены в одно двоичное число.
Затем мы должны найти способ произвести вариации на основе этой информации. Верхний / нижний регистр прост, предполагая, что мы получаем слово без пробелов:
def toggleCharCase(c: Char) = (c ^ ('A' ^ 'a')).toChar
def toggleCases(s: String, bits: List[Int]) = (
s.zipWithIndex
map (t => if (Set(bits: _*) contains t._2) (toggleCharCase(t._1), t._2) else t)
map (_._1)
)
Переключение пробелов сложнее. Мы воспользуемся пространством spaceToBinary, определенным выше, преобразуем его в список установленных номеров битов, переключим запрошенные биты и вернем их. После этого мы используем другую функцию для вставки пробелов в нужные места:
def toggleSpaces(s: String, bits: List[Int]): List[Int] = {
val before = spacesToBinary(s)
val beforeBits = Set() ++
(for(i <- 0 to 30; // Assuming 32 bits, 1 for sign
if (scala.Math.pow(2, i).toInt & before) != 0)
yield i)
val afterBits = (beforeBits union Set(bits: _*)) diff
(beforeBits intersect Set(bits : _*))
afterBits.toList
}
def addSpaces(s: String, bits: List[Int]): String = (
s.filter(_ != ' ').zipWithIndex
map (t => t._1.toString + (if (bits contains t._2) " " else ""))
mkString
)
Теперь нам нужно вычислить разницу в пробелах, удалить пробелы, переключить регистры, а затем снова добавить пробелы. Посмотрим:
def changeCombination(s: String, n: Int, bits: List[Int]) = {
// Split the binary number with all changes into case changes and space changes
val spaceBits = bits filter (_ >= n) map (_ - n)
val caseBits = bits filter (_ < n)
// Compute the set of spaces after changing them
val newSpaces = toggleSpaces(s, spaceBits)
// Remove spaces and toggle case
val noSpaces = s filter (_ != ' ')
val caseChanged = toggleCases(noSpaces, caseBits).mkString
// Now add the spaces as computed before
val spacesAdded = addSpaces(caseChanged, newSpaces).mkString
spacesAdded
}
Наконец, мы вычисляем все комбинации, и затем создать измененную строку для каждого из них:
def makeCombinations(s: String, d: Int) = {
val n = (s filter (_ != ' ')).length
for(combination <- computeCombinations(n*2-1, d))
yield changeCombination(s, n, combination)
}
Весь этот код был протестирован на Scala 2.8 (за исключением некоторого переименования, которое я только что сделал). Вот результат прогона:
scala> makeCombinations("ai ds", 2) foreach println
AI ds
Ai Ds
Ai dS
A i ds
Aids
Ai d s
aI Ds
aI dS
a I ds
aIds
aI d s
ai DS
a i Ds
aiDs
ai D s
a i dS
aidS
ai d S
a ids
a i d s
aid s
As the other answers already said, there are 2^(n-1) possiblities for point 1. About some of the special cases (point 2):
Well, in that case you had 2^n different case combinations, so you had 2^n * 2^(n-1) = 2^(2 * n - 1) possibilities at all.
That's a more interesting question. You have 1 possibility to place no space, 3 possibilities to place 1 space, 3 possibilites to place 2 spaces and 1 possibility to place 3 spaces. This is a binomial distribution, if I recall correctly, and there are formulas to calculate the numbers. You can also use Pascal's triangle for this:
1
1 1
1 2 1
1 3 3 1 <- your case (4th row)
...
After you got these numbers, calculate the total character count like this:
1*4 + 3*5 + 3*6 + 1*7 = 44
http://www-cs-faculty.stanford.edu/~knuth/fasc3b.ps.gz (загрузите Ghostscript / Ghostview, если вы не можете просматривать postscript) обсуждает разделы подробнее.
Для последовательности длины n существует 2 ^ (n-1) разделов. Подумайте немного между каждой парой последовательных пунктов. Если бит установлен, они разделены (пробелом, как вы их указали). "aids" (длина 4) имеет 2 ^ 3. возможных разделов.
В ответ на другие ваши вопросы:
Время перечислить: O (n * 2 ^ n) - постоянная длина вывода. С увеличением длины ввода увеличивается не только количество элементов, но и количество символов в каждом элементе.
Количество написанных символов: давайте не будем считать символы новой строки (если вы это сделаете, добавьте еще 2 ^ (n-1) символы). Тогда у вас есть n * 2 ^ (n-1) непробельных символов, плюс количество единиц во всех уникальных цепочках битов n-1. Битовые строки из k цифр при записи имеют k * 2 ^ k бит, половина из которых равна 1. Таким образом, общее количество символов составляет [n + (n-1) / 2] * 2 ^ (n-1) ), не считая новых строк. В вашем списке из 8 вариантов «вспомогательных средств» 32 непробельных символа и 12 пробелов - 4 * 2 ^ 3 и (3/2) * 2 ^ 3 соответственно.
Изменить расстояние: у вас будет а точнее о преобразованиях и их стоимости. Под словом "слово" я предполагаю, что вы имеете в виду один раздел (одну из ваших 8 строк примера). Если редактирование - это удаление или добавление одного пробела, то вы говорите о расстоянии Хэмминга на битовых строках из n-1 цифр.
Таким образом, общее количество символов составляет [n + (n-1) / 2] * 2 ^ (n-1)), не считая новых строк. В вашем списке из 8 вариантов «вспомогательных средств» 32 непробельных символа и 12 пробелов - 4 * 2 ^ 3 и (3/2) * 2 ^ 3 соответственно.Изменить расстояние: у вас будет а точнее о преобразованиях и их стоимости. Под словом "слово" я предполагаю, что вы имеете в виду один раздел (одну из ваших 8 строк примера). Если редактирование - это удаление или добавление одного пробела, то вы говорите о расстоянии Хэмминга на битовых строках из n-1 цифр.
Таким образом, общее количество символов составляет [n + (n-1) / 2] * 2 ^ (n-1)), не считая новых строк. В вашем списке из 8 вариантов «вспомогательных средств» 32 непробельных символа и 12 пробелов - 4 * 2 ^ 3 и (3/2) * 2 ^ 3 соответственно.Изменить расстояние: у вас будет а точнее о преобразованиях и их стоимости. Под словом "слово" я предполагаю, что вы имеете в виду один раздел (одну из ваших 8 строк примера). Если редактирование - это удаление или добавление одного пробела, то вы говорите о расстоянии Хэмминга на битовых строках из n-1 цифр.
Я предполагаю, что вы имеете в виду один раздел (одна из ваших 8 строк примера). Если редактирование - это удаление или добавление одного пробела, то вы говорите о расстоянии Хэмминга на битовых строках из n-1 цифр. Я предполагаю, что вы имеете в виду один раздел (одна из ваших 8 строк примера). Если редактирование - это удаление или добавление одного пробела, то вы говорите о расстоянии Хэмминга на битовых строках из n-1 цифр.Аргументы подсчета верны.
Есть общий способ программирования подобных задач с использованием ветвлений и границ. Вот пример.
По сути, вместо того, чтобы писать цикл для сканирования строки, вы пишете рекурсивную функцию и отслеживаете стоимость в качестве одного из ее аргументов. Затем на каждом шаге вы можете 1) шагнуть вниз по строке за дополнительную плату, равную нулю, затем 2) внести небольшое изменение в строку, добавить приращение к стоимости, а затем шагнуть вперед и 3) повторить 2 для столько различных видов изменений, которые вы хотите учесть.
Тогда имейте общий бюджет затрат и откажитесь принимать любую ветвь, где затраты превышают бюджет.
Наконец, в качестве внешнего цикла сделайте все это один раз с бюджетом 0. Если это не дает совпадений, повторите попытку со стоимостью 1,
Простой алгоритм для посещения каждого из слова на расстоянии k или меньше: используя хеш-таблицу для посещения каждой строки битов только один раз (или массив из 2 ^ (n-1) бит, но это может быть слишком большим), рекурсивно посещайте каждую из соседних разностей однократного редактирования ( предполагая расстояние Хэмминга: для i от 1 .. (n-1), XOR 2 ^ i с исходной цепочкой битов, переключение i-го бита).
Сделайте это до глубины k (глубина передается вместе с вашей рекурсией), и вы посетите все изменения в пределах расстояния k. Конечно, если вам нужны только те, которые имеют глубину k, вы я захочу использовать поиск в ширину: вместо того, чтобы сразу посещать каждого соседа, держите их в очереди для посещения. При посещении очереди для элементов данного поколения (j) (все с одинаковым наилучшим расстоянием редактирования) ставьте будущие элементы в другую очередь для следующего поколения (j + 1). Таким образом, вы сначала просматриваете каждую строку, используя минимально возможное количество изменений (сначала ширина = сначала лучше, если каждый переход имеет одинаковую стоимость).
Если вы не хотите выполнять поиск в ширину, вы всегда можете вычислить набор слов в пределах k или меньше и набор в пределах k-1 или меньше, и возьмите разницу (вы бы использовали две отдельные таблицы). По сути, это «итеративный поиск с углублением».
BK-деревья здесь не подходят, если вы не рассматриваете неструктурированный набор слов (общий словарь).