Perl6: 解 work-break-ii
作者:gugod 發佈於: #perl6word-break-ii 是一則在 leetcode 上的題目。這題目對於實做搜尋引擎的人是有些實用的,並不是只是頭腦體操而己。
這題目內文及範列摘要如下:
Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, add spaces in s to construct a sentence where each word is a valid dictionary word. Return all such possible sentences.
Example 1:
Input:
s = "catsanddog"
wordDict = ["cat", "cats", "and", "sand", "dog"]
Output:
[
"cats and dog",
"cat sand dog"
]
之所以說這題目有實際用途,是因為使用者在輸入英文時不一定會好好地把字與字用空白 隔開,此演算法可以為 tokenizer 的一部份。另外就是這演算法幾乎可直接用於進行中 文分詞。
輸入會是一個字串 $s
,與一個陣列 @wordDict
。陣列的內容是字典。解完後的答案也要是陣列。
sub word-break-ii (Str $s, @wordDict) returns Array
我所思得的做法大致上是由 $s
的尾端向前搜。首先針對字串 $s
的每一個位置找出
所有結尾於此的單字、逐個向前「接龍」。途中若接龍接完了,則表示找到一組解答。或
者途中被發現不可能接得下去,則要避免去進行無謂的深度搜尋。
題目要求所有的解答,所以需要將所有選項找完為止。選用的搜尋演算法為 BFS,但想來
DFS 應該也行。此外,於進行搜尋演算之前,先建出一個索引 @choices
。對於字串的
每個位置 $i
,去記住所有於位置 $i
出現的單字列表。
事後重新想過一輪,覺得應該也可以由前方開始向後搜。或許日後再撰文筆記。
完整程式碼如下:
sub word-break-ii (Str $s, @wordDict) returns Array {
my @choices = (1..$s.chars).map({ Array.new });
loop (my $i = 0; $i < $s.chars; $i++) {
my $sx = $s.substr(0, $i+1);
for @wordDict.grep({ $sx.ends-with($_) }) -> $w {
my $j = $i - $w.chars;
if ($j == -1) || ($j >= 0 && @choices[$j].elems > 0) {
@choices[$i].push($w);
}
}
}
my @stash;
for @choices.tail.values -> $w {
@stash.push( ($s.chars- 1 - $w.chars) => [$w] );
}
my @ans;
while @stash.elems > 0 {
my $it = @stash.shift();
my $i = $it.key;
my @ansx = $it.value;
if ($i == -1) {
@ans.push( @ansx.join(" ") )
} else {
for @choices[$i].values -> $w {
@stash.push( ($i - $w.chars) => [$w].append(@ansx) );
}
}
}
return @ans;
}
leetcode 並不直接支援 perl6 ,因此無法簡單地去測試這段程式碼是否能過關。但至少以 下幾項範例的測試結果看來都是正確的:
say word-break-ii( "cat", ["cat", "cats", "and", "sand", "dog"]).perl;
say word-break-ii( "catsanddog", ["cat", "cats", "and", "sand", "dog"]).perl;
say word-break-ii( "pineapplepenapple", ["pen", "pine", "pineapple", "apple", "pie"] ).perl;
say word-break-ii( "catsandog", ["cats", "dog", "sand", "and", "cat"]).perl;