我正在努力在Scala中实现我自己的Trie(用于学习/爱好目的),并且我试图保持它的通用性(这样它可以存储任何Iterable,而不仅仅是字符串)。我的班级签名看起来像
class Trie[Item <% Iterable[Part], Part](items: Item*) extends Set[Item]
我已经有了包含,+ =和 - =已实现(它正在扩展Set的可变版本),但我在迭代器方面遇到了一些麻烦。我目前的方法让我摸不着头脑,寻找一个优雅的实现。我有办法迭代所有的TrieNodes,只给出标记为“有效结尾”的TrieNodes。从那里我计划按照父链接获取各个部分。 (例如,树中的“hello”将被存储为标记为结尾的“o”节点,父级为“l” - &gt;“l” - &gt;“e” - &gt;“h”)
现在我的问题。由于我试图保持通用,我无法从其“部件”重建“项目”。所以我向SO的人提出的问题是处理这个问题的最优雅方式是什么?我应该在构造函数参数中添加重建函数吗? Item
应该以不同的方式强制执行函数的存在吗?或者它完全是另一回事?
答案 0 :(得分:1)
物品与零件之间存在隐含关系。至少你需要将一个Item分解为Part对象并重建你需要反过来。
因此,在"hello": String
时,您需要f("hello")
返回('h': Char, "ello": String)
,并且您需要使用反函数g('h', "ello")
返回"hello"
。
因此,只要遵循一些规则,任何具有两个这样的函数的两种类型都会起作用。我认为规则很容易直觉。您或多或少地使用head
和tail
递归分解列表并使用::
重建它
您可以使用上下文绑定来为常规类型提供这些函数。
(编辑)
实际上我不能真正使用上下文绑定,因为有两个类型参数,但这就是我的想法:
trait R[Item, Part] {
def decompose(item: Item): (Part, Item)
def recompose(item: Item, part: Part): Item
def empty: Item
}
class NotATrie[Item, Part](item: Item)(implicit rel: R[Item, Part]) {
val brokenUp = {
def f(i: Item, acc: List[Part] = Nil): List[Part] = {
if (i == rel.empty) acc
else {
val (head, tail) = rel.decompose(i)
f(tail, head :: acc)
}
}
f(item)
}
def rebuilt = (rel.empty /: brokenUp)( (acc, el) => rel.recompose(acc, el) )
}
然后我们提供一些隐含的对象:
implicit object string2R extends R[String, Char] {
def decompose(item: String): (Char, String) = (item.head, item.tail)
def recompose(item: String, part: Char): String = part + item
def empty: String = ""
}
implicit object string2RAlt extends R[String, Int] {
def decompose(item: String): (Int, String) = {
val cp = item.codePointAt(0)
val index = Character.charCount(cp)
(cp, item.substring(index))
}
def recompose(item: String, part: Int): String =
new String(Character.toChars(part)) + item
def empty: String = ""
}
val nat1 = new NotATrie[String, Char]("hello")
nat1.brokenUp // List(o, l, l, e, h)
nat1.rebuilt // hello
val nat2 = new NotATrie[String, Int]("hello\ud834\udd1e")
nat2.brokenUp // List(119070, 111, 108, 108, 101, 104)
nat2.rebuilt // hello