Seeking a scala-esque approach to iterate through a list with access to the "next" element

833 views Asked by At

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?

6

There are 6 answers

2
Debilski On BEST ANSWER

Of course you can use sliding:

(vertices :+ vertices.head) sliding 2
1
Dmitry Stropalov On

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)

6
Artyom Shalkhakov On

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

3
Landei On
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))
0
Raphael On

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.

0
Rustem Suniev On

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))