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 展平深度函数混淆的主要内容,如果未能解决你的问题,请参考以下文章

Python深度学习之路-3.1性能评价指标

Python深度学习之路-3.1性能评价指标

Python深度学习之路-3.1性能评价指标

Scala嵌套数组展平

混淆过的js代码如何还原?

Python使用pandas的crosstab函数计算混淆矩阵并使用Seaborn可视化混淆矩阵实战