Recursion Go中的递归

Recursion Go中的递归,recursion,go,Recursion,Go,我是一名Javascript开发人员,我决定试一试。作为一个学习练习,我决定在我的一个节点项目中移植一个函数,但却无法让它在我的一生中正常工作。该函数的目的是显示所有有效的英语单词,这些单词可以由不同单词中的字母组成(我正在构建一个多人版的)。例如,findAllWords(“dances”)将返回['can'、'scan'、'dance'、'dances'等]。我通过递归从英语单词列表构建的trie来实现这一点 以下是函数在Javascript中的实现: self.findAllWords =

我是一名Javascript开发人员,我决定试一试。作为一个学习练习,我决定在我的一个节点项目中移植一个函数,但却无法让它在我的一生中正常工作。该函数的目的是显示所有有效的英语单词,这些单词可以由不同单词中的字母组成(我正在构建一个多人版的)。例如,findAllWords(“dances”)将返回['can'、'scan'、'dance'、'dances'等]。我通过递归从英语单词列表构建的trie来实现这一点

以下是函数在Javascript中的实现:

self.findAllWords = function(letters = [], trie = dictionary, curString = '') {
    let words = [];
    letters = typeof letters === 'string' ? letters.split('') : letters;
    letters.forEach( (letter,i,ar) => {
        if (!trie[letter]) return;
        let curWord = curString + letter;
        let newTrie = trie[letter];
        let newLetters = [...ar.slice(0,i),...ar.slice(i+1)];
        if (trie[letter][FLAG_INDEX]) words.push(curWord);
        if (self.isValidPrefix(curWord, dictionary)) words = [...words,...self.findAllWords(newLetters,newTrie,curWord)];
    });
    return uniq(words);
}
下面是我在Go中复制它的尝试(使用trie实现):


我很想知道为什么会失败,我如何才能让它工作,以及我滥用/没有利用的任何惯例。谢谢

这可能是Go代码的唯一问题,也可能不是,但是for循环中的return语句与javascript forEach中的return语句做的事情不同

javascript代码中匿名函数内的返回从匿名函数返回到
findAllWords
函数内。从
FindAllWords
返回的Go-for循环中的Return。当遇到不在trie根中的字母时,这将过早地停止操作。我假定您遇到的问题是返回的
[]字符串是空的或不完整的


除了返回单词,您应该使用。

这可能是Go代码的唯一问题,也可能不是,但是for循环中的return语句与javascript forEach中的return语句做的事情不同

javascript代码中匿名函数内的返回从匿名函数返回到
findAllWords
函数内。从
FindAllWords
返回的Go-for循环中的Return。当遇到不在trie根中的字母时,这将过早地停止操作。我假定您遇到的问题是返回的
[]字符串是空的或不完整的


您应该使用
而不是
返回单词。

好的,OP的实现中有两个问题:

func FindAllWords(letters []rune, node *Node, curString string) []string {

words := []string{}
for i, let := range letters {
    n, ok := node.Children()[let]

    if !ok {
        return words
    }
    curWord := curString + string(n.val)
    newLetters := []rune{}
    newLetters = append(newLetters, letters[:i]...)
    newLetters = append(newLetters, letters[i+1:]...)

    if n.term {
        words = append(words, curWord)
    }

    words = append(words, FindAllWords(newLetters, n, curWord)...)
}
return words
}
  • if块检查子级是否存在的操作应该“继续”循环,而不是作为结果返回单词(尝试其他树分支)
  • 检查当前节点是否为终端(word)的if块条件必须更改,以适应github.com/derekparker/trie作者的设计决策,即将终端节点存储为对应于单词最后一个字母的节点的子节点,并将其父节点键入为0符文
以下是工作版本:

func FindAllWords(letters []rune, node *trie.Node, curString string) []string {
    words := []string{}
    for i, let := range letters {
        n, ok := node.Children()[let]
        if !ok {
            continue
        }
        curWord := curString + string(n.Val())
        newLetters := []rune{}
        newLetters = append(newLetters, letters[:i]...)
        newLetters = append(newLetters, letters[i+1:]...)
        if n.Children()[0x0] != nil {
            words = append(words, curWord)
        }
        words = append(words, FindAllWords(newLetters, n, curWord)...)
    }
    return words
}
这里有一个更具可读性的版本(当然是我的口味):

