Scala 展平深度函数混淆
Posted
技术标签:
【中文标题】Scala 展平深度函数混淆【英文标题】:Scala flatten depth function confusion 【发布时间】:2021-09-08 22:51:39 【问题描述】:我对有关此功能的文档/提供的使用示例感到有些困惑。 flatten
只能发生一次吗?喜欢
List(List(1, 2), List(3, List(4, 5, 6))) -> List(1, 2, 3, List(4, 5, 6))
或者您以某种方式指定展平的深度,使其变为List(1, 2, 3, 4, 5, 6)
?
因为,例如,在 JS 中,函数似乎可以flat()
进入一维数组的任何深度。 Scala flatten
可以做到这一点还是只能提升一次?
我正在尝试自己重新创建该功能,并希望模仿所需的行为并了解其工作方式可能不同的原因。
【问题讨论】:
List#flatten
只会展平 1 个级别。您可能可以自己尝试;)如果您想深度扁平化,您可能必须构建一个递归方法。
请注意,您的 List(List(1, 2), List(3, List(4, 5, 6)))
是可疑的,因为它可能被定义为 List[Any]
,这在 Scala 中是一种耻辱。
@GaëlJ,由于无法定义函数的类型,因此即使是递归方法也不起作用,您可以尝试一下。您可以通过类检查和不安全的访问来做一些非常讨厌的事情,但这并不值得,当您需要动态深度展平时,这意味着您在其他地方遇到了设计问题。
像这样的嵌套列表,其中嵌套级别可能会有所不同,是树形的。它们基本上是树,而不是列表。使用实际的树类型而不是使用List
。
相关***.com/questions/12160596/…
【参考方案1】:
正如在 Scala 2 中定义这样一个方法的 cmets 中提到的,以类型安全的方式执行起来并不简单。首先,Scala 2 不直接支持递归类型,因此您必须对 List[Any]
进行操作并使用运行时反射来区分元素是列表还是整数。
最近发布的Scala 3在其类型系统上有很多改进,所以我想知道是否可以在那里实现这样的方法?我试过了,我认为我能够实现可用的实现。
首先,我想知道是否可以实现递归联合类型(在打字稿中也可以实现类似的事情):
type ListOr[A] = A | List[ListOr[A]]
不幸的是,这种类型会引发编译器错误:
非法循环类型引用:ListOr 类型的别名 ... 引用回类型本身
这很令人失望,but after some digging,我发现我可以将这样的递归类型定义为:
type ListOr[A] = A match
case AnyVal => AnyVal | List[ListOr[AnyVal]]
case _ => A | List[ListOr[A]]
它是可用的:
val ints: ListOr[Int] = List(List(1), 2, 3, List(List(4, List(5)), 6), 7, 8, List(9))
val strings: ListOr[String] = List(List("A", "B", "C"), List(List("D", List("E")), "F"), "G", "H", List("I"), "J")
所以现在我只需要实现展平功能:
//I needed class tag for A to be able to do a match
def deepFlatten[A: ClassTag](s: ListOr[A]): List[A] =
s match
case a: A => List(a)
case ls: List[_ <: ListOr[A]] => ls.flatMap(deepFlatten(_))
它似乎工作正常:
@main
def main =
val i: List[Int] = deepFlatten[Int](ints) //List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val j: List[String] = deepFlatten[String](strings)//List(A, B, C, D, E, F, G, H, I, J)
显然,这种实现可以改进(它不是尾递归),但它正在发挥作用。
由于我是 Scala 3 新手,我不确定这是否是最好的实现,但绝对可以实现任意深度的扁平化函数作为类型安全的。
Scastie with the solution.
【讨论】:
活着的好时光:) Scala 应该可以处理这个illegal cyclic type reference: alias
@John 它不能工作,类型别名就是一个别名,它不能递归,因为它是无限的。你可以只创建一个普通的类,而不是递归的。
它在打字稿中工作。那里仍然是无限递归,但它认为它在那里被懒惰地处理,所以它不会导致循环引用错误。【参考方案2】:
这是尝试使用简单的Tree
数据结构做类似的事情。
final case class Tree[+A](value: A, children: List[Tree[A]] = Nil)
def flattenTree[A](tree: Tree[A]): List[A] =
@annotation.tailrec
def loop(remainingNodes: List[Tree[A]], acc: List[A]): List[A] =
remainingNodes match
case Tree(value, children) :: tail =>
loop(
remainingNodes = children reverse_::: tail,
value :: acc
)
case Nil =>
acc
loop(remainingNodes = tree :: Nil, acc = List.empty)
可以这样使用:
val tree = Tree(
value = 1,
children = List(
Tree(value = 2),
Tree(
value = 3,
children = List(
Tree(value = 4),
Tree(value = 5),
Tree(value = 6)
)
)
)
)
val flattenedTree = flattenTree(tree)
println(flattenedTree.mkString("[", ", ", "]"))
这将产生以下输出:
[2, 4, 5, 6, 3, 1]
如您所见,这是一个反向的 DFS(其结果也被反向)。如果顺序无关紧要,这是一种简单而有效的实现,如果顺序很重要,则可以使用代码。
另一种方法是使用如下数据结构:
sealed trait ListOr[+A] extends Product with Serializable
final case class NestedList[+A](data: List[ListOr[A]]) extends ListOr[A]
final case class SingleValue[+A](value: A) extends ListOr[A]
可以看到运行here的代码。
【讨论】:
以上是关于Scala 展平深度函数混淆的主要内容,如果未能解决你的问题,请参考以下文章