Scala deconstruct then reconstruct an Iterable
I'm currently working on implementing my own Trie in Scala (for learning/hobby purposes), and I'm trying to keep it generic (so that it can store anything Iterable, not just Strings). My class signature looks like
class Trie[Item <% Iterable[Part], Part](items: Item*) extends Set[Item]
I already have contains, += and -= implemented (it's extending the mutable version of Set), but I'm having some trouble with the iterator. My current approach has me scratching my head looking for a graceful implementation. I have a way to iterate over all of the TrieNodes, and give off only the ones that are marked as a "valid ending". From there I plan to follow parent links to get the individual parts. (e.g. "hello" in the tree would be stored as an 'o' node marked as an ending, with parent 'l'开发者_C百科 -> 'l' -> 'e' -> 'h')
Now my problem. Since I'm trying to keep things generic I have no way to reconstruct the "Item" from its "Parts." So my question to the people of SO is what would be the most graceful way to handle this? Should I add a reconstruction function to the constructor arguments? Should Item
be bounded differently to enforce the presence of the function? Or is it something else entirely?
There is an implied relationship between Item and Part. At the minimum you need to decompose an Item into Part objects and to reconstruct you need to do the reverse.
So taking "hello": String
, you need to have f("hello")
return ('h': Char, "ello": String)
and you need the inverse function g('h', "ello")
return "hello"
.
So any two types with two such functions will do as long as some rules are followed. I think the rules are easy to intuit. It's more or less how you decompose a list recursively using head
and tail
and rebuild it using ::
You could use a context bound to provide these functions for the usual type.
(edit)
Actually I can't really use a context bound because there are two type parameters, but this is what I had in mind:
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) )
}
Then we provide some implicit objects:
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
精彩评论