【问题标题】:Scala deconstruct then reconstruct an IterableScala解构然后重建一个Iterable
【发布时间】:2011-07-16 03:15:20
【问题描述】:

我目前正致力于在 Scala 中实现我自己的 Trie(用于学习/爱好目的),并且我试图保持它的通用性(以便它可以存储任何可迭代的内容,而不仅仅是字符串)。我的班级签名看起来像

class Trie[Item <% Iterable[Part], Part](items: Item*) extends Set[Item]

我已经实现了 contains、+= 和 -=(它扩展了 Set 的可变版本),但是我在使用迭代器时遇到了一些问题。我目前的方法让我挠头寻找一个优雅的实现。我有一种方法可以遍历所有的 TrieNode,并且只释放那些被标记为“有效结尾”的节点。从那里我计划按照父链接获取各个部分。 (例如,树中的“hello”将存储为标记为结尾的 'o' 节点,其父节点为 'l' -> 'l' -> 'e' -> 'h')

现在我的问题。由于我试图保持通用性,我无法从“部件”中重建“项目”。所以我向 SO 的人们提出的问题是,处理这个问题的最优雅的方式是什么?我应该在构造函数参数中添加重建函数吗? Item 是否应该有不同的界限以强制函数的存在?还是完全是另外一回事?

【问题讨论】:

    标签: generics scala trie


    【解决方案1】:

    Item 和 Part 之间存在隐含关系。至少你需要将一个 Item 分解为 Part 对象,而重构你需要做相反的事情。

    所以取"hello": String,你需要让f("hello")返回('h': Char, "ello": String),你需要反函数g('h', "ello")返回"hello"

    所以只要遵循一些规则,任何两种具有两个这样的功能的类型都可以。我认为规则很容易直觉。这或多或少是如何使用headtail 递归分解列表并使用:: 重建它

    您可以使用上下文绑定来为常用类型提供这些功能。

    (编辑)

    实际上我不能真正使用上下文绑定,因为有两个类型参数,但这是我的想法:

    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?
    

    【讨论】:

    • 所以边界看起来像Item &lt;% {def decons(i:Item) =&gt; (Part, Item)} with {def cons(p:Part, i:Item) =&gt; Item} .. 我知道这可能在语法上是错误的,但这是正确的想法吗?
    • 我已根据您指出的内容进行了更改,但带有隐式对象的部分似乎只能通过命令行/脚本界面工作。如果我尝试在顶层定义隐式构建器对象,编译器会抱怨,但如果我将它包装在顶层对象中,则找不到隐式转换。如果我想提供我的隐式构建器而不给用户带来任何额外的麻烦,我在哪里定义它们?
    • 将隐式对象放在object R { ... } 中,编译器应该在伴生对象中搜索隐式定义。
    猜你喜欢
    • 2017-01-14
    • 2013-06-04
    • 2010-11-06
    • 1970-01-01
    • 1970-01-01
    • 2021-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多