代码翻译需要一点帮助(从Python到C#)

4

大家晚上好,

这个问题让我有点尴尬,因为我知道我应该自己能够得到答案。然而,我的Python知识仅仅比零略微多一点,所以我需要从比我更有经验的人那里寻求帮助...

以下代码来自于最近编辑的一本书中Norvig's "Natural Language Corpus Data"一章,它的目的是将句子“likethisone”转换为“[like, this, one]”(也就是正确分割单词)...

我已经将所有代码移植到C#中(实际上是自己重写了程序),但是函数segment让我非常困惑,甚至无法理解它的语法。请问是否有人能够帮我将其翻译成更易读的C#形式?

非常感谢您提前的帮助。

################ 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)

它必须尽可能动态,还是您可以接受一些静态类型?函数是否应该一对一翻译? - Hamish Grubijan
我已经用C#编写了其他所有函数。我只需要“segment”函数以期望的方式工作(无论它是什么)。非常感谢。 - Miguel
你能翻译一下 return max(candidates, key=Pwords) 吗?这段代码将每个对象使用 Pwords() 函数转换为数字,然后找到其中的最大值。现在尝试将生成器表达式中的 candidates = ([first]+segment(rem) for first,rem in splits(text)) 展开成一个 for 循环。实际上,你可以使用 yield return 技巧。 - Hamish Grubijan
1
Hamish:不需要使用for循环;生成器表达式与C#查询推导完全对应。 - Gabe
2个回答

2

让我们先解决第一个函数:

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) 

它接受一个单词并返回可能性最大的单词列表,因此其签名为static IEnumerable<string> segment(string text)。显然,如果text是空字符串,则结果应该为空列表。否则,它创建一个递归列表推导式,定义可能的候选词列表,并根据其概率返回最大值。
static IEnumerable<string> segment(string text)
{
    if (text == "") return new string[0]; // C# idiom for empty list of strings
    var candidates = from pair in splits(text)
                     select new[] {pair.Item1}.Concat(segment(pair.Item2));
    return candidates.OrderBy(Pwords).First();
}

当然,现在我们需要翻译 splits 函数。它的作用是返回一个包含所有可能的单词开头和结尾元组的列表。这个函数相对比较简单易懂:
static IEnumerable<Tuple<string, string>> splits(string text, int L = 20)
{
    return from i in Enumerable.Range(1, Math.Min(text.Length, L))
           select Tuple.Create(text.Substring(0, i), text.Substring(i));
}

下面是 Pwords,它只是在其输入列表中的每个单词上调用 Pw 的结果上调用 product 函数:
static double Pwords(IEnumerable<string> words)
{
    return product(from w in words select Pw(w));
}

而且,产品非常简单:

static double product(IEnumerable<double> nums)
{
    return nums.Aggregate((a, b) => a * b);
}

补充:

查看完整的源代码后,我们可以明显地发现Norvig打算使用记忆化技术来加速segment函数的结果。这是一个提供此加速的版本:

static Dictionary<string, IEnumerable<string>> segmentTable =
   new Dictionary<string, IEnumerable<string>>();

static IEnumerable<string> segment(string text)
{
    if (text == "") return new string[0]; // C# idiom for empty list of strings
    if (!segmentTable.ContainsKey(text))
    {
        var candidates = from pair in splits(text)
                         select new[] {pair.Item1}.Concat(segment(pair.Item2));
        segmentTable[text] = candidates.OrderBy(Pwords).First().ToList();
    }
    return segmentTable[text];
}

非常感谢!这是一段非常好的代码,Gabe。我通过这个学到了很多新的C#特性。 - Miguel

1

我完全不懂C#,但我可以解释Python代码的工作原理。

@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)

第一行,

@memo

是一个装饰器。这会导致在随后的代码行中定义的函数被包装在另一个函数中。装饰器通常用于过滤输入和输出。在这种情况下,根据它所包装的函数的名称和角色,我推断这个函数记忆化调用segment

接下来:

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))

外层括号与 for..in 结构相结合,创建了一个 生成器表达式。这是一种在序列上迭代的有效方式,在本例中为 splits(text)。生成器表达式类似于紧凑的 for 循环,可以产生值。在本例中,这些值成为迭代 candidates 的元素。"Genexps" 类似于 列表推导式,但通过不保留它们产生的每个值来实现更高的内存效率。

因此,对于 splits(text) 返回的迭代中的每个值,生成器表达式都会产生一个列表。

splits(text) 中的每个值都是一个 (first, rem) 对。

每个生成的列表都以对象first开头;这是通过将first放入列表字面量中来表示的,即[first]。然后向其添加另一个列表;第二个列表由对segment的递归调用确定。在Python中,添加列表会将它们连接起来,即[1, 2] + [3, 4]得到[1, 2, 3, 4]
最后,在
    return max(candidates, key=Pwords)

递归确定的列表 iteration 和一个关键函数被传递给 max。 在迭代中调用关键函数以获取用于确定该列表是否具有最高值的值。


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