Scala中的广义笛卡尔积函数

时间:2018-10-12 19:48:55

标签: scala

我编写了以下笛卡尔积函数(在各种类型的元素上可以使用任意数量的Iterable的{​​em>任意个数):

def cross(a: Iterable[_]*): Iterable[_] =
   if (a.length == 1)
     for (i <- a.head) yield i
   else
     for (i <- a.head; j <- cross3(a.tail:_*))
       yield i :: j :: Nil

哪个产生,例如:

List(List(1, List(a, -1)), List(1, List(a, 0)) ...

但是我想要类似的东西:

List(List(1, a, -1), List(1, a, 0) ...

甚至更好:

List((1, a, -1), (1, a, 0) ...

我如何“展平”结果元组?我尝试了几件事,但推断j的类型为Any的类型推断似乎迷失了。

1 个答案:

答案 0 :(得分:2)

我认为您想要这样的东西(使用 Shapeless ):

import shapeless._

object CartesianProduct
extends App {

  def cross(a: Iterable[Iterable[_]]): Iterable[HList] = {

    // If a is empty, return HNil to signal the end of this heterogenous list.
    if(a.isEmpty) Iterable(HNil)

    // Otherwise, create a new heterogeneous list for each element in this list,
    // prefixed to each heterogeneous list for the remainder.
    else for {

      i <- a.head // For each element in the head sequence
      t <- cross(a.tail) // For each heterogenous list in the output sequence
    } yield i :: t // Create a new heterogeneous list
  }

  val data = List(List(1, 2, 3), List("a", "b"), List(-1, -2, -3))
  val result = cross(data)
  println(result)
}

结果是:

List(1 :: a :: -1 :: HNil, 1 :: a :: -2 :: HNil, 1 :: a :: -3 :: HNil, 1 :: b :: -1 :: HNil, 1 :: b :: -2 :: HNil, 1 :: b :: -3 :: HNil, 2 :: a :: -1 :: HNil, 2 :: a :: -2 :: HNil, 2 :: a :: -3 :: HNil, 2 :: b :: -1 :: HNil, 2 :: b :: -2 :: HNil, 2 :: b :: -3 :: HNil, 3 :: a :: -1 :: HNil, 3 :: a :: -2 :: HNil, 3 :: a :: -3 :: HNil, 3 :: b :: -1 :: HNil, 3 :: b :: -2 :: HNil, 3 :: b :: -3 :: HNil)

更新:是否可以不使用 Shapeless 而做到这一点?

如何?

object CartesianProduct
extends App {

  def cross(a: Iterable[Iterable[_]]): Iterable[List[_]] = {

    // If a is empty, return Nil to signal the end of this list of Anys.
    if(a.isEmpty) Iterable(Nil)

    // Otherwise, create a new list of Anys for each element in this iterable,
    // prefixed to each list of Anys for the remainder.
    else for {

      i <- a.head // For each element in the head sequence
      t <- cross(a.tail) // For each list of Anys in the output sequence
    } yield i :: t // Create a new list of Anys
  }

  val data = List(List(1, 2, 3), List("a", "b"), List(-1, -2, -3))
  val result = cross(data)
  println(result)
}

它实际上输出List[List[Any]]

List(List(1, a, -1), List(1, a, -2), List(1, a, -3), List(1, b, -1), List(1, b, -2), List(1, b, -3), List(2, a, -1), List(2, a, -2), List(2, a, -3), List(2, b, -1), List(2, b, -2), List(2, b, -3), List(3, a, -1), List(3, a, -2), List(3, a, -3), List(3, b, -1), List(3, b, -2), List(3, b, -3))

(类型推断丢失的原因是,首先,您的函数签名中没有任何泛型类型,因此,_泛型类型等同于说您的可迭代对象包含Any值其次,您要将这些Any值添加到List中,因此列表包含异构元素(在这种情况下为IntString)。结果表明您将不得不对类型进行类型转换或模式匹配。要确定是否有更好的方法,我必须询问您将此函数用于什么?)

在一般情况下,我认为返回元组列表是不可能的(因为您需要确定输入中有多少可迭代量才能创建具有这么多值的元组)。

更新2 :如果要以元组的形式输出,则显然需要知道传递给该函数的每个可迭代对象的数量和类型。 Shapeless 版本如下所示:

// Convert result to a tuple.
val generic = Generic[Tuple3[Int, String, Int]]
val tupleResult = result.map {l =>
  val t = l.asInstanceOf[Int :: String :: Int :: HNil]
  generic.from(t)
}
println(tupleResult)

和非 Shapeless 版本看起来像这样:

val tupleResult = result.map {l =>
  (
    l.head.asInstanceOf[Int],
    l.tail.head.asInstanceOf[String],
    l.tail.tail.head.asInstanceOf[Int]
  )
}
println(tupleResult)

在两种情况下,输出均为:

List((1,a,-1), (1,a,-2), (1,a,-3), (1,b,-1), (1,b,-2), (1,b,-3), (2,a,-1), (2,a,-2), (2,a,-3), (2,b,-1), (2,b,-2), (2,b,-3), (3,a,-1), (3,a,-2), (3,a,-3), (3,b,-1), (3,b,-2), (3,b,-3))