展平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
解构为x
和y
。
然后我们的 fold 函数返回 acc + x + y
,它返回一个集合,其中包含除 x
和 y
之外的累加器中的所有元素。该结果作为累加器传递给下一次迭代——因此,它累加每个元组内的所有值。
为什么不List
s?
我特别欣赏这个解决方案,因为它避免在进行展平时创建中间 List
s,而是在构建新的 Set[T]
时直接解构每个元组。
我们还可以更改我们的评估代码以返回 List[T]
s,其中包含每个失败评估中的左右项目 - 然后 flatten
将 Just Work™。但我们认为元组更准确地代表了我们的评估目标——特别是一个项目与另一个项目,而不是可以想象代表任意数量项目的开放式类型。
【讨论】:
您能解释一下这是如何工作的吗?它是如何解决问题的? @RichardWеrеzaк 我在答案中添加了解释。【参考方案2】:jwvh 的回答完美地涵盖了您的问题的“编码”解决方案,因此我不会对此进行详细介绍。我想补充的唯一一件事是澄清为什么需要您和 jwvh 找到的解决方案。
正如 Scala 库中所述,Tuple2
((,)
转换为)是:
2 个元素的元组;
Product2
的规范表示。
并跟进:
Product2
是 2 个分量的笛卡尔积。
...means 代表Tuple2[T1,T2]
:
所有可能的元素对的集合,其组件是两个集合的成员(分别在
T1
和T2
中的所有元素)。
另一方面,List[T]
表示 T
元素的有序集合。
这实际上意味着没有绝对的方法可以将任何可能的Tuple2[T1,T2]
转换为List[T]
,仅仅是因为T1
和T2
可能不同。例如,采用以下元组:
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)
的典型用法来存储坐标。 x
和 y
仍然不是一回事,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中的元组列表?的主要内容,如果未能解决你的问题,请参考以下文章