开发者

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