在Scala中压缩任意嵌套集合的通用,类型安全方法?

时间:2012-08-28 13:44:03

标签: scala scala-collections

有时我会花一些时间和Scala一起玩,尽管我在自己的工作中无法使用Scala,但它的功能组合对我很有吸引力(到目前为止)。对于踢球,我决定以最通用的方式尝试前几个99 Haskell Problems - 操作并返回任何类型的适用集合。前几个问题并不太难,但我发现自己完全陷入flatten的困境。我只是无法弄清楚如何输入这样的东西。

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

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]。我是否正确,这将需要某种类型的递归?或者我这样做是否比它更难?

3 个答案:

答案 0 :(得分:13)

以下适用于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)

答案 1 :(得分:4)

似乎正确的做法就是调用.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一样。

答案 2 :(得分: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特征中。