# Reducing a list of points into a list of pairs of points

I have some data…

let points = [(718, 620), (4596, 1280), (410, 333), (4597, 993),
(410, 337), (4597, 996), (428, 337), (4597, 1000), (431, 335), (4599, 1044)]


… that follows the pattern

[(pointStartOne.x, pointStartOne.y), (pointEndOne.x, pointEndOne.y),
(pointStartTwo.x, pointStartTwo.x) // ...


I need to structure it to

typealias PointPair = [(start: Point, end: Point)]
struct Point {
let x: Int
let y: Int
}


Here is my code:

var result: PointPair = []
while i < points.count{
result.append((Point(x: points[i].0, y: points[i].1), Point(x: points[i+1].0, y: points[i+1].1)))
i+=2
}


I think maybe it is nice situation to use functional programming reduce

Here is code ituitive:

let result = zip((points.enumerated().filter({ (offset: Int, element: (Int, Int)) -> Bool in
return offset%2==0
})), (points.enumerated().filter({ (offset: Int, element: (Int, Int)) -> Bool in
return offset%2==1
}))).reduce(PointPair(), { (result: PointPair, arg) -> PointPair in
let (one, other) = arg
let (offset, point) = one
let (offsetTwo, otherPoint) = other
return result + [(Point(x: point.0,y: point.1), Point(x: otherPoint.0,y: otherPoint.1))]
})


It is messy. Trying to use functional programming conveniently and elegantly.

Here is the improved code:

result = points.enumerated().reduce(into: PointPair(), {(temp, arg) in
let (offset, point) = arg
if offset % 2 == 0{
temp += [(Point(x: point.0, y: point.1), Point(x: 0, y: 0) )]
}
else if let last = temp.last {
temp[temp.count - 1] = (last.0, Point(x: point.0, y: point.1))
}
})


var i = 0
while i < points.count {
// ...
i += 2
}


can be written as a for-loop over a stride:

for i in stride(from: 0, to: points.count, by: 2) {
// ...
}


which restricts the scope of i to the loop body, and makes it a constant. Now each loop iteration appends exactly one element to var result: PointPair, which means that we can write this as a map operation:

let result: PointPair = stride(from: 0, to: points.count, by: 2)
.map { i in
(start: Point(x: points[i].0, y: points[i].1),
end: Point(x: points[i+1].0, y: points[i+1].1))
}


This may already be the “functional” version that you are looking for, but let's also have a look at your other two variants.

The “intuitive code” creates two additional arrays by filtering the original one for even/odd indices. After filtering the arrays the offsets are not needed, giving the warnings

Immutable value 'offset' was never used; consider replacing with '_' or removing it
Immutable value 'offsetTwo' was never used; consider replacing with '_' or removing it


This can be fixed with

let (_, point) = one
let (_, otherPoint) = other


A better way is to use compactMap() (which can be thought of as a combination of filter() and map()) to create arrays of the points with even/odd indices, without the element offsets.

Using reduce() for creating the result array is inefficient, as it creates intermediate arrays for each iteration. reduce(into:_:) would be an improvement, but actually this is just a map():

let result: PointPair = zip(
points.enumerated().compactMap { (offset, element) -> Point? in
offset % 2 == 0 ? Point(x: element.0, y: element.1) : nil },
points.enumerated().compactMap { (offset, element) -> Point? in
offset % 2 == 1 ? Point(x: element.0, y: element.1) : nil }
).map { ($0,$1) }


This looks a lot “less messy” than your original version, but still has the disadvantage of creating intermediate arrays.

I have not much to say about your last version. It uses reduce(into:) which is good to avoid the creation of intermediate arrays. The only disadvantage is that temporary pairs are created and modified in every second step.

So the “stride + map” variant looks like the best to me: It is efficient and easy to read.

A final remark: The type alias

typealias PointPair = [(start: Point, end: Point)]


is misleading, because it does not define a pair of points but an array of pairs of points. I would probably define a dedicated type for a pair of points instead

struct Line {
let from: Point
let to: Point
}


and then use [Line] as the result type.

one other idea is to add chunked to Array (source: HackingWithSwift):

import UIKit

extension Array {
func chunked(into size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
Array(self[$0 ..< Swift.min($0 + size, count)])
}
}
}

struct Point {
let x: Int
let y: Int
}

struct Line {
let from: Point
let to: Point
}


just for better prints in Playground -> it removes the __lldb_expr prefix:

extension Point: CustomStringConvertible {
var description: String { return "Point( x:\(x), y:\(y) )" }
}
extension Line: CustomStringConvertible {
var description: String { return "Line( \(from) -> \(to) )" }
}


test it:

let points = [(718, 620), (4596, 1280), (410, 333), (4597, 993),
(410, 337), (4597, 996), (428, 337), (4597, 1000), (431, 335), (4599, 1044)]

let lines = points.map{ point in Point(x: point.0, y: point.1) }
.chunked(into: 2)
.map{ pointpair in Line(from: pointpair[0],to:pointpair[1]) }

dump(lines)



output:

▿ 5 elements
▿ Line( Point( x:718, y:620 ) -> Point( x:4596, y:1280 ) )
▿ from: Point( x:718, y:620 )
- x: 718
- y: 620
▿ to: Point( x:4596, y:1280 )
- x: 4596
- y: 1280
▿ Line( Point( x:410, y:333 ) -> Point( x:4597, y:993 ) )
▿ from: Point( x:410, y:333 )
- x: 410
- y: 333
▿ to: Point( x:4597, y:993 )
- x: 4597
- y: 993
▿ Line( Point( x:410, y:337 ) -> Point( x:4597, y:996 ) )
▿ from: Point( x:410, y:337 )
- x: 410
- y: 337
▿ to: Point( x:4597, y:996 )
- x: 4597
- y: 996
▿ Line( Point( x:428, y:337 ) -> Point( x:4597, y:1000 ) )
▿ from: Point( x:428, y:337 )
- x: 428
- y: 337
▿ to: Point( x:4597, y:1000 )
- x: 4597
- y: 1000
▿ Line( Point( x:431, y:335 ) -> Point( x:4599, y:1044 ) )
▿ from: Point( x:431, y:335 )
- x: 431
- y: 335
▿ to: Point( x:4599, y:1044 )
- x: 4599
- y: 1044