【问题标题】:Seeking a scala-esque approach to iterate through a list with access to the "next" element寻求一种 scala-esque 方法来遍历一个可以访问“下一个”元素的列表
【发布时间】:2011-06-21 05:04:57
【问题描述】:

我正在研究一个 Polygon 类,它在 Array[Vec2] 中保存一组顶点(Vec2 是定义 x 和 y 的简单案例类)。

现在,我想实现一个函数,以在 Array[LineSegment] 中返回多边形的边缘(其中 LineSegment 又是一个定义开始和结束的简单案例类)。

解决方案是创建将数组中每个顶点连接到下一个顶点的线段,最后将最后一个顶点连接到第一个顶点。

我只习惯命令式编程,所以这是我的命令式方法:

def edges: Array[LineSegment] = {
  val result = new Array[LineSegment](vertices.length)

  for (i <- 0 to vertices.length - 2) {
    result.update(i, LineSegment(vertices.apply(i), vertices.apply(i + 1)))
  }
  result.update(edges.length - 1, LineSegment(vertices.head, vertices.last))

  result
}

这很好用,但是很丑。我想在这里利用函数式编程的优势,但我有点坚持。

我的想法是把它写成类似这样:

def edges: Array[LineSegment] = {
    for (v <- vertices) yield 
      LineSegment(v, if (v == vertices.last) vertices.head else /* next? */)
}

问题是在给定当前项v的情况下,无法访问数组中的下一个项。

我已经阅读了IterableLike 中定义的sliding 方法,但是这似乎是不可旋转的,即它不会认为第一项在最后一项之后,因此不会返回它。

那么什么是好的“scala-esque”方法呢?

【问题讨论】:

  • 你能澄清一件事吗:你有一个由一组点定义的多边形,这些点都位于某个平面上,并且它们都以一致的绕组隐式连接(即在 [A,B ,C] 顶点你实际上有一个三角形,(BA)和(CA)的叉积(归一化)产生一个垂直于平面的单位向量)。对吗?
  • 是的,没错。请注意,我在二维空间中工作,因此我们甚至不必进行正常操作(当然,除非这有助于找到解决方案)。

标签: scala functional-programming scala-2.8


【解决方案1】:

可能的解决方案可能不是尝试访问下一个元素,而是保留前一个元素。因此,您可以采用 foldLeft 来满足您的需要 - 获取一个没有第一个元素的边数组切片,将第一个元素作为起始值,接下来是这样的:

val n = 5
(0 to n).toList.slice(1, n + 1).foldLeft(0)((x, y) => {print(x,y); y})

输出:(0,1) (1,2) (2,3) (3,4) (4,5)

【讨论】:

  • 这将允许我将每个顶点连接到下一个顶点,但连接 (5, 0) 会缺失。
【解决方案2】:

可以 (a) 枚举所有(有向)边(由其顶点的索引定义),(b) 给定边列表和顶点列表,以构造线段数组(通过查找)。我认为这两项任务都可以在没有突变的情况下完成。

第一个任务(以及您似乎正在努力解决的那个)可以按如下方式处理(在 Haskell 中):

foldr (\x a -> (x,(x+1) `mod` 4):a) [] [0..3]

剩下的就是概括这个例子。

这是你想做的吗?

编辑:添加示例

【讨论】:

  • 老实说,我在将 Haskell 源代码转换为 Scala 时遇到了一点麻烦......我是函数式编程的新手,不知道 Haskell 语法 - 但如果你可以帮我分解一下吗?
  • 当然(我不知道 Scala,所以请原谅)。 foldr 是一个右关联折叠列表。我们传递给它的函数有一个索引 x,并产生边 (x, (x+1) % 4),其中 % 是模运算的缩写(加法“环绕”4)。然后它将这条边合并到生成的边列表中。因此,给定一个从 0 到 3 的顶点索引列表,我们计算相应的边。
  • 啊哈!是的,这也是一个解决方案,使用模数创建一个循环,谢谢!
  • 为什么人们一直坚持用 Haskell 回答 Scala 问题?如果 Scala 没有折叠,我会理解,但你并没有展示任何不能在这里本地完成的东西。
  • 这是 IndexedSeq 的快速解决方案。
【解决方案3】:
def cyclicSliding[A](s:Seq[A]) = s.zip(s.tail :+ s.head)

println(cyclicSliding(List(1,2,3,4)))
//--> List((1,2), (2,3), (3,4), (4,1))

【讨论】:

  • 这完全符合我的需要 - 非常感谢!我现在看一下zip 以了解其工作原理。
  • 这种方法的“问题”是:+ 方法,当在 Seq 的末尾添加元素代价高昂时(例如,在 List 的情况下),它会降低性能。 Debilski 的解决方案也是如此。
  • 可能会更糟,但在这种情况下,将其转换为迭代器可能会有所帮助(然后对循环项使用“迭代器添加”——或者,好吧,手动添加它......)。跨度>
【解决方案4】:

当然你可以使用sliding:

(vertices :+ vertices.head) sliding 2

【讨论】:

  • 只需将第一项附加到列表的末尾,有时解决方案就是这么简单......非常感谢!
  • 如果是List,前置会更快,但您仍然需要遍历整个列表才能获取最后一个元素。不过,我认为性能会好得多。
【解决方案5】:

如果您想避免像 Debilski 的解决方案那样复制整个列表,您可以稍微冗长:

vertices.view.sliding(2).map(p => if (p.size == 1) p :+ vertices.head else p)

这会在一系列视图上生成一个迭代器上的视图,所以不要感到惊讶。

【讨论】:

    【解决方案6】:

    另一种方法是使用 zip。这次使用 zipAll:

    val l1 = List(1,2,3,4)
    l1 zipAll (l1.tail,null,l1.head)
    res: List((1,2), (2,3), (3,4), (4,1))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-11
      • 2012-06-13
      • 2011-03-17
      • 2021-01-14
      • 2011-01-09
      相关资源
      最近更新 更多