如何将字符串拆分为单词。例如:“stringintowords”->“String Into Words”?
Posted
技术标签:
【中文标题】如何将字符串拆分为单词。例如:“stringintowords”->“String Into Words”?【英文标题】:How to split a string into words. Ex: "stringintowords" -> "String Into Words"? 【发布时间】:2011-03-28 20:59:25 【问题描述】:将字符串拆分为单词的正确方法是什么? (字符串不包含任何空格或标点符号)
例如:“stringintowords”->“String Into Words”
您能告诉我这里应该使用什么算法吗?
!更新:对于那些认为这个问题只是出于好奇的人。该算法可用于对域名(“sportandfishing .com”->“SportAndFishing .com”)进行大写,目前 aboutus dot org 使用该算法动态进行此转换。
【问题讨论】:
【参考方案1】:假设您有一个函数isWord(w)
,它使用字典检查w
是否是一个单词。为简单起见,我们现在还假设您只想知道对于某个单词w
是否可以进行这样的拆分。这可以通过动态规划轻松完成。
让S[1..length(w)]
是一个带有布尔条目的表。如果单词w[1..i]
可以拆分,则S[i]
为真。然后将S[1] = isWord(w[1])
和for i=2
设置为length(w)
计算
S[i] = (isWord[w[1..i] 或 2..i 中的任何 j:S[j-1] 和 isWord[j..i])。
如果字典查询是常数时间,这需要 O(length(w)^2) 时间。要真正找到拆分,只需将获胜拆分存储在每个设置为 true 的 S[i] 中。这也可以适用于通过存储所有此类拆分来枚举所有解决方案。
【讨论】:
如何拆分单词?假设 dict 包含“by, bygone, gone, days”,而单词字符串是“bygonedays”。我想要最大分割数 - 所以输出必须是“过去的日子”而不是“过去的日子” 原题没有要求获取最大字数。如果需要,只需在每个表条目中跟踪此数字即可。 要真正找到拆分,我们不能只将拆分存储在设置为 true 的 S 中。比如“splitting”这个词,可以有“split”和“splitting”,这就形成了一个bool数组:[f,f,f,f,true,f,f,f,true],所以最后,根据您的算法,我们最终可能会说:“拆分”和“ting”是解决方案(尽管“ting”不是一个有效的词)。也许我们可以存储一个列表,而不是将 bool 值存储在数组中,其中包含到目前为止所有有效的拆分,最后,我们可以简单地检查数组的最后一个槽以获取解决方案。【参考方案2】:正如这里很多人提到的,这是一个标准的、简单的动态规划问题:Falk Hüffner 给出了最佳解决方案。附加信息:
(a) 您应该考虑使用 trie 实现 isWord,如果使用得当(即通过增量测试单词),这将为您节省大量时间。
(b) 键入“分段动态编程”会产生更详细的答案,这些答案来自大学级别的讲座,使用伪代码算法,例如this lecture at Duke's(甚至提供了一种简单的概率方法来处理当您有任何字典中都不包含的单词时该怎么办)。
【讨论】:
【参考方案3】:如果你想确保你做对了,你将不得不使用基于字典的方法,它的效率会非常低。您还必须期望从您的算法中收到多个结果。
例如:windowsteamblog
(http://windowsteamblog.com/ 成名)
windows
team
blog
window
steam
blog
【讨论】:
同意字典是必要的,但为什么你认为它会那么低效?这是 Tries 的典型应用...... @Jérémie,好吧,也许效率低下不是正确的选择,也许“非常慢”会更好 =) window steam 博客永远不会是网站!我也非常支持它,但不是:msft。 =(【参考方案4】:在学术文献中应该有相当多的内容。您要搜索的关键字是word segmentation。例如,This paper 看起来很有前途。
一般来说,您可能想了解markov models 和viterbi algorithm。后者是一种动态编程算法,它可以让您找到字符串的合理分段,而无需详尽地测试每个可能的分段。这里的基本见解是,如果您对前 m 个字符有 n 个可能的分段,并且您只想找到最有可能的分段,则无需针对后续字符评估每个分段 - 您只需要继续评估最有可能的。
【讨论】:
我认为开箱即用的解决方案太复杂了,这显然是意料之中的 :)【参考方案5】:考虑给定字符串的可能拆分数量。如果字符串中有n
字符,则有n-1
可能的分割位置。例如,对于字符串cat
,可以在a
之前拆分,也可以在t
之前拆分。这会导致 4 种可能的分裂。
您可以将此问题视为选择需要拆分字符串的位置。您还需要选择拆分的数量。所以有Sum(i = 0 to n - 1, n - 1 choose i)
可能的分裂。通过Binomial Coefficient Theorem,x 和 y 都为 1,这等于 pow(2, n-1)。
当然,很多这种计算都依赖于常见的子问题,因此Dynamic Programming 可能会加快您的算法。在我的脑海中,计算boolean matrix M such M[i,j] is true if and only if the substring of your given string from i to j is a word
会有很大帮助。你仍然有指数级的可能分割,但如果早期分割没有形成一个词,你很快就能消除分割。然后,一个解决方案将是一个整数序列(i0、j0、i1、j1、...),条件为 j sub k
= i sub (k + 1)
。
如果您的目标是正确的驼峰式 URL,我会回避这个问题并采取更直接的方法:获取 URL 的主页,从源 html 中删除所有空格和大写字母,然后搜索您的字符串。如果匹配,则在原始 HTML 中找到该部分并将其返回。您需要一个 NumSpaces 数组来声明原始字符串中出现了多少空格,如下所示:
Needle: isashort
Haystack: This is a short phrase
Preprocessed: thisisashortphrase
NumSpaces : 000011233333444444
您的答案将来自:
location = prepocessed.Search(Needle)
locationInOriginal = location + NumSpaces[location]
originalLength = Needle.length() + NumSpaces[location + needle.length()] - NumSpaces[location]
Haystack.substring(locationInOriginal, originalLength)
当然,如果 madduckets.com 在主页的某处没有“Mad Duckets”,这将会中断。唉,这就是你为避免指数级问题而付出的代价。
【讨论】:
【参考方案6】:这基本上是knapsack problem 的变体,因此您需要的是一个完整的单词列表以及 Wiki 中涵盖的任何解决方案。
对于相当大的字典,这将是非常耗费资源和冗长的操作,您甚至无法确定这个问题是否会得到解决。
【讨论】:
实际上,它不需要像背包问题那样昂贵。您可以应用动态编程技术来大幅减少搜索空间。 是的,同意尼克约翰逊的观点:这是一个标准的、简单的 O(n^2) 动态规划问题。抛出一个NP完全问题就像试图用手提钻切面包!!!【参考方案7】:创建一个可能的单词列表,从长单词到短单词排序。
检查列表中的每个条目是否与字符串的第一部分相对应。如果相等,请将其删除并在您的句子中附加一个空格。重复这个。
【讨论】:
【参考方案8】:这实际上可以在没有字典的情况下(在一定程度上)完成。本质上,这是一个无监督的分词问题。您需要收集大量域名,应用无监督分割学习算法(例如Morfessor)并将学习模型应用于新域名。不过,我不确定它的效果如何(但它会很有趣)。
【讨论】:
【参考方案9】:其实用字典可以在O(n)
时间解决这个问题。更准确地说是(k + 1) * n
,最糟糕的是,其中n
是字符串中的字符数,k
是字典中最长单词的长度。
此外,该算法允许您跳过垃圾。
这是我前段时间在 Common Lisp 中创建的工作实现:https://gist.github.com/3381522
【讨论】:
【参考方案10】:您可以将该字符串拆分为单词的唯一方法是使用字典。尽管这可能会占用大量资源。
【讨论】:
【参考方案11】:最好的办法是将 0 的子字符串与字典进行比较,当您找到匹配项时,提取该单词并从该点开始新的字典搜索......但这将非常容易出错,并且你会遇到复数和撇号(sinks、sink's)和其他词性的问题。
编辑
“singleemotion”会变成“singleemotion”还是“sing glee motion”?
【讨论】:
【参考方案12】:我正在研究这个问题,并想也许我可以分享一下我是如何做到的。 用文字解释我的算法有点太难了,所以也许我可以用伪代码分享我的优化解决方案:
string mainword = "stringintowords";
array substrings = get_all_substrings(mainword);
/** this way, one does not check the dictionary to check for word validity
* on every substring; It would only be queried once and for all,
* eliminating multiple travels to the data storage
*/
string query = "select word from dictionary where word in " + substrings;
array validwords = execute(query).getArray();
validwords = validwords.sort(length, desc);
array segments = [];
while(mainword != "")
for(x = 0; x < validwords.length; x++)
if(mainword.startswith(validwords[x]))
segments.push(validwords[x]);
mainword = mainword.remove(v);
x = 0;
/**
* remove the first character if any of valid words do not match, then start again
* you may need to add the first character to the result if you want to
*/
mainword = mainword.substring(1);
string result = segments.join(" ");
【讨论】:
【参考方案13】:一个运行时间为 O(n^2) 的简单 Java 解决方案。
public class Solution
// should contain the list of all words, or you can use any other data structure (e.g. a Trie)
private HashSet<String> dictionary;
public String parse(String s)
return parse(s, new HashMap<String, String>());
public String parse(String s, HashMap<String, String> map)
if (map.containsKey(s))
return map.get(s);
if (dictionary.contains(s))
return s;
for (int left = 1; left < s.length(); left++)
String leftSub = s.substring(0, left);
if (!dictionary.contains(leftSub))
continue;
String rightSub = s.substring(left);
String rightParsed = parse(rightSub, map);
if (rightParsed != null)
String parsed = leftSub + " " + rightParsed;
map.put(s, parsed);
return parsed;
map.put(s, null);
return null;
【讨论】:
以上是关于如何将字符串拆分为单词。例如:“stringintowords”->“String Into Words”?的主要内容,如果未能解决你的问题,请参考以下文章