【问题标题】:Scala type that is Iterable and has a length?Scala 类型是 Iterable 并且有长度?
【发布时间】:2011-09-28 16:11:19
【问题描述】:

在编写 Scala 代码时,我经常遇到这样的情况,即我有“处理器”函数对元素集合进行迭代操作,并且还需要知道集合的长度。

另一方面,我有生成集合的“提供者”函数,因此已经知道长度。生成的集合可能是List[T]Array[T]Set[T] 等,但即使在List[T] 的情况下,我的生成器也知道大小(即使List 类型不存储它)。

所以我很自然地将“处理器”函数声明为采用似乎适合所有集合类型的最通用类型Iterable[T] 作为参数。然而,他们在内部需要以 O(N) 为代价通过迭代集合遍历来找出大小,这是不可取的。

所以我的天真的解决方案是创建一个像IterableWithSize[T] 这样的新类型,并让提供者和处理器函数创建并采用这种类型。 Seq[T]IndexedSeq[T] 似乎都不符合要求。但这似乎是一个相对常见的用例,所以我怀疑有一种更惯用的方法来做到这一点。那会是什么?

【问题讨论】:

  • 我不明白为什么IndexedSeq[T] 不适合你……
  • 由于您实际上想在集合中存储额外的数据,我认为您最好的天真方法是。
  • @Jean-Philippe Set[T] 不是 IndexedSeq[T]

标签: scala types size iterable


【解决方案1】:

在 Scala 集合中,像 size 这样的性能敏感方法不是从特征继承的,而是在底部类型中被覆盖的。比如看immutable.HashSet的实现:

https://lampsvn.epfl.ch/trac/scala/browser/scala/tags/R_2_9_0_1/src//library/scala/collection/immutable/HashSet.scala

所以你不需要关心它。只需定义一个高级通用特征,例如 TraversableIterable,就完成了。

【讨论】:

  • 嗯,不完全是:我已经知道底部类型在某些情况下会是 'List[T]',它不存储大小信息,导致 O(N) 操作(列表遍历)。因为出于问题的目的,我假设我总是有可用的大小,所以我确实需要一个特殊的类型来保存集合大小。
【解决方案2】:

实际上,没有惯用的方法。 Scala 集合实际上是为了以其他规定的方式遍历或使用(例如Set.containsMap.get)。检查大小不是其中的一部分,其中一些甚至不是有限的。

现在,IndexedSeq 是一个相对安全的选择——它保证了 O(logn) 索引访问,这只有在你有 O(logn) 大小的情况下才有可能。此外,出于类似的原因,SetMap 也相当安全。但是,如果您正在寻找能够保证size 速度的特征,那么没有。

【讨论】:

    【解决方案3】:

    我认为没有一种惯用的方法可以做到这一点。但这里有两种选择:

    (1) 扩展 Scala 的 List/Set/Array 集合并覆盖 size 方法。这并不像乍看起来那么困难。

    (2) 将您的 List/Set/Array 集合与大小一起包装并定义一个隐式展开器,例如:

    class IterableWithSizeWrapper[E](private val c: Iterable[E], val size: Int)
    object IterableWithSizeWrapper {
      implicit def unwrap[E](iws: IterableWithSizeWrapper[E]): Iterable[E] = iws.c
    }
    
    object ListWithSizeTest {
    
      def process[E](iws: IterableWithSizeWrapper[E]) {
            // iws.size uses your cached size value
            // iws.take(i) forces the unwrap to the original collect
            // so iws.take(i).size takes the calculated size
        for (i <- 0 to iws.size) assert(iws.take(i).size == i)
      }
    
      def main(args: Array[String]) {
        process(new IterableWithSizeWrapper(List(1,2,3), 3))
        process(new IterableWithSizeWrapper(Set(1,2,3), 3))
        process(new IterableWithSizeWrapper(Array(1,2,3), 3))
      }
    }
    

    【讨论】:

      【解决方案4】:

      Traversable 怎么样?你提到的所有集合都继承自它(Array 间接通过WrappedArray),它提供sizetoIterable(或toIterator)用于遍历。

      【讨论】:

      • 我也考虑过 Traversable。但是(a)它更原始且不可迭代(我只能对所有元素执行 foreach(),因此我不能对某些前缀进行操作)和 (b) 它也没有大小信息 已存储 ,所以我最终还是会以 O(N) 的成本进行集合遍历以确定大小。不过谢谢你的建议!
      • No Traversable 不是可迭代的,但它具有提供它的上述方法。通常,特征/超类无法真正指定 getter 方法的实现方式,因此返回的大小是否被存储或计算,或者返回的 Iterable 是否不是完整副本而不是瘦包装器。甚至它的派生 List 和 Set 也不能​​保证这一点。那些你必须在接口契约中建立自己的东西,并确保你只使用遵守它的子类。
      【解决方案5】:

      您的处理器函数应该接受Seq[T]Seq 恰好是“有长度”的 Iterable。您唯一剩下的问题是使length 高效。 AFAIK 除了List 之外,它在所有情况下都已经很有效了。要使 List.length 高效,只需按照其他人的描述进行操作:创建 Seq 的实现,它包装 List 并存储其长度。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-08-16
        • 1970-01-01
        • 2010-11-06
        • 1970-01-01
        • 2020-08-11
        • 2021-09-13
        • 2020-12-14
        • 2020-06-17
        相关资源
        最近更新 更多