【问题标题】:Generic, type-safe way to flatten arbitrarily nested collections in Scala?在 Scala 中展平任意嵌套集合的通用、类型安全的方法?
【发布时间】:2012-08-23 00:57:27
【问题描述】:

有时我会花一些时间来玩 Scala,尽管我无法在自己的工作中使用它(到目前为止),但它的混合功能对我很有吸引力。对于踢球,我决定以最通用的方式尝试前几个 99 Haskell Problems — 操作并返回任何类型的适用集合。前几个问题并不太难,但我发现自己被flatten 完全阻碍了。我就是不知道怎么打这样的东西。

具体来说我的问题:是否可以编写一个类型安全的函数来展平任意嵌套的SeqLikes?比如说,

flatten(List(Array(List(1, 2, 3), List(4, 5, 6)), Array(List(7, 8, 9), List(10, 11, 12))))

会回来

List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12): List[Int]

?请注意,这与 Haskell 和 Scala 问题集中的问题并不完全相同。我正在尝试编写一个函数,该函数不是扁平化异构列表,而是扁平化同质但嵌套的序列。

在网上搜索时,我找到了该问题的 translation into Scala,但它运行并返回 List[Any]。我是否正确,这需要某种类型的递归?还是我把它弄得更难了?

【问题讨论】:

  • 请将您的 reverse 问题作为单独的 SO 问题,因为它与主要问题无关
  • 差不多就可以了,只是我无法将它变成通用的 WRT 序列类型。

标签: scala scala-collections


【解决方案1】:

以下适用于 Scala 2.10.0-M7。您将需要为Array 支持添加额外的案例,并可能对其进行改进以具有更具体的输出集合类型,但我想这一切都可以从这里开始:

sealed trait InnerMost {
  implicit def innerSeq[A]: CanFlatten[Seq[A]] { type Elem = A } =
    new CanFlatten[Seq[A]] {
      type Elem = A
      def flatten(seq: Seq[A]): Seq[A] = seq
    }
}
object CanFlatten extends InnerMost {
  implicit def nestedSeq[A](implicit inner: CanFlatten[A]) 
  : CanFlatten[Seq[A]] { type Elem = inner.Elem } =
    new CanFlatten[Seq[A]] {
      type Elem = inner.Elem
      def flatten(seq: Seq[A]): Seq[inner.Elem] =
        seq.flatMap(a => inner.flatten(a))
    }
}
sealed trait CanFlatten[-A] {
  type Elem
  def flatten(seq: A): Seq[Elem]
}

implicit final class FlattenOp[A](val seq: A)(implicit val can: CanFlatten[A]) {
  def flattenAll: Seq[can.Elem] = can.flatten(seq)
}

// test        
assert(List(1, 2, 3).flattenAll == Seq(1, 2, 3))
assert(List(Seq(List(1, 2, 3), List(4, 5, 6)), Seq(List(7, 8, 9),
                List(10, 11, 12))).flattenAll == (1 to 12).toSeq)

【讨论】:

  • 非常好,谢谢!我会将其标记为已回答,尽管我可能需要一些时间来完成它。老实说,如果我一开始就知道有那么多代码,在我问之前我就不会这么努力了。 ;)
  • CanFlatten 仅使用两个类型参数可能会更容易。它涉及类型成员,因为否则隐式不会完全自行解决。我认为如果你不需要nestedSeq.flattenAllflattenAll(nestedSeq) 就足够了(即没有“拉皮条”),它会变得更容易,更像是链接的问题(“这是如何递归的......”)。
【解决方案2】:

似乎正确的做法是调用.flatten 正确的次数:

scala> val x = List(Array(List(1, 2, 3), List(4, 5, 6)), Array(List(7, 8, 9), List(10, 11, 12)))
x: List[Array[List[Int]]] = List(Array(List(1, 2, 3), List(4, 5, 6)), Array(List(7, 8, 9), List(10, 11, 12)))

scala> x.flatten.flatten
res0: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

由于 Scala 是类型化的,因​​此您总是可以提前知道特定变量的嵌套深度。由于您提前知道这一点,因此处理任意结构没有太大价值,就好像您不确定需要调用多少次 .flatten

【讨论】:

  • 这是一个公平的回应,尽管我仍然很好奇这样的函数是否可以编写。 :)
【解决方案3】:

您面临着他们在 Haskell solution 中描述的相同问题:Scala 中没有异构的 List。幸运的是,您可以遵循他们在 Haskell 解决方案中所采用的完全相同的路径。

定义一些可以嵌套的数据类型:

sealed trait NestedList[A]
case class Elem[A](a: A) extends NestedList[A]
case class AList[A](a: List[NestedList[A]]) extends NestedList[A]

然后为该类型编写一个通用的 flatten 函数:

def flatten[A](l: NestedList[A]): List[A] = l match {
  case Elem(x) => List(x)
  case AList(x :: xs) => flatten(x) ::: flatten(AList(xs))
  case AList(Nil) => Nil
}

甚至

def flatten[A](l: NestedList[A]): List[A] = l match {
  case Elem(x) => List(x)
  case AList(x) => x.flatMap(flatten)
}

用法:

flatten(AList(Elem(1) :: Elem(2) :: AList(Elem(3) :: Nil) :: Nil))

当然,我们也可以将其作为方法直接添加到 NestedList 特征中。

【讨论】:

  • 在问我的问题时,我可能并不完全清楚或准确,因为这不是我的想法。我并不是要编写一个在异构列表上运行的展平函数,而是一个展平函数,它在我的示例中一般在同构但嵌套的 Seqs 上运行。我很好奇(1)是否有可能,(2)如果有的话怎么写。不过,谢谢。
  • 啊,好吧。然后haskell的例子有点误导我。
  • 不适用于将 Int 与 String 混合的列表
猜你喜欢
  • 2011-11-17
  • 2016-04-30
  • 2020-04-15
  • 1970-01-01
  • 1970-01-01
  • 2018-10-28
  • 1970-01-01
  • 1970-01-01
  • 2012-06-05
相关资源
最近更新 更多