如何将没有空格的文本拆分成单词列表

152

输入: "tableapplechairtablecupboard..." 许多单词

有什么有效的算法可以将这样的文本拆分成单词列表并获得:

输出: ["table", "apple", "chair", "table", ["cupboard", ["cup", "board"]], ...]

首先想到的是遍历所有可能的单词(从第一个字母开始)并找到最长的单词,然后从position = word_position + len(word)继续。

P.S.
我们有所有可能单词的列表。
"cupboard"单词可以是"cup"和"board",选择最长的那个单词。
语言:Python,但主要是算法本身。


2
@Sergey - 你的“最长可能”的标准意味着它是针对复合词的。在这种情况下,如果字符串是“carpetrel”,会是“carpet”还是“petrel”? - Rob Hruska
3
你的字符串中有许多词汇: ['able', 'air', 'apple', 'boa', 'boar', 'board', 'chair', 'cup', 'cupboard', 'ha', 'hair', 'lea', 'leap', 'oar', 'tab', 'table', 'up'] - reclosedev
我知道有很多单词,但如果你之前选择了错误的单词,那么在某个时候你将无法得到下一个单词。 - Sergey
使用“解析错误”来做决策可能是危险的。如果您的输入字符串中真的有错误呢?或者有一个您没有的单词? - Rik Poggi
这个回答解决了你的问题吗?需要帮助理解这个Python Viterbi算法 - Matthias Winkelmann
显示剩余2条评论
18个回答

273
一个朴素算法在处理真实世界的数据时往往效果不佳。下面是一个利用相对词频提供真实文本准确结果的20行算法。
(如果您想获得一个不使用词频的答案,您需要精确定义“最长单词”到底是什么意思:一个20个字母的单词和十个3个字母的单词哪个更好?还是五个10个字母的单词更好?一旦你确定了一个精确的定义,你只需要改变定义wordcost的那一行代码,以反映出预期的含义。)
想法
最好的方法是建立输出分布的模型。一个好的第一次近似是假设所有的单词都是独立分布的。然后你只需要知道所有单词的相对频率。可以合理地假设它们遵循Zipf's定律,也就是说,在单词列表中排名为n的单词具有大约1/(nlogN)的概率,其中N是字典中单词的数目。
一旦你确定了模型,你就可以使用动态规划来推断空格的位置。最可能的句子是使每个单词的概率的乘积最大化的句子,而这很容易用动态规划来计算。我们使用一个定义为概率的倒数的对数的成本,以避免溢出。
代码
from math import log

# Build a cost dictionary, assuming Zipf's law and cost = -math.log(probability).
words = open("words-by-frequency.txt").read().split()
wordcost = dict((k, log((i+1)*log(len(words)))) for i,k in enumerate(words))
maxword = max(len(x) for x in words)

def infer_spaces(s):
    """Uses dynamic programming to infer the location of spaces in a string
    without spaces."""

    # Find the best match for the i first characters, assuming cost has
    # been built for the i-1 first characters.
    # Returns a pair (match_cost, match_length).
    def best_match(i):
        candidates = enumerate(reversed(cost[max(0, i-maxword):i]))
        return min((c + wordcost.get(s[i-k-1:i], 9e999), k+1) for k,c in candidates)

    # Build the cost array.
    cost = [0]
    for i in range(1,len(s)+1):
        c,k = best_match(i)
        cost.append(c)

    # Backtrack to recover the minimal-cost string.
    out = []
    i = len(s)
    while i>0:
        c,k = best_match(i)
        assert c == cost[i]
        out.append(s[i-k:i])
        i -= k

    return " ".join(reversed(out))

你可以与之一起使用的是

s = 'thumbgreenappleactiveassignmentweeklymetaphor'
print(infer_spaces(s))

结果

我使用了这个我从维基百科的一个小子集中组合起来的快速且简短的125k词典。

Before: thumbgreenappleactiveassignmentweeklymetaphor.
After: thumb green apple active assignment weekly metaphor.

Before: 有大量从HTML中解析出来的人们评论的文本信息,但是它们并没有分隔符,例如thumb green apple active assignment weekly metaphor。显然字符串中也有thumb green apple等单词,在我的字典中也有查询是否合理的大量单词,所以最快的提取方式是什么?多谢。

