寻求一种 scala 式的方法来迭代列表并访问“下一个”元素

发布于 2024-10-15 20:37:08 字数 1011 浏览 2 评论 0原文

我正在开发一个 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 式”方法呢?

I am working on a Polygon class which holds an array of vertices in an Array[Vec2] (with Vec2 being a simple case class defining x and y).

Now, I would like to implement a function to return the edges of the polygon in an Array[LineSegment] (where LineSegment is again a simple case class which defines start and end).

The solution is to create line segments connecting each vertex to the next in the array, and finally connecting the last vertex to the first.

I'm only used to imperative programming, so this is my imperative approach:

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
}

This works fine, but it's plain ugly. I want to use the advantages of functional programming here, but I'm kinda stuck with that.

My idea was to put it like something similar to this:

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

The problem is that there's no way to access the next item in the array given the current item v.

I've read about the sliding method defined in IterableLike, however that seems to be non-rotating, ie it will not consider the first item to be subsequent to the last item and therefore not return it.

So what is a good "scala-esque" approach to this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

弱骨蛰伏 2024-10-22 20:37:08

当然你可以使用滑动

(vertices :+ vertices.head) sliding 2

Of course you can use sliding:

(vertices :+ vertices.head) sliding 2
简单爱 2024-10-22 20:37:08
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))
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))
一身软味 2024-10-22 20:37:08

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

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

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

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

这是你想做的吗?

编辑:添加了一个示例

It is possible to (a) enumerate all (directed) edges (defined by indices to their vertices), (b) given a list of edges and a list of vertices, to construct an array of line segments (by doing lookups). Both tasks, I think, can be accomplished without mutation.

The first task (and the one you seem to be struggling with) can be dealt with as follows (in Haskell):

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

What is left is to generalize this example.

Is this what you want to do?

EDIT: added an example

羁绊已千年 2024-10-22 20:37:08

另一种方法是使用 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))

One more way to it with zip. This time using 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))
埋情葬爱 2024-10-22 20:37:08

可能的解决方案可能不是尝试访问下一个元素,而是保留前一个元素。因此,您可以根据需要采用 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)

The possible solution is maybe not to try to access the next element, but to keep the prevous one. So, you can adopt a foldLeft to your needs - get a slice of edges array without the first element, put the first element as a starting value and next something like this:

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

Output: (0,1) (1,2) (2,3) (3,4) (4,5)

逆光飞翔i 2024-10-22 20:37:08

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

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

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

If you want to avoid copying the whole list as in Debilski's solution, you can do the slightly verbose:

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

This yields a view on an iterator over a sequence of views, so don't be surprised.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文