我想为整数索引序列实现trie。为了提高效率,节点的子节点通过索引序列而不是某种映射与节点相关联是很重要的。为了说明基本思想,这里是Ruby中的一个实现:
%
Perl中的情况与此相同:
require 'virtus'
class TrieNode
include Virtus.model
attribute :terminal, Boolean, default: false
attribute :children, Array, default: []
def add(i, vec)
if i == vec.length
terminal = true
else
( children[vec[i]] ||= TrieNode.new ).add i + 1, vec
end
end
end
以下是我在Rust中实现此功能的各种尝试之一:
package TrieNode;
use Moo;
has terminal => ( is => 'rw' );
has children => ( is => 'ro', default => sub { [] } );
sub add {
my ( $self, $i, $vec ) = @_;
if ( $i == @$vec ) {
$self->terminal(1);
}
else {
( $self->children->[ $vec->[$i] ] //= TrieNode->new )->add( ++$i, $vec );
}
}
'wheee!!!';
对于它的价值,以下是该尝试的编译错误:
struct TrieNode {
children: Vec<Option<TrieNode>>,
terminal: bool
}
impl TrieNode {
fn new() -> TrieNode { TrieNode { children: vec![], terminal: false } }
fn add(&mut self, i: usize, s: &Vec<usize>) {
if s.len() == i {
self.terminal = true;
} else {
let j = s[i];
let k = j - 1;
let ref mut c = self.children;
while c.len() < k {
c.push(None);
}
if c.len() < j {
let mut n = TrieNode::new();
n.add( i + 1, s );
c.push(Some(n));
} else {
let ref o = c[j];
match o {
Some(ref mut n) => {
n.add( i + 1, s );
},
None => {
let mut n = TrieNode::new();
n.add( i + 1, s );
c.remove(j);
c.insert(j, Some(n));
}
}
}
}
}
}
这是对this的Rust实现的预期用途。一旦完全塞满序列,trie将是不可变的,并将在程序的整个生命周期中存活。我这样做是为了学习Rust,所以我的愚蠢越多越好。谢谢!
答案 0 :(得分:4)
您的控制流程有点复杂,并且存在大量嵌套。通过减少嵌套和展开控制流程,我们可以获得更加可口的东西。我也希望避免所有的冗余。
#[derive(Clone, Debug, Default)]
struct TrieNode {
children: Vec<Option<TrieNode>>,
terminal: bool
}
impl TrieNode {
pub fn add(&mut self, element: &[usize]) {
if element.len() == 0 {
self.terminal = true;
return;
}
let ref mut c = self.children;
let value = element[0];
// Ensure there is at least "value" children
if c.len() < value { c.resize(value, None); }
// Ensure the "value"th child is a full TrieNode
if c[value - 1].is_none() {
c[value - 1] = Some(TrieNode::default());
}
c[value - 1].as_mut().unwrap().add(&element[1..]);
}
}
fn main() {
let mut t = TrieNode::default();
t.add(&[1, 2]);
println!("{:?}", t);
}
讨厌的位是add
的后6行。 match
在概念上更优雅,但是您无法在c[value - 1]
期间借用match
并在None
分支中修改它,因为借用检查不是“路径感知” “但只是”范围意识“(这种限制可能会在将来解除,但我们现在必须与之抗衡)。
建议:
Vec::resize
而不是while
循环可以更准确地传达意图。new()
没有参数时,最好实现Default
特征(打开更多门);另外,Default
可以定期派生,这样你甚至不必输入它!String
/ Vec
的所有权,请使用切片。除了使代码更通用(任何可以解析切片的类型都可以工作)之外,这里我们还可以从索引符号[1..]
中受益,以便廉价地切断第一个元素,从而避免携带额外的索引。 所有这些都使代码更短,代码越少通常意味着错误的可能性就越小。特别是在避免重复时。
另一方面,如果您愿意为孩子使用HashMap
或BTreeMap
,您可以免费获得“稀疏性”,并且可以更轻松地添加。例如,使用add
修改了use std::collections::HashMap
方法:
impl TrieNode {
pub fn add(&mut self, element: &[usize]) {
if element.len() == 0 {
self.terminal = true;
return;
}
self.children
.entry(element[0])
.or_insert(TrieNode::default())
.add(&element[1..]);
}
}
很多,很多,更简单,不是吗?
答案 1 :(得分:1)
错误表明o
是引用(&
),但您与Option
值(不是引用)匹配。
如果您添加引用(&Some(ref mut n)
),则会收到另一个错误,因为您正在对n
进行可变引用,但o
是不可变的。
如果您o
可变(let o = &mut c[j]
),则无法更改c
分支中的None
,因为o
有c
的可变借位}}
一种解决方案是取得c[j]
的所有权(这会很烦人
如果c
值不是Option
),请将其返回Some
分支。
impl TrieNode {
fn new() -> TrieNode { TrieNode { children: vec![], terminal: false } }
fn add(&mut self, i: usize, s: &Vec<usize>) {
if s.len() == i {
self.terminal = true;
} else {
let j = s[i];
let k = j - 1;
let ref mut c = self.children;
while c.len() < k {
c.push(None);
}
if c.len() < j {
let mut n = TrieNode::new();
n.add( i + 1, s );
c.push(Some(n));
} else {
match c[j].take() {
Some(mut n) => {
n.add( i + 1, s );
c[j] = Some(n)
},
None => {
let mut n = TrieNode::new();
n.add( i + 1, s );
c[j] = Some(n);
}
}
}
}
}
}