After: 有大量从HTML中解析出来的人们评论的文本信息,但是它们并没有分隔符,例如thumb green apple active assignment weekly metaphor。显然字符串中也有thumb green apple等单词,在我的字典中也有查询是否合理的大量单词,所以最快的提取方式是什么?多谢。

Before: 这是一个又黑暗又有风暴的夜晚,雨水倾盆而下,除了偶尔的间歇时刮起的狂风,街道上一片漆黑。我们的场景在伦敦,屋顶上格格作响,灯光微弱,几乎被黑暗吞噬。

After: 这是一个又黑暗又有风暴的夜晚,雨水倾盆而下,除了偶尔的间歇时刮起的狂风,街道上一片漆黑。我们的场景在伦敦,屋顶上格格作响,灯光微弱,几乎被黑暗吞噬。

您可以看到这个方法基本上是完美无瑕的,最重要的部分是确保您的词汇表训练于与实际遇到相似的语料库中,否则结果将很糟糕。


优化

该实现消耗时间和内存量呈线性增长,因此效率还算不错。如果您需要更快的速度,可以从单词列表构建后缀树以缩小候选集的大小。

如果您需要处理非常大的连续字符串,则将字符串拆分为减少内存使用量是合理的。例如,您可以将文本按10000个字符的块处理,再加上每侧1000个字符的边距以避免边界效应。这将使内存使用量最小,并且几乎不会影响质量。


不错的函数! 在“namethecompanywherebonniewasemployedwhenwestarteddating”上使用https://github.com/dwyl/english-words/blob/master/words.txt会得到“name the comp anywhere bonnie was employed when we started dating”的结果。你知道为什么吗?是因为字典的原因吗? - wittrup
26
太棒了!我已将其制作成pip软件包: https://pypi.python.org/pypi/wordninjapip install wordninja - keredson
2
@wittrup 你的 words.txt 文件中包含 "comp":$ grep "^comp$" words.txt comp并且它是按字母顺序排序的。这段代码假设它按出现频率降序排序(这在像 n-gram 列表这样的列表中很常见)。 如果你使用一个正确排序的列表,你的字符串就会正常输出:>>> wordninja.split('namethecompanywherebonniewasemployedwhenwestarteddating') ['name', 'the', 'company', 'where', 'bonnie', 'was', 'employed', 'when', 'we', 'started', 'dating'] - keredson
这主要取决于训练语料库,去除空格时会产生很多歧义。"hishowme" 可以被分成 "hi show me" 和 "his how me"(从 "wordninja" 的实际输出中)。我建议根据您的需要仔细适应语料库,并在使用自由文本减少噪音时小心,因为它可能会导致更多的混乱。 - leoschet
作为一种改进,您还可以考虑某些单词出现在其他单词旁边的概率,即使用n-gram。 - leoschet
显示剩余6条评论

96

基于顶级答案的优秀工作,我已经创建了一个pip软件包以便于使用。

>>> import wordninja
>>> wordninja.split('derekanderson')
['derek', 'anderson']

安装方法:运行pip install wordninja

唯一的区别是微小的。它返回一个list而不是str,在python3中工作,包括单词列表,并正确地分割非字母字符(如下划线、破折号等)。

再次感谢Generic Human!

https://github.com/keredson/wordninja


@keredson - 首先,感谢您提供的解决方案。它的表现很好。但是,它会剥离特殊字符,比如“-”等。有时它无法正确分割长字符串,例如 - “WeatheringPropertiesbyMaterial Trade Name Graph 2-1. Color Change, E, after Arizona, Florida, Cycolac®/Geloy® Resin Systems Compared to PVC.[15] 25 20 15 ∆E 10 5 0 PVC, White PVC, Brown C/G, BrownC/G. Capstock is the material used as the surface layer applied to the exterior surface of a profile extrusion. Geloy® resin capstock over a Cycolac® substrate provides outstanding weatherability.[25]” - Rakesh Lamp Stack
你可以在 GH 上开一个问题吗? - keredson
我可以忽略本地名称吗?有没有办法提供一个要忽略的单词列表? - Sunil Garg
代码很好,但似乎忽略了重音符号lm.split('knihyokoních') = ['knihy', 'ok', 'on', 'ch'],í丢失了。另一件事是,如果我将一些单词放在列表顶部,它们似乎在拆分这个短语时被忽略了。奇怪。 - luky
正则表达式分割器的目的是什么?我建议将其更改为这个_SPLIT_RE = re.compile("[^'\w'']+"),以支持重音符号,例如“knihyokoních”,或者也可以将其作为某些自定义参数。因为当前版本也会通过重音字符进行拆分,并且会丢失它们。 - luky
@keredson 非常感谢您提供的解决方案。 我遇到了这样一种情况,输入字符串中插入了一些随机字母。 例如在“btmicrosoffilesecurebthomelogindropboxupdatetofficedropbox”中,“microsof”的末尾缺少“t”,而“update”后面的“t”是额外添加的。 您能否想到一种方法来跳过随机单词并从字符串中获取最有可能的单词。 请帮忙。 - Mathee

18

这是一种使用递归搜索的解决方案:

def find_words(instring, prefix = '', words = None):
    if not instring:
        return []
    if words is None:
        words = set()
        with open('/usr/share/dict/words') as f:
            for line in f:
                words.add(line.strip())
    if (not prefix) and (instring in words):
        return [instring]
    prefix, suffix = prefix + instring[0], instring[1:]
    solutions = []
    # Case 1: prefix in solution
    if prefix in words:
        try:
            solutions.append([prefix] + find_words(suffix, '', words))
        except ValueError:
            pass
    # Case 2: prefix not in solution
    try:
        solutions.append(find_words(suffix, prefix, words))
    except ValueError:
        pass
    if solutions:
        return sorted(solutions,
                      key = lambda solution: [len(word) for word in solution],
                      reverse = True)[0]
    else:
        raise ValueError('no solution')

print(find_words('tableapplechairtablecupboard'))
print(find_words('tableprechaun', words = set(['tab', 'table', 'leprechaun'])))
产出
['table', 'apple', 'chair', 'table', 'cupboard']
['tab', 'leprechaun']

可以直接使用,非常感谢!我也考虑使用Trie结构,就像Miku所说的那样,而不仅仅是所有单词的集合。无论如何,还是非常感谢! - Sergey

12

Generic Human答案很好。但我见过的最好的实现是Peter Norvig自己在他的书“美丽的数据”中写的。

在我贴出他的代码之前,让我解释一下为什么Norvig的方法更准确(虽然稍微慢一些,代码也长一些)。

  1. 数据更好 - 在大小和精度方面都更好(他使用单词计数而不是简单的排名)
  2. 更重要的是,n-gram背后的逻辑真正使得这种方法如此准确。

他在书中提供的例子是将字符串'sitdown'拆分成两个单词。现在,一个非bigram方法的字符串拆分会考虑p('sit') * p ('down'),如果这个结果小于p('sitdown') - 这通常是情况 - 它就不会拆分,但我们希望它能够拆分(大多数情况下)。

然而,当你使用bigram模型时,你可以将p('sit down')视为bigram,而不是p('sitdown'),前者更优。基本上,如果你不使用bigrams,它会将你正在拆分的单词的概率视为独立的,这并不是事实,有些单词更有可能相互跟随出现。不幸的是,这些单词在很多情况下也会被黏在一起,从而使分割器变得混乱。

以下是数据链接(它是3个单独问题的数据,分词仅是其中之一。请阅读章节以了解详情):http://norvig.com/ngrams/

这是代码链接:http://norvig.com/ngrams/ngrams.py

这些链接已经存在一段时间了,但我还是会将代码的分割部分复制粘贴在此处。

import re, string, random, glob, operator, heapq
from collections import defaultdict
from math import log10

def memo(f):
    "Memoize function f."
    table = {}
    def fmemo(*args):
        if args not in table:
            table[args] = f(*args)
        return table[args]
    fmemo.memo = table
    return fmemo

def test(verbose=None):
    """Run some tests, taken from the chapter.
    Since the hillclimbing algorithm is randomized, some tests may fail."""
    import doctest
    print 'Running tests...'
    doctest.testfile('ngrams-test.txt', verbose=verbose)

################ Word Segmentation (p. 223)

@memo
def segment(text):
    "Return a list of words that is the best segmentation of text."
    if not text: return []
    candidates = ([first]+segment(rem) for first,rem in splits(text))
    return max(candidates, key=Pwords)

def splits(text, L=20):
    "Return a list of all possible (first, rem) pairs, len(first)<=L."
    return [(text[:i+1], text[i+1:]) 
            for i in range(min(len(text), L))]

def Pwords(words): 
    "The Naive Bayes probability of a sequence of words."
    return product(Pw(w) for w in words)

#### Support functions (p. 224)

