Go中的递归

I'm a Javascript developer by trade and decided to give Go a spin. As a learning exercise I decided to port a function in one of my node projects, but can't get it working for the life of me. The function's purpose is to display all of the valid english words that can be made from the letters present in a different word (I'm building a multiplayer version of Text Twist). For example, findAllWords("dances") would return ['can','scan','dance','dances',etc...]. I've achieved this by recursing on a trie built from an English word list.

Here is the function's implementation in 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);
}

and here's my attempt at replicating it in Go (using this trie implementation):

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
}

Would love to know why this is failing, how I can get it working, and any conventions I'm abusing/not making use of. Thanks!

This may or may not be the only problem with the Go code, but the return statement in the for loop is not doing the same thing as the return statement in the javascript forEach.

Return within the anonymous function in the javascript code returns from the anonymous function to within the findAllWords function. Return within the Go for loop returns from FindAllWords. This will prematurely stop the operation when it comes across a letter not within the root of the trie. I presume the issue you are having is the []string being returned is empty or incomplete.

Instead of return words, you should use break.

OK, there are two problems in OP's implementation:

  • The action of the if block checking for child existence should "continue" the loop instead of returning words as a result (to try other tree branches).
  • The if block condition checking whether the current node is a terminal (word) must be changed to accomodate github.com/derekparker/trie author's design decision of storing the terminal node as a child of the node corresponding to the last letter of the word, keyed in its parent as the 0 rune.

Here's the working version:

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
}

Here's a somewhat more readable version (to my taste, of course):

func FindAllWords2(s string, n *trie.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.Children()[0x0] != nil {
            r = append(r, w1)
        }
        r = append(r, FindAllWords2(s1, n1, w1)...)
    }
    return r
}

The second problem is of course coming from depending on an external library with a somewhat leaky API which exposes implementation details to the client code.

To avoid such kind of trouble for this simple case, I would recommend to build a simple trie implementation which can come as easy as this:

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

Using that structure this is how I loaded an english wordlist:

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
}

That structure can be used in the same algorithm with very little modification and no implementation misteries:

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
}

Here are the results of the three implementations above applied to the word "dances" and the same english wordlist loaded above:

[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]