Colin
Colin

Reputation: 3752

swift function to iterate possibly reversed array

I'd like to create a function that will iterate over an array (or collection or sequence). Then I will call that function with an array, and the reversed version of the array (but efficiently: without creating a new array to hold the reverse).

If I do this:

func doIteration(points: [CGPoint]) {
  for p in points {
     doSomethingWithPoint(p)
  }
  // I also need random access to points
  doSomethingElseWithPoint(points[points.count-2]) // ignore obvious index error
}

And if I have this:

let points : [CGPoint] = whatever

I can do this just fine:

doIteration(points)

But then if I do this:

doIteration(points.reverse())

I get 'Cannot convert value of type 'ReverseRandomAccessCollection<[CGPoint]> to expected argument type [_]'

Now, I DON'T want to do this:

let reversedPoints : [CGPoint] = points.reverse()
doIteration(reversedPoints)

even though it will work, because that will (correct me if I'm wrong) create a new array, initializing it from the ReverseRandomAccessCollection returned by reverse().

So I guess I'd like to write my doIteration function to take some sort of sequence type, so I can pass in the result of reverse() directly, but ReverseRandomAccessCollection doesn't conform to anything at all. I think I'm missing something - what's the accepted pattern here?

Upvotes: 3

Views: 254

Answers (2)

Luca Angeletti
Luca Angeletti

Reputation: 59506

You can do this

let reversedPoints : [CGPoint] = points.reverse()
doIteration(reversedPoints)

or this

doIteration(points.reverse() as [CGPoint])

but I don't think there is any real difference by the point of view of a the footprint.

Scenario 1

let reversedPoints : [CGPoint] = points.reverse()
doIteration(reversedPoints)

Infact in this case a new Array containing references to the CGPoint(s) present in the original array is created. This thanks to the Copy-on-write mechanism that Swift used to manage structures.

So the memory allocated is the following:

points.count * sizeOf(pointer)

Scenario 2

On the other hand you can write something like this

doIteration(points.reverse() as [CGPoint])

But are you really saving memory? Let's see. A temporary variable is created, that variable is available inside the scope of the function doIteration and requires exactly a pointer for each element contained in points so again we have:

points.count * sizeOf(pointer)

So I think you can safely choose one of the 2 solutions.

Considerations

We should remember that Swift manages structures in a very smart way.

When I write

var word = "Hello"
var anotherWord = word

On the first line Swift create a Struct and fill it with the value "Hello". On the second line Swift detect that there is no real reason to create a copy of the original String so writes inside the anotherWord a reference to the original value.

Only when word or anotherWord is modified Swift really create a copy of the original value.

Upvotes: 0

oisdk
oisdk

Reputation: 10091

If you change your parameter's type to a generic, you should get the functionality you need:

func doIteration
  <C: CollectionType where C.Index: RandomAccessIndexType,  C.Generator.Element == CGPoint>
  (points: C) {
  for p in points {
    doSomethingWithPoint(p)
  }
  doSomethingElseWithPoint(points[points.endIndex - 2])
}

More importantly, this won't cause a copy of the array to be made. If you look at the type generated by the reverse() method:

let points: [CGPoint] = []

let reversed = points.reverse() // ReverseRandomAccessCollection<Array<__C.CGPoint>>

doIteration(reversed)

You'll see that it just creates a struct that references the original array, in reverse. (although it does have value-type semantics) And the original function can accept this new collection, because of the correct generic constraints.

Upvotes: 2

Related Questions