def product(nums):
    "Return the product of a sequence of numbers."
    return reduce(operator.mul, nums, 1)

class Pdist(dict):
    "A probability distribution estimated from counts in datafile."
    def __init__(self, data=[], N=None, missingfn=None):
        for key,count in data:
            self[key] = self.get(key, 0) + int(count)
        self.N = float(N or sum(self.itervalues()))
        self.missingfn = missingfn or (lambda k, N: 1./N)
    def __call__(self, key): 
        if key in self: return self[key]/self.N  
        else: return self.missingfn(key, self.N)

def datafile(name, sep='\t'):
    "Read key,value pairs from file."
    for line in file(name):
        yield line.split(sep)

def avoid_long_words(key, N):
    "Estimate the probability of an unknown word."
    return 10./(N * 10**len(key))

N = 1024908267229 ## Number of tokens

Pw  = Pdist(datafile('count_1w.txt'), N, avoid_long_words)

#### segment2: second version, with bigram counts, (p. 226-227)

def cPw(word, prev):
    "Conditional probability of word, given previous word."
    try:
        return P2w[prev + ' ' + word]/float(Pw[prev])
    except KeyError:
        return Pw(word)

P2w = Pdist(datafile('count_2w.txt'), N)

@memo 
def segment2(text, prev='<S>'): 
    "Return (log P(words), words), where words is the best segmentation." 
    if not text: return 0.0, [] 
    candidates = [combine(log10(cPw(first, prev)), first, segment2(rem, first)) 
                  for first,rem in splits(text)] 
    return max(candidates) 

def combine(Pfirst, first, (Prem, rem)): 
    "Combine first and rem results into one (probability, words) pair." 
    return Pfirst+Prem, [first]+rem 

这个很好用,但是当我尝试将其应用于整个数据集时,它一直显示“RuntimeError: maximum recursion depth exceeded in cmp”。 - Harry M
ngrams肯定会给你带来更高的准确性,使用指数级别更大的频率字典,但也会增加内存和计算的使用量。顺便说一句,备忘录函数像漏斗一样泄露内存。在调用之间应该清除它。 - keredson

12

使用trie 数据结构来存储可能的单词列表,执行以下操作不会太复杂:

  1. 推进指针(在连接的字符串中)
  2. 查找并存储trie中对应的节点
  3. 如果trie节点有子节点(例如有更长的单词),则返回1。
  4. 如果到达的节点没有子节点,则发生了最长匹配;将单词(存储在节点中或在trie遍历期间连接)添加到结果列表中,重置trie中的指针(或重置引用),并重新开始

3
如果目标是消耗整个字符串,你需要回溯,然后在 "tab" 后面拆分 "tableprechaun"。 - Daniel Fischer
提到trie很好,但我也同意Daniel的观点,需要进行回溯。 - Sergey
@Daniel,最长匹配搜索不需要回溯。你为什么这么认为?上面的算法有什么问题吗? - Devin Jeanpierre
2
@Devin 对于“tableprechaun”,从开头开始最长的匹配是“table”,剩下的“prechaun”无法分成字典单词。因此,您必须选择较短的匹配项“tab”,留下“leprechaun”。 - Daniel Fischer
@Daniel,抱歉,是的。我误解了问题。修正后的算法应该同时跟踪所有可能的树位置——也就是线性时间NFA搜索。否则就回溯,但这是最坏情况下的指数时间。 - Devin Jeanpierre

11
Unutbu的解决方案相当接近,但我发现代码难以阅读,并且没有产生预期的结果。Generic Human的解决方案的缺点是它需要单词频率。不适用于所有用例。
这里有一个使用分治算法的简单解决方案。

翻译如下:

  1. 它试图将单词数量最小化。例如,find_words('cupboard')将返回['cupboard']而不是['cup', 'board'](假设字典中有cupboardcupboard)。
  2. 最优解不唯一,下面的实现返回一个解答。find_words('charactersin')可以返回['characters', 'in']或者可能会返回['character', 'sin'](如下所示)。您可以很容易地修改算法以返回所有最优解。
  3. 在此实现中,解决方案被记忆化,以便在合理的时间内运行。

代码如下:

words = set()
with open('/usr/share/dict/words') as f:
    for line in f:
        words.add(line.strip())

