展平Scala中的元组列表?

Posted

技术标签:

【中文标题】展平Scala中的元组列表?【英文标题】:Flatten a list of tuples in Scala? 【发布时间】:2016-09-06 10:43:25 【问题描述】:

我原以为元组列表可以很容易地展平:

scala> val p = "abcde".toList
p: List[Char] = List(a, b, c, d, e)

scala> val q = "pqrst".toList
q: List[Char] = List(p, q, r, s, t)

scala> val pq = p zip q
pq: List[(Char, Char)] = List((a,p), (b,q), (c,r), (d,s), (e,t))

scala> pq.flatten

但是,这种情况发生了:

<console>:15: error: No implicit view available from (Char, Char) => scala.collection.GenTraversableOnce[B].
       pq.flatten
          ^

我可以完成工作:

scala> (for (x <- pq) yield List(x._1, x._2)).flatten
res1: List[Char] = List(a, p, b, q, c, r, d, s, e, t)

但我不理解错误消息。而且我的替代解决方案似乎很复杂且效率低下。

该错误消息是什么意思,为什么我不能简单地展平元组列表?

【问题讨论】:

【参考方案1】:

我们最近需要这样做。在说明我们的解决方案之前,请允许我简要解释一下用例。

用例

给定一个项目池(我将其称为类型T),我们希望针对池中的所有其他项目对每个项目进行评估。这些比较的结果是失败的评估Set,我们将其表示为所述评估中左侧项目和右侧项目的元组:(T, T)

这些评估完成后,我们可以将Set[(T, T)] 扁平化为另一个Set[T],以突出显示所有未通过任何比较的项目。

解决方案

我们对此的解决方案是折叠:

val flattenedSet =
    set.foldLeft(Set[T]())
                 case (acc, (x, y)) => acc + x + y 

这以一个空集(foldLeft 的初始参数)作为累加器开始。

然后,对于这里消费的Set[(T, T)](命名为set)中的每个元素,传递fold函数:

    累加器的最后一个值 (acc),并且 该元素的(T, T) 元组,case 解构为xy

然后我们的 fold 函数返回 acc + x + y,它返回一个集合,其中包含除 xy 之外的累加器中的所有元素。该结果作为累加器传递给下一次迭代——因此,它累加每个元组内的所有值。

为什么不Lists?

我特别欣赏这个解决方案,因为它避免在进行展平时创建中间 Lists,而是在构建新的 Set[T] 时直接解构每个元组。

我们还可以更改我们的评估代码以返回 List[T]s,其中包含每个失败评估中的左右项目 - 然后 flatten 将 Just Work™。但我们认为元组更准确地代表了我们的评估目标——特别是一个项目与另一个项目,而不是可以想象代表任意数量项目的开放式类型。

【讨论】:

您能解释一下这是如何工作的吗?它是如何解决问题的? @RichardWеrеzaк 我在答案中添加了解释。【参考方案2】:

jwvh 的回答完美地涵盖了您的问题的“编码”解决方案,因此我不会对此进行详细介绍。我想补充的唯一一件事是澄清为什么需要您和 jwvh 找到的解决方案。

正如 Scala 库中所述,Tuple2(,) 转换为)是:

2 个元素的元组; Product2 的规范表示。

并跟进:

Product2 是 2 个分量的笛卡尔积。

...means 代表Tuple2[T1,T2]

所有可能的元素对的集合,其组件是两个集合的成员(分别在T1T2 中的所有元素)

另一方面,List[T] 表示 T 元素的有序集合。

这实际上意味着没有绝对的方法可以将任何可能的Tuple2[T1,T2] 转换为List[T],仅仅是因为T1T2 可能不同。例如,采用以下元组:

val tuple = ("hi", 5)

这样的元组怎么可能被展平?应该将5 设为String 吗?或者可能只是扁平化为List[Any]?虽然这两种解决方案都可以使用,但它们围绕类型系统工作,因此它们没有被设计成编码在Tuple API 中。

所有这一切都归结为这样一个事实,即这种情况没有默认的隐式视图,您必须自己提供一个,因为 jwvh 和您都已经弄清楚了。

【讨论】:

所以基本上@jwvh 提供的转换确保元组成员都具有相同的类型(T,T)。这将允许它们被展平(a,b)=> List(a,b)。很好的解释(: @mdm:从Tuple2[T1,T2]List[T] 的转换确实不明显,但是Tuple2[T,T] -> List[T] 另一方面相当简单。我不认为 scala 没有提供它的充分理由。如果我可以将List[Option[T]] 展平,我应该可以对List[(T,T)]List[(T,T,T)] 等做同样的事情。 @Dima,为什么,当完美的Seq(等)已经存在时?我认为用例是有限的(在某些情况下,这是完全错误的)。以(x, y) 的典型用法来存储坐标。 xy 仍然不是一回事,flatten 将是一个非常不合适的操作。元组不仅仅是一系列可能不同的类型,它们中的索引也非常重要,并且隐含地提供了一些忽略的操作,这不是一个好主意(当然,所有这些都非常“主要是意见”,但这在 cmets 中是可以的) . @dima, Option[T] 是一个 Monad,在这种情况下 flatten 可以有效地锻炼它。 Every Option[T] 是一个 Monad,其行为在类型内是一致的:任何 T 上的展平都将具有相同的行为。另一方面,Tuple[T1,T2] 不是 Monad,它没有提供一致的行为。事实上,对于 Tuple[T,T]List[T] 之间的交互,您可以将其展平,这很难证明语言级别的特殊情况,恕我直言。 @dima,另外,您可以将 Tuple[T,T] 设为特定的 monad 实例,但为什么语言设计者会选择将 List((5,7),(6,8)) 扁平化为 List(5,7,6,8) 而不是 List(5,6) ?或者List(7,8)?仅当您将 Tuple 用作 List 时,您描述的行为才是“明显的”,您不能指望类型系统理所当然地给予。【参考方案3】:

如果找不到隐式转换,您可以显式提供它。

pq.flatten case (a,b) => List(a,b)

如果在整个代码中多次这样做,那么您可以通过将其设为隐式来节省一些样板。

scala> import scala.language.implicitConversions
import scala.language.implicitConversions

scala> implicit def flatTup[T](t:(T,T)): List[T]= t match case (a,b)=>List(a,b)
flatTup: [T](t: (T, T))List[T]

scala> pq.flatten
res179: List[Char] = List(a, p, b, q, c, r, d, s, e, t)

【讨论】:

当源类型和目标类型都很常见时,请不要使用隐式转换。将其与例如自动元组,你会得到各种古怪的东西。有一个采用字符串列表的方法吗?突然foo("a", "b") 有效,但foo("a", "b", "c") 无效。一直在…… 点了。就其本质而言,隐含有点过于“诡异”,在这种随意的情况下可能应该避免使用。

以上是关于展平Scala中的元组列表?的主要内容,如果未能解决你的问题,请参考以下文章

python的元组和列表的区别

将 Scala 列表转换为元组?

如何在Scala中展平不同类型的列表?

元组和列表的区别

元组和列表的区别

元组和列表的区别