第二个问题当然来自于依赖一个外部库,该库带有一个有点泄漏的API,该API向客户机代码公开了实现细节

为了避免这种简单情况下的麻烦,我建议构建一个简单的trie实现,它可以像下面这样简单:

type Node struct {
    Char     rune
    Term     bool
    Children map[rune]*Node
}

func (n *Node) Add(s []rune) {
    if len(s) == 0 {
        n.Term = true
        return
    }
    r := s[0]
    c, ok := n.Children[r]
    if !ok {
        c = &Node{Char: r, Children: make(map[rune]*Node)}
        n.Children[r] = c
    }
    c.Add(s[1:])
}

func Empty() *Node {
    return &Node{Children: make(map[rune]*Node)}
}
使用这种结构,我加载了一个英文单词列表:

func English() *Node {
    f, err := os.Open("/usr/share/dict/american-english")
    if err != nil {
        panic(err)
    }
    defer f.Close()
    t := Empty()
    s := bufio.NewScanner(f)
    for s.Scan() {
        t.Add([]rune(strings.ToLower(s.Text())))
    }
    return t
}
该结构可以在相同的算法中使用,只需很少的修改,并且没有实现错误:

func FindAllWords3(s string, n *Node, w string) []string {
    r := []string{}
    for i, l := range s {
        n1, ok := n.Children[l]
        if !ok {
            continue
        }
        s1 := s[:i] + s[i+1:]
        w1 := w + string(l)
        if n1.Term {
            r = append(r, w1)
        }
        r = append(r, FindAllWords3(s1, n1, w1)...)
    }
    return r
}
以下是应用于单词“dances”和上面加载的相同英语单词列表的上述三种实现的结果:

[d dan dance dances dane danes dean deans den dena dens dec a ad aden ads an and andes ac acne ace aced aces as ascend n nd na ne ned c cd ca cad cads can cane caned canes cans case cased cs e ed edna end ends es s sad sade san sand sane sac sn snead sc scad scan se sedan sedna sea sean sen send sec]
[d dan dance dances dane danes dean deans den dena dens dec a ad aden ads an and andes ac acne ace aced aces as ascend n nd na ne ned c cd ca cad cads can cane caned canes cans case cased cs e ed edna end ends es s sad sade san sand sane sac sn snead sc scad scan se sedan sedna sea sean sen send sec]
[d dan dance dances dane danes dean deans den dena dens dec a ad aden ads an and andes ac acne ace aced aces as ascend n nd na ne ned c cd ca cad cads can cane caned canes cans case cased cs e ed edna end ends es s sad sade san sand sane sac sn snead sc scad scan se sedan sedna sea sean sen send sec]

好的,OP的实现中有两个问题:

func FindAllWords(letters []rune, node *Node, curString string) []string {

words := []string{}
for i, let := range letters {
    n, ok := node.Children()[let]

    if !ok {
        return words
    }
    curWord := curString + string(n.val)
    newLetters := []rune{}
    newLetters = append(newLetters, letters[:i]...)
    newLetters = append(newLetters, letters[i+1:]...)

    if n.term {
        words = append(words, curWord)
    }

    words = append(words, FindAllWords(newLetters, n, curWord)...)
}
return words
}
  • if块检查子级是否存在的操作应该“继续”循环,而不是作为结果返回单词(尝试其他树分支)
  • 检查当前节点是否为终端(word)的if块条件必须更改,以适应github.com/derekparker/trie作者的设计决策,即将终端节点存储为对应于单词最后一个字母的节点的子节点,并将其父节点键入为0符文
以下是工作版本:

func FindAllWords(letters []rune, node *trie.Node, curString string) []string {
    words := []string{}
    for i, let := range letters {
        n, ok := node.Children()[let]
        if !ok {
            continue
        }
        curWord := curString + string(n.Val())
        newLetters := []rune{}
        newLetters = append(newLetters, letters[:i]...)
        newLetters = append(newLetters, letters[i+1:]...)
        if n.Children()[0x0] != nil {
            words = append(words, curWord)
        }
        words = append(words, FindAllWords(newLetters, n, curWord)...)
    }
    return words
}
这里有一个更具可读性的版本(当然是我的口味):

第二个问题当然来自于依赖一个外部库,该库带有一个有点泄漏的API,该API向客户机代码公开了实现细节

为了避免这种简单情况下的麻烦,我建议构建一个简单的trie实现,它可以像下面这样简单:

type Node struct {
    Char     rune
    Term     bool
    Children map[rune]*Node
}

func (n *Node) Add(s []rune) {
    if len(s) == 0 {
        n.Term = true
        return
    }
    r := s[0]
    c, ok := n.Children[r]
    if !ok {
        c = &Node{Char: r, Children: make(map[rune]*Node)}
        n.Children[r] = c
    }
    c.Add(s[1:])
}

func Empty() *Node {
    return &Node{Children: make(map[rune]*Node)}
}
使用这种结构,我加载了一个英文单词列表:

func English() *Node {
    f, err := os.Open("/usr/share/dict/american-english")
    if err != nil {
        panic(err)
    }
    defer f.Close()
    t := Empty()
    s := bufio.NewScanner(f)
    for s.Scan() {
        t.Add([]rune(strings.ToLower(s.Text())))
    }
    return t
}
该结构可以在相同的算法中使用,只需很少的修改,并且没有实现错误:

func FindAllWords3(s string, n *Node, w string) []string {
    r := []string{}
    for i, l := range s {
        n1, ok := n.Children[l]
        if !ok {
            continue
        }
        s1 := s[:i] + s[i+1:]
        w1 := w + string(l)
        if n1.Term {
            r = append(r, w1)
        }
        r = append(r, FindAllWords3(s1, n1, w1)...)
    }
    return r
}
以下是应用于单词“dances”和上面加载的相同英语单词列表的上述三种实现的结果:

[d dan dance dances dane danes dean deans den dena dens dec a ad aden ads an and andes ac acne ace aced aces as ascend n nd na ne ned c cd ca cad cads can cane caned canes cans case cased cs e ed edna end ends es s sad sade san sand sane sac sn snead sc scad scan se sedan sedna sea sean sen send sec]
[d dan dance dances dane danes dean deans den dena dens dec a ad aden ads an and andes ac acne ace aced aces as ascend n nd na ne ned c cd ca cad cads can cane caned canes cans case cased cs e ed edna end ends es s sad sade san sand sane sac sn snead sc scad scan se sedan sedna sea sean sen send sec]
[d dan dance dances dane danes dean deans den dena dens dec a ad aden ads an and andes ac acne ace aced aces as ascend n nd na ne ned c cd ca cad cads can cane caned canes cans case cased cs e ed edna end ends es s sad sade san sand sane sac sn snead sc scad scan se sedan sedna sea sean sen send sec]

当您尝试使用该代码时会发生什么?它是否有错误或给出不正确的结果?错误是什么?我只是得到一个返回的空切片。正如jdd0所指出的,我非常确定第8行需要中断而不是返回,但这并不能解决问题。我认为您不想返回或中断,如果不正常,您希望
继续
(所以跳过这个例子,继续循环。相关:当您尝试使用该代码时会发生什么?它是否有错误或给出不正确的结果?错误是什么?我只是得到一个返回的空片段。我非常确定第8行需要中断,而不是返回,正如jdd0所指出的那样,但这并不能解决问题。我认为您不希望这样做。)t要返回或中断,如果不正常,您希望
继续
(因此跳过该案例并继续循环)。相关:您完全正确,但我仍然返回一个空片段。以下是一些输出(字母、n.val和curString),打印在函数开头,将返回更改为中断:[100 97 110 99 101 115][97 110 99 101 115]d[110 99 101 115]a da[99 101 115]n dan[101 115]c dance[115]e dance[115]e dance[110 101 115]c dance[110 101 115]c dac似乎n.term的评价永远不会正确,即使我能成功