solutions = {}
def find_words(instring):
    # First check if instring is in the dictionnary
    if instring in words:
        return [instring]
    # No... But maybe it's a result we already computed
    if instring in solutions:
        return solutions[instring]
    # Nope. Try to split the string at all position to recursively search for results
    best_solution = None
    for i in range(1, len(instring) - 1):
        part1 = find_words(instring[:i])
        part2 = find_words(instring[i:])
        # Both parts MUST have a solution
        if part1 is None or part2 is None:
            continue
        solution = part1 + part2
        # Is the solution found "better" than the previous one?
        if best_solution is None or len(solution) < len(best_solution):
            best_solution = solution
    # Remember (memoize) this solution to avoid having to recompute it
    solutions[instring] = best_solution
    return best_solution

这将在我的3GHz机器上大约需要5秒钟:
result = find_words("thereismassesoftextinformationofpeoplescommentswhichisparsedfromhtmlbuttherearenodelimitedcharactersinthemforexamplethumbgreenappleactiveassignmentweeklymetaphorapparentlytherearethumbgreenappleetcinthestringialsohavealargedictionarytoquerywhetherthewordisreasonablesowhatsthefastestwayofextractionthxalot")
assert(result is not None)
print ' '.join(result)

从人们的评论中解析出来的文本信息很多,但是它们没有被分隔开。例如,字符串中有“thumb”,“green apple”等单词。我也有一个大型字典可以查询这些单词是否合理。那么,最快的提取方法是什么?非常感谢。

没有理由认为一个文本不能以单个字母结尾。你应该考虑再分割一次。 - panda-34

4

这里是被接受的答案,已翻译为JavaScript(需要node.js和来自https://github.com/keredson/wordninja的文件“wordninja_words.txt”):

var fs = require("fs");

var splitRegex = new RegExp("[^a-zA-Z0-9']+", "g");
var maxWordLen = 0;
var wordCost = {};

fs.readFile("./wordninja_words.txt", 'utf8', function(err, data) {
    if (err) {
        throw err;
    }
    var words = data.split('\n');
    words.forEach(function(word, index) {
        wordCost[word] = Math.log((index + 1) * Math.log(words.length));
    })
    words.forEach(function(word) {
        if (word.length > maxWordLen)
            maxWordLen = word.length;
    });
    console.log(maxWordLen)
    splitRegex = new RegExp("[^a-zA-Z0-9']+", "g");
    console.log(split(process.argv[2]));
});


function split(s) {
    var list = [];
    s.split(splitRegex).forEach(function(sub) {
        _split(sub).forEach(function(word) {
            list.push(word);
        })
    })
    return list;
}
module.exports = split;


function _split(s) {
    var cost = [0];

    function best_match(i) {
        var candidates = cost.slice(Math.max(0, i - maxWordLen), i).reverse();
        var minPair = [Number.MAX_SAFE_INTEGER, 0];
        candidates.forEach(function(c, k) {
            if (wordCost[s.substring(i - k - 1, i).toLowerCase()]) {
                var ccost = c + wordCost[s.substring(i - k - 1, i).toLowerCase()];
            } else {
                var ccost = Number.MAX_SAFE_INTEGER;
            }
            if (ccost < minPair[0]) {
                minPair = [ccost, k + 1];
            }
        })
        return minPair;
    }

    for (var i = 1; i < s.length + 1; i++) {
        cost.push(best_match(i)[0]);
    }

    var out = [];
    i = s.length;
    while (i > 0) {
        var c = best_match(i)[0];
        var k = best_match(i)[1];
        if (c == cost[i])
            console.log("Alert: " + c);

        var newToken = true;
        if (s.slice(i - k, i) != "'") {
            if (out.length > 0) {
                if (out[-1] == "'s" || (Number.isInteger(s[i - 1]) && Number.isInteger(out[-1][0]))) {
                    out[-1] = s.slice(i - k, i) + out[-1];
                    newToken = false;
                }
            }
        }

        if (newToken) {
            out.push(s.slice(i - k, i))
        }

        i -= k

    }
    return out.reverse();
}

3

这将有所帮助

from wordsegment import load, segment
load()
segment('providesfortheresponsibilitiesofperson')


2
如果你将单词列表预编译成DFA(这会非常慢),那么匹配输入所需的时间将与字符串长度成正比(实际上,只比遍历字符串稍微慢一点)。这实际上是之前提到的trie算法的更通用版本。我只是为了完整性而提及它--目前还没有可以直接使用的DFA实现。 RE2可以工作,但我不知道Python绑定是否允许您调整在什么大小下允许DFA丢弃已编译的DFA数据并执行NFA搜索。

特别是对于re2,我以前没有使用过,这是一个加分项。 - Sergey

1

非常感谢在https://github.com/keredson/wordninja/中的帮助。

我也提供一个关于Java的小贡献。

可以将公共方法splitContiguousWords与该类中包含忍者单词文本文件的其他两个方法一起嵌入同一目录中(或根据编码人员的选择进行修改)。并且可以使用方法splitContiguousWords实现该目的。

public List<String> splitContiguousWords(String sentence) {

    String splitRegex = "[^a-zA-Z0-9']+";
    Map<String, Number> wordCost = new HashMap<>();
    List<String> dictionaryWords = IOUtils.linesFromFile("ninja_words.txt", StandardCharsets.UTF_8.name());
    double naturalLogDictionaryWordsCount = Math.log(dictionaryWords.size());
    long wordIdx = 0;
    for (String word : dictionaryWords) {
        wordCost.put(word, Math.log(++wordIdx * naturalLogDictionaryWordsCount));
    }
    int maxWordLength = Collections.max(dictionaryWords, Comparator.comparing(String::length)).length();
    List<String> splitWords = new ArrayList<>();
    for (String partSentence : sentence.split(splitRegex)) {
        splitWords.add(split(partSentence, wordCost, maxWordLength));
    }
    log.info("Split word for the sentence: {}", splitWords);
    return splitWords;
}

private String split(String partSentence, Map<String, Number> wordCost, int maxWordLength) {
    List<Pair<Number, Number>> cost = new ArrayList<>();
    cost.add(new Pair<>(Integer.valueOf(0), Integer.valueOf(0)));
    for (int index = 1; index < partSentence.length() + 1; index++) {
        cost.add(bestMatch(partSentence, cost, index, wordCost, maxWordLength));
    }
    int idx = partSentence.length();
    List<String> output = new ArrayList<>();
    while (idx > 0) {
        Pair<Number, Number> candidate = bestMatch(partSentence, cost, idx, wordCost, maxWordLength);
        Number candidateCost = candidate.getKey();
        Number candidateIndexValue = candidate.getValue();
        if (candidateCost.doubleValue() != cost.get(idx).getKey().doubleValue()) {
            throw new RuntimeException("Candidate cost unmatched; This should not be the case!");
        }
        boolean newToken = true;
        String token = partSentence.substring(idx - candidateIndexValue.intValue(), idx);
        if (token != "\'" && output.size() > 0) {
            String lastWord = output.get(output.size() - 1);
            if (lastWord.equalsIgnoreCase("\'s") ||
                    (Character.isDigit(partSentence.charAt(idx - 1)) && Character.isDigit(lastWord.charAt(0)))) {
                output.set(output.size() - 1, token + lastWord);
                newToken = false;
            }
        }
        if (newToken) {
            output.add(token);
        }
        idx -= candidateIndexValue.intValue();
    }
    return String.join(" ", Lists.reverse(output));
}


private Pair<Number, Number> bestMatch(String partSentence, List<Pair<Number, Number>> cost, int index,
                      Map<String, Number> wordCost, int maxWordLength) {
    List<Pair<Number, Number>> candidates = Lists.reverse(cost.subList(Math.max(0, index - maxWordLength), index));
    int enumerateIdx = 0;
    Pair<Number, Number> minPair = new Pair<>(Integer.MAX_VALUE, Integer.valueOf(enumerateIdx));
    for (Pair<Number, Number> pair : candidates) {
        ++enumerateIdx;
        String subsequence = partSentence.substring(index - enumerateIdx, index).toLowerCase();
        Number minCost = Integer.MAX_VALUE;
        if (wordCost.containsKey(subsequence)) {
            minCost = pair.getKey().doubleValue() + wordCost.get(subsequence).doubleValue();
        }
        if (minCost.doubleValue() < minPair.getKey().doubleValue()) {
            minPair = new Pair<>(minCost.doubleValue(), enumerateIdx);
        }
    }
    return minPair;
}

如果我们没有单词列表怎么办? - shirazy
如果我理解查询正确:因此,在上述方法中,“public”方法接受一个基于正则表达式的第一级拆分的“String”类型的句子。对于“ninja_words”的列表,可以从git存储库中下载。 - Arnab Das

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接