繁体   English   中英

在解析Trie树时计数单词笔画

[英]Counting word strokes while parsing Trie tree

我正在尝试解决此处描述的键盘自动完成问题 问题是计算一个单词所需的击键次数,给出一些字典和自动完成规则。 例如,对于字典:

data = ['hello', 'hell', 'heaven', 'goodbye']

我们得到以下结果(请参阅上面的链接以获得进一步的解释):

{'hell': 2, 'heaven': 2, 'hello': 3, 'goodbye': 1}

快速说明:如果用户键入h ,则e自动完成,因为以h开头的所有单词也都有e作为第二个字母。 现在,如果用户键入l ,则填充另一个l ,为单词hell提供2个笔画。 当然, hello需要再多一次。 请参阅上面的链接以获取更多示例。

我的Trie代码如下,它工作正常(取自https://en.wikipedia.org/wiki/Trie )。 Stack代码是从root解析树(参见下面的编辑):

class Stack(object):
    def __init__(self, size):
        self.data = [None]*size
        self.i = 0
        self.size = size

    def pop(self):
        if self.i == 0:
            return None
        item = self.data[self.i - 1]
        self.i-= 1
        return item

    def push(self, item):
        if self.i >= self.size:
            return None
        self.data[self.i] = item
        self.i+= 1
        return item

    def __str__(self):
        s = '# Stack contents #\n'
        if self.i == 0:
            return
        for idx in range(self.i - 1, -1, -1):
            s+= str(self.data[idx]) + '\n'
        return s

class Trie(object):
    def __init__(self, value, children):
        self.value = value #char
        self.children = children #{key, trie}

class PrefixTree(object):
    def __init__(self, data):
        self.root = Trie(None, {})
        self.data = data

        for w in data:
            self.insert(w, w)

    def insert(self, string, value):
        node = self.root
        i = 0
        n = len(string)

        while i < n:
            if string[i] in node.children:
                node = node.children[string[i]]
                i = i + 1
            else:
                break

        while i < n:
            node.children[string[i]] = Trie(string[:i], {})
            node = node.children[string[i]]
            i = i + 1

        node.value = value


    def find(self, key):
        node = self.root
        for char in key:
            if char in node.children:
                node = node.children[char]
            else:
                return None
        return node

我无法弄清楚如何计算笔画数:

data = ['hello', 'hell', 'heaven', 'goodbye']
tree = PrefixTree(data)
strokes = {w:1 for w in tree.data} #at least 1 stroke is necessary

这是从根解析树的代码:

stack = Stack(100)
stack.push((None, pf.root))
print 'Key\tChilds\tValue'
print '--'*25

strokes = {}

while stack.i > 0:
    key, curr = stack.pop()

    # if something:
         #update strokes

    print '%s\t%s\t%s' % (key, len(curr.children), curr.value)
    for key, node in curr.children.items():
        stack.push((key, node))

print strokes

任何想法或建设性意见都会有所帮助,谢谢!

编辑

@SergiyKolesnikov的精彩回答。 可以做一个小的改动,以避免调用endsWith() 我刚刚给Trie类添加了一个布尔字段:

class Trie(object):
    def __init__(self, value, children, eow):
        self.value = value #char
        self.children = children #{key, trie}
        self.eow = eow # end of word

并在insert()的末尾:

def insert(self, string, value):
#...
    node.value = value
    node.eow = True

然后,只需更换curr.value.endswith('$'):curr.eow 谢谢你们!

你的例子的trie看起来像这样

 ' '
|    \
H     G
|     |
E     O
| \   |
L  A  O
|  |  |
L$ V  D
|  |  |
O  E  B
   |  |
   N  Y
      |
      E

可以将trie中的哪些节点视为用户击键的标记? 这种节点有两种类型:

  1. 具有多个子节点的内部节点,因为用户必须在多个备选项中进行选择。
  2. 表示单词的最后一个字母但不是叶子(用$标记)的节点,因为如果当前单词不是所需的,则用户必须键入下一个字母。

在递归地遍历trie时,计算在到达单词的最后一个字母之前遇到了多少这些标记节点。 此计数是单词所需的笔划数。

对于单词“hell”,它是两个标记节点: ' 'E (2个笔画)。
对于单词“hello”,它是三个标记节点: ' 'EL$ (3笔画)。
等等...

您的实施需要改变什么:

需要在树中标记有效单词的结尾,以便可以检查第二个条件。 因此,我们更改了PrefixTree.insert()方法的最后一行

node.value = value

node.value = value + '$'

现在我们为每个堆栈项添加一个笔划计数器(三次推送到堆栈中的最后一个值)以及增加计数器的检查:

stack = Stack(100)
stack.push((None, tree.root, 0)) # We start with stroke counter = 0
print('Key\tChilds\tValue')
print('--'*25)

strokes = {}

while stack.i > 0:
    key, curr, stroke_counter = stack.pop()

    if curr.value is not None and curr.value.endswith('$'):
        # The end of a valid word is reached. Save the word and the corresponding stroke counter.
        strokes[curr.value[:-1]] = stroke_counter

    if len(curr.children) > 1:
        # Condition 2 is true. Increase the stroke counter.
        stroke_counter += 1
    if curr.value is not None and curr.value.endswith('$') and len(curr.children) > 0:
        # Condition 1 is true. Increase the stroke counter.
        stroke_counter += 1

    print('%s\t%s\t%s' % (key, len(curr.children), curr.value))
    for key, node in curr.children.items():
        stack.push((key, node, stroke_counter)) # Save the stroke counter

print(strokes)

输出:

Key Childs  Value
--------------------------------------------------
None    2   None
h   1   
e   2   h
a   1   he
v   1   hea
e   1   heav
n   0   heaven$
l   1   he
l   1   hell$
o   0   hello$
g   1   
o   1   g
o   1   go
d   1   goo
b   1   good
y   1   goodb
e   0   goodbye$
{'heaven': 2, 'goodbye': 1, 'hell': 2, 'hello': 3}

在浏览堆栈时,应为每个节点保留一个笔画计数器:

  • 它从0开始,表示无。
  • 如果当前节点有超过2个子节点,则子节点的计数器将比当前计数器多1个。
  • 如果当前值是有效字并且至少有一个子节点,则子节点(ren)的计数器将比当前计数器多1。

出于文档目的,这是我的Ruby答案:

class Node
  attr_reader :key, :children
  attr_writer :final
  def initialize(key, children = [])
    @key = key
    @children = children
    @final = false
  end

  def final?
    @final
  end
end

class Trie
  attr_reader :root
  def initialize
    @root = Node.new('')
  end

  def add(word)
    node = root
    word.each_char.each{|c|
      next_node = node.children.find{|child| child.key == c}
      if next_node then
        node = next_node
      else
        next_node = Node.new(c)
        node.children.push(next_node)
        node = next_node
      end
    }
    node.final = true
  end

  def count_strokes(node=root,word="",i=0)
    word=word+node.key
    strokes = {}
    if node.final? then
      strokes[word]=i
      if node.children.size>0 then
        i+=1
      end
    elsif node.children.size>1 then
      i+=1
    end
    node.children.each{|c|
      strokes.merge!(count_strokes(c, word, i))
    }
    strokes
  end
end

data = ['hello', 'hell', 'heaven', 'goodbye']
trie =  Trie.new
data.each do |word|
  trie.add(word)
end

# File.readlines('/usr/share/dict/british-english').each{|line|
#   trie.add line.strip
# }

puts trie.count_strokes
#=> {"hell"=>2, "hello"=>3, "heaven"=>2, "goodbye"=>1}

仅60行,10万字不到3秒。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM