How to store many attributes at once? - swift

I'm working with Swift, Xcode 6 and SpriteKit,
I want make a game where some sprites fall down from the top of the screen, but each sprite have a defined speed, position and activation time. I have this working code, but I really don't think that it's the most optimised way to do it:
class obstacles: SKSpriteNode
{
var isOnScreen = false
var type: Int = 0
var initTime: Int = 0
}
var obstacle = [obstacles]() // obstacle is an array of SKSpriteNode
// type : initTime : speed : position : xScale : yScale
var level1: [String] = ["0:120:3:0:1:1", "0:130:4:80:2:2","1:140:8:120:6:1","0:150:6:240:2:2"]
override func didMoveToView(view: SKView)
{
initObstacles()
}
func initObstacles()
{
for var x = 0; x < level1.count; x++ // for each obstacle
{
var index = 0
var type = String("")
var initTime = String("")
var speed = String("")
var position = String("")
var xScale = String("")
var yScale = String("")
var lastIndex = obstacle.count
for Character in level1[x] // we read each character one by one
{
if Character == ":" { index++ } // if it's a ":" we change the variable
else
{
switch index
{
case 0:
type += String(Character)
case 1:
initTime += String(Character)
case 2:
speed += String(Character)
case 3:
position += String(Character)
case 4:
xScale += String(Character)
case 5:
yScale += String(Character)
default:
break;
}
}
}
obstacle.append(obstacles(imageNamed: "Rectangle")) // we add an element to the array
obstacle[lastIndex].type = type.toInt()! // we init all the values
obstacle[lastIndex].initTime = initTime.toInt()!
obstacle[lastIndex].speed = CGFloat(speed.toInt()!)
obstacle[lastIndex].size.width = DEFAULT_OBSTACLE_SIZE * CGFloat(xScale.toInt()!)
obstacle[lastIndex].size.height = DEFAULT_OBSTACLE_SIZE * CGFloat(yScale.toInt()!)
obstacle[lastIndex].position = CGPointMake(CGFloat(position.toInt()!) - obstacle[lastIndex].size.width/2, CGRectGetMaxY(frame) + obstacle[lastIndex].size.height/2)
}
}
Do you know how could I manage to do the same thing, but more "clean" ?

I would suggest to create a class or struct that holds all necessary data for an obstacle and additionally change your type from a standard number to an enum, e.g.:
enum ObstacleType {
case Block, Tree, WhatEverObstaclesYouHave...
}
struct Obstacle {
var type: ObstacleType
var initializationTime: NSTimeInterval
var speed: Double
// and similarly for position, your scales and what you may need in future
}
and create them using, e.g.
Obstacle(type: .Block, initializationTime: 0, speed: 12.0, ...)
Advantage (obviously) is that you have no problems anymore parsing your string (there is no string anymore) and can provide all necessary information using the appropriate type directly. And you can easily use an enum for your type, which should be an enum, because your Obstacle is not a 1, but a Block, Tree or whatever.
Your level1 variable could then be defined like this:
var level1 : [Obstacle] = [
Obstacle(type: .Block, initializationTime: 120, speed: 3.0, ...),
Obstacle(type: .Block, initializationTime: 130, speed: 4.0, ...),
Obstacle(type: .Tree, initializationTime: 140, speed: 8.0, ...),
Obstacle(type: .Block, initializationTime: 150, speed: 6.0, ...)
]
To get rid of the labels in the initializer, just define your own initializer. Just add this to your struct:
init(_ type: ObstacleType, _ time: NSTimeInterval, _ speed: Double, ...) {
self.type = type
self.initializationTime = time
self.speed = speed
// other variables...
}
Then you can create every Obstacle like this:
Obstacle(.Block, 120, 3.0, ...)
But now you can not easily tell anymore which number has what meaning from reading the instantiation. I do not recommend this just to type less as autocomplete will present you with most of it.

Related

Why did Xcode warn me about making this a constant, and why does it still change?

Within my updateBlob function, Xcode warned me that pos is unchanged and should be changed to a let constant, even though I can see that it's being changed, and running the program does indeed change the position values. This all seemed to happen when I updated the BlobPos class with a defer keyword to update the x/y coordinates when it is sent the radius value. Although I could avoid using defer, why does the compiler warn me of making pos a constant, and the program is still able to change what should presumably be a constant?
class BlobPos
{
var x:CGFloat = 0
var y:CGFloat = 0
public init(radius:CGFloat) {
defer {
x = radius + 5
y = radius + 5
}
}
}
class Blob
{
var radius: CGFloat
var pos: BlobPos
init
(
radius: CGFloat,
pos: BlobPos,
)
{
self.radius = radius
self.pos = pos
}
}
func makeBlob() -> Blob
{
let radius = 8
let pos = BlobPos(radius:radius)
return Blob(radius: radius, pos: pos)
}
func updateBlob(blob:Blob)
{
let radius = blob.radius
let pos = blob.pos // compiler warning wanting me to turn this into a let constant instead of var
pos.x += 6
pos.y += 2
blob.pos = pos // strangely, new position is set
}
That is because BlobPos is a class and changing a class's properties doesn't change its location in memory, which is how classes are passed around (by reference to their location in memory). If BlobPos were a structure, then you would have to declare it a variable because structures are passed around by their values (not references to their locations in memory).

Class variables are deallocated after calling function

I have a function in swift, as below. There is a loop with a reference to variables existing within an instance of this class. (fftfilterbankReal is an array in the class). However after one pass, I get an error with 'index out of range' at the code line 'for i in 0..
In the debugger it seems that on the 2nd iteration of this loop, there are no variables under the 'self' drop down.
If I comment out the line 'vDSP_zvmul(&kernel!, 1, &fft1Input, 1, &result, 1, vDSP_Length(r.count), 1)' then the loop runs and I can debug at any time and visually see the self variables in the debugger.
What am I missing that seems to make these variables disappear? I have read into memory allocation and such, and my class variables are declared using 'var' and nothing more, as that should default to strong in swift.
func convolveInput(realsamples:[Float], imagsamples:[Float]) -> [Float]{
realResult = Array(repeating: [], count: filterbankReal.count)
imagResult = Array(repeating: [], count: filterbankReal.count)
let x = realsamples
let y = imagsamples
var N = x.count
var logN = 16
var fft1Setup = vDSP_create_fftsetup(UInt(logN), FFTRadix(FFT_RADIX2))!
var paddedLength = x.count + filterbankReal.count - 1
var halfPaddedLength = paddedLength/2
var halfKernelLength = kernelLength/2
//setup Complex Buffer 1
var reals = [Float]()
var imags = [Float]()
for i in 0..<x.count{
reals.append(x[i])
imags.append(y[i])
}
var complexBuffer1 = DSPSplitComplex(realp: UnsafeMutablePointer(mutating: reals), imagp: UnsafeMutablePointer(mutating: imags))
//Perform FFT on incoming samples
var re = [Float](repeating:0.0, count: N)
var im = [Float](repeating:0.0, count: N)
var fft1Input = DSPSplitComplex(realp: UnsafeMutablePointer(mutating: re), imagp: UnsafeMutablePointer(mutating: im))
var fftlength = 10
vDSP_fft_zop(fft1Setup, &(complexBuffer1), 1, &fft1Input, 1, UInt(fftlength), Int32(FFT_FORWARD))
//Remove DC from FFT Signal
re.remove(at: 0)
im.remove(at: 0)
for i in 0..<self.fftfilterbankReal.count {
var r:[Float] = self.fftfilterbankReal[i]
var im:[Float] = self.fftfilterbankImag[i]
var kernel:DSPSplitComplex? = DSPSplitComplex(realp: &r, imagp: &im)
var res:Float = 0
var ims:Float = 0
var result:DSPSplitComplex = DSPSplitComplex(realp: &res, imagp: &ims)
vDSP_zvmul(&kernel!, 1, &fft1Input, 1, &result, 1, vDSP_Length(r.count), 1)
self.realResult[i].append(res)
self.imagResult[i].append(ims)
}
Your code is sort of a showcase of bad usages when working with Arrays and pointers.
For example:
var complexBuffer1 = DSPSplitComplex(realp: UnsafeMutablePointer(mutating: reals), imagp: UnsafeMutablePointer(mutating: imags))
or:
var kernel:DSPSplitComplex? = DSPSplitComplex(realp: &r, imagp: &im)
DSPSplitComplex holds two pointers for real part and imaginary part separately and does not copy the contents. You should not pass Swift Arrays for such parameters.
And the most critical part in your code is...
var res:Float = 0
var ims:Float = 0
var result:DSPSplitComplex = DSPSplitComplex(realp: &res, imagp: &ims)
vDSP_zvmul(&kernel!, 1, &fft1Input, 1, &result, 1, vDSP_Length(r.count), 1)
vDSP_zvmul generates N (in your code N = vDSP_Length(r.count)) complex numbers, so you need to prepare a region which can hold N elements.
Once you call vDSP_zvmul with your current code, you break whole stack contents which causes what you have experienced:
In the debugger it seems that on the 2nd iteration of this loop, there
are no variables under the 'self' drop down.
You are hiding many parts of your code, so it is very hard to guess what you really want to do, but if I re-write your code in safer manner, it would be something like this:
func convolveInput(realsamples:[Float], imagsamples:[Float]) -> [Float]{
realResult = Array(repeating: [], count: filterbankReal.count)
imagResult = Array(repeating: [], count: filterbankReal.count)
let x = realsamples
let y = imagsamples
var N = x.count
var logN = 16
var fft1Setup = vDSP_create_fftsetup(UInt(logN), FFTRadix(FFT_RADIX2))!
var paddedLength = x.count + filterbankReal.count - 1
var halfPaddedLength = paddedLength/2
var halfKernelLength = kernelLength/2
//setup Complex Buffer 1
var reals = UnsafeMutableBufferPointer<Float>.allocate(capacity: x.count)
defer {reals.deallocate()}
var imags = UnsafeMutableBufferPointer<Float>.allocate(capacity: y.count)
defer {imags.deallocate()}
_ = reals.initialize(from: x)
_ = imags.initialize(from: y)
var complexBuffer1 = DSPSplitComplex(realp: reals.baseAddress!, imagp: imags.baseAddress!)
//Perform FFT on incoming samples
var re = UnsafeMutableBufferPointer<Float>.allocate(capacity: N)
defer {re.deallocate()}
var im = UnsafeMutableBufferPointer<Float>.allocate(capacity: N)
defer {im.deallocate()}
var fft1Input = DSPSplitComplex(realp: re.baseAddress!, imagp: im.baseAddress!)
let fftlength = 10
vDSP_fft_zop(fft1Setup, &complexBuffer1, 1, &fft1Input, 1, UInt(fftlength), Int32(FFT_FORWARD))
//Remove DC from FFT Signal
fft1Input = DSPSplitComplex(realp: re.baseAddress!+1, imagp: im.baseAddress!+1)
for i in 0..<self.fftfilterbankReal.count {
self.fftfilterbankReal[i].withUnsafeMutableBufferPointer {rBuf in
self.fftfilterbankImag[i].withUnsafeMutableBufferPointer {imBuf in
var kernel = DSPSplitComplex(realp: rBuf.baseAddress!, imagp: imBuf.baseAddress!)
var res = UnsafeMutableBufferPointer<Float>.allocate(capacity: rBuf.count)
defer {res.deallocate()}
var ims = UnsafeMutableBufferPointer<Float>.allocate(capacity: rBuf.count)
defer {ims.deallocate()}
var result:DSPSplitComplex = DSPSplitComplex(realp: res.baseAddress!, imagp: ims.baseAddress!)
vDSP_zvmul(&kernel, 1, &fft1Input, 1, &result, 1, vDSP_Length(rBuf.count), 1)
//vDSP_zvmul generates `N` complex numbers,
// I do not understand what you really want to do...
self.realResult[i].append(res[0])
self.imagResult[i].append(ims[0])
}
}
}
//...
}
There may be other parts to fix, but anyway, please try and see what you get.
Foreword: Boy, sifting through all this took me almost 2 hours, and it's still not even perfect, but boy is this much nicer. I hope it helps!
Your code is suffer massively because the Accelerate APIs are C APIs that are not adapted to take advantage of Swift features. You will have much more readable code if you make yourself some nice wrappers for the Accelerate API, which lets you tuck all the "Ugly stuff" away into a corner you seldom have to see or edit.
I did this by creating a new type, ComplexFloatArray, which is similar to DSPSplitComplex, but actually encapsulates and owns its buffers. This prevents the dangling buffers that DSPSplitComplex is susceptible to.
After dining the ComplexFloatArray types, it's time to define some wrappers for the the Accelerate functions you used. In this case, vDSP_zvmul and vDSP_fft_zop. Since C doesn't have tuples, returning multiple values from a C function usually requires that you use out-parameters, which are used pervasively in the Accelerate framework. We can re-design these as Swift functions with regular return types. These APIs are very naturally expressed as instance methods that operate on ComplexFloatArray, so we'll put them there.
Additionally, your code is made much more complex by its dependance on external state. Convolution is a function, there's no reason why it does anything besides taking in input (via parameters, and not via instance variables) and returns the result (via return value, and not via instance variables).
import Accelerate
class ComplexFloatArray {
var reals: [Float]
var imaginaries: [Float]
init(reals: [Float], imaginaries: [Float]) {
self.reals = reals
self.imaginaries = imaginaries
}
}
extension ComplexFloatArray { // Core features
var count: Int {
assert(reals.count == imaginaries.count)
return reals.count
}
static let stride = 1
func append(real: Float, imaginary: Float) {
self.reals.append(real)
self.imaginaries.append(imaginary)
}
func useAsDSPSplitComplex<R>(_ closure: (inout DSPSplitComplex) -> R) -> R {
return reals.withUnsafeMutableBufferPointer { realBufferPointer in
return imaginaries.withUnsafeMutableBufferPointer { imaginaryBufferPointer in
var dspSplitComplex = DSPSplitComplex(realp: realBufferPointer.baseAddress!, imagp: imaginaryBufferPointer.baseAddress!)
return closure(&dspSplitComplex)
}
}
}
}
extension ComplexFloatArray { // Convenience utilities
convenience init() {
self.init(reals: [], imaginaries: [])
}
static func zeros(count: Int) -> ComplexFloatArray {
return ComplexFloatArray(reals: Array(repeating: 0, count: count), imaginaries:Array(repeating: 0, count: count))
}
}
extension ComplexFloatArray { // Vector multiplciation extensions
enum ComplexMultiplicationType: Int32 { case normal = 1, conjugate = -1 }
func complexMultiply(
with other: ComplexFloatArray,
multiplicationType: ComplexMultiplicationType = .normal
) -> ComplexFloatArray {
assert(self.count == other.count, "Multiplied vectors must have the same size!")
let result = ComplexFloatArray.zeros(count: self.count)
self.useAsDSPSplitComplex { selfPointer in
other.useAsDSPSplitComplex { otherPointer in
result.useAsDSPSplitComplex { resultPointer in
vDSP_zvmul(
&selfPointer, ComplexFloatArray.stride,
&otherPointer, ComplexFloatArray.stride,
&resultPointer, ComplexFloatArray.stride, vDSP_Length(result.count),
multiplicationType.rawValue)
}
}
}
return result
}
}
extension ComplexFloatArray { // FFT extensions
enum FourierTransformDirection: Int32 { case forward = 1, inverse = -1 }
//TODO: name log2n label better
func outOfPlaceComplexFourierTransform(
setup: FFTSetup,
resultSize: Int,
log2n: UInt,
direction: FourierTransformDirection
) -> ComplexFloatArray {
let result = ComplexFloatArray.zeros(count: resultSize)
self.useAsDSPSplitComplex { selfPointer in
result.useAsDSPSplitComplex{ resultPointer in
vDSP_fft_zop(
setup,
&selfPointer, ComplexFloatArray.stride,
&resultPointer, ComplexFloatArray.stride,
log2n,
direction.rawValue
)
}
}
return result
}
}
extension FFTSetup {
enum FourierTransformRadix: Int32 {
case radix2 = 0, radix3 = 1, radix5 = 2
// Static let constants are only initialized once
// This function's intent to to make sure this enum stays in sync with the raw constants the Accelerate framework uses
static let assertRawValuesAreCorrect: Void = {
func assertRawValue(for actual: FourierTransformRadix, isEqualTo expected: Int) {
assert(actual.rawValue == expected, "\(actual) has a rawValue of \(actual.rawValue), but expected \(expected).")
}
assertRawValue(for: .radix2, isEqualTo: kFFTRadix2)
assertRawValue(for: .radix3, isEqualTo: kFFTRadix3)
assertRawValue(for: .radix5, isEqualTo: kFFTRadix5)
}()
}
init(log2n: Int, _ radix: FourierTransformRadix) {
_ = FourierTransformRadix.assertRawValuesAreCorrect
guard let setup = vDSP_create_fftsetup(vDSP_Length(log2n), FFTRadix(radix.rawValue)) else {
fatalError("vDSP_create_fftsetup(\(log2n), \(radix)) returned nil")
}
self = setup
}
}
struct NameMe {
// I don't know what this is, but if it can somehow be removed,
// the whole convolveInput method could be moved into an extension on ComplexFloatArray.
var fftFilterBank: [ComplexFloatArray]
func convolve(samples: ComplexFloatArray) -> [ComplexFloatArray] {
// TODO: rework reimplement this code to remove the DC from samples, and add it back in
// //Remove DC from FFT Signal
// re.remove(at: 0)
// im.remove(at: 0)
let fftlength: UInt = 10 // Todo: what is this, exactly?
let fft1Input = samples.outOfPlaceComplexFourierTransform( // Rename me to something better
setup: FFTSetup(log2n: 16, .radix2),
resultSize: samples.count,
log2n: fftlength,
direction: .forward
)
return self.fftFilterBank.map { kernel in kernel.complexMultiply(with: fft1Input) }
}
// Stub for compatibility with the old API. Deprecate it and move to the
// `convolve(samples: ComplexFloatArray) -> [ComplexFloatArray]` as soon as possible.
func convolveInput(realsamples: [Float], imagsamples: [Float]) -> [ComplexFloatArray] {
return self.convolve(samples: ComplexFloatArray(reals: realsamples, imaginaries: imagsamples))
}
}
I have some notes along the way
This function is WAAAAAAAAAAAY too long. If you have a function that's over 10 lines long, there's a fairly strong indicator that it's growing too large, does to many things, and could benefit from being broken down into simpler steps.
You have lots of redundant variables. You don't need more than 1 copy of any given immutable value. You have all these different names, referring to the same thing, which just complicates things. There might be an argument to be made that this can be useful if the new names have significance, but names like x, y, re, im are near-useless in their communicative ability, and should almost-always be avoided entirely.
Arrays are value types with Copy-on-Write. You can make copies of them by simply assigning to them to a new variable, so code like:
var reals = [Float]()
var imags = [Float]()
for i in 0..<x.count{
reals.append(x[i])
imags.append(y[i])
}
Is both slow, and visually cumbersome. This could be simply: let (reals, imags) = (x, y). But again, these copies are unnecessary (as are x and y). Remove them, and just use realsamples and imagsamples directly.
When you find yourself frequently passing multiple pieces of data together, that's a very strong indication that you should define a new aggregate type to wrap them. For example, if you're passing two Array<Float> to represent complex vectors, you should define a ComplexVector type. This can let you enforce invariants (e.g. there are always as many real values as imaginary values), and add convenient operations (e.g. a func append(real: Float, imaginary: Float), which operates on both simultaneously, ensuring you can never forget to append to one of the arrays).
In closing,
There's a lot going on here, so I can't possible pre-empt every question and explain it ahead of time. I encourage you to take some time, read through this, and feel free to ask me any follow up questions.
I suspect I've made mistakes during my refactor (because I had no test cases to work with), but the code is modular enough that it should be very simple to isolate and fix and bugs.

Looping Through Arrays of Structs in AudioKit

I'm trying to create MIDI content for sequencer playback by using an array of structs. Each struct creates a 'measure', and each measure has properties of location, numberOfBeats, and tempo. I'd like to loop through each measure for each beat within the measure, and play one of two MIDI notes based on whether the beat is the first in the measure or not.
The code below will print correctly (click1, click 2, click2, click1, click 2, click1, click2, click2), however the MIDI content (attached to a wav) will only play once through and not advance to the next measure in the array. I've been trying to get the func to work it's way sequentially through the array of measures using a variety of loop types, but can't get it to progress correctly. Thanks!
import UIKit
import AudioKit
class ViewController: UIViewController {
let sequencer = AKSequencer ()
var metronomeTrack1: AKMusicTrack!
var metronomeTrack2: AKMusicTrack!
let click1 = AKMIDISampler()
let click2 = AKMIDISampler ()
func setupTracks() {
metronomeTrack1 = sequencer.newTrack()
metronomeTrack1?.setMIDIOutput(click1.midiIn)
metronomeTrack2 = sequencer.newTrack()
metronomeTrack2?.setMIDIOutput(click2.midiIn)
}
struct Measure {
var location : Int
var numberOfBeats : Int
var tempo : Int
}
var measureOne = Measure(location: 1, numberOfBeats: 4, tempo: 100)
var measureTwo = Measure(location: 2, numberOfBeats: 2, tempo: 100)
var measureThree = Measure(location: 3, numberOfBeats: 4, tempo: 100)
var arrayIndex = [Measure]()
//Variables to track location through loops
var arrayLocator = 0
var beatWatcher = 0
func playMeasures () {
for _ in arrayIndex {
for beats in 0...(arrayIndex[arrayLocator].numberOfBeats - 1) {
if beatWatcher == 0 {
metronomeTrack1?.add(noteNumber: 60, velocity: 100, position: AKDuration(beats: Double(beats)), duration: AKDuration(beats: 1))
print("click1")
beatWatcher += 1
} else {
metronomeTrack2?.add(noteNumber: 60, velocity: 100, position: AKDuration(beats: Double(beats)), duration: AKDuration(beats: 1))
print("click2")
beatWatcher += 1
if beatWatcher == (arrayIndex[arrayLocator].numberOfBeats) {
beatWatcher = 0
arrayLocator += 1
}
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
arrayIndex = [measureOne, measureTwo, measureThree]
metronomeTrack1?.clear()
metronomeTrack2?.clear()
sequencer.setLength(AKDuration(beats: 100))
AudioKit.output = click1
AudioKit.output = click2
setupTracks()
try! click1.loadWav("blockhigh")
try! click2.loadWav("blocklow")
playMeasures()
sequencer.play()
try!AudioKit.start()
}
}
After further research; looks like using stride is the best way to work through the loops, and the "position" in each MIDI track needs to increment by 1 each loop to get added to the sequence in proper order.

Trouble using width for String in for-in cycle

I've write a simple code:
extension String {
func trailingSpaces (width: Int) -> String {
var s = "\(self)"
for i in count(s)..<width {
s = s + " "
}
return s
}
func leadingSpaces (width: Int) -> String {
var s = "\(self)"
for i in count(s)..<width {
s = " " + s
}
return s
}
}
class ViewController: UIViewController {
var users = ["Marco", "Gianni", "Antonio", "Giulio", "Franco"]
var ages = [29, 45, 17, 33, 37]
override func viewDidLoad() {
super.viewDidLoad()
var merged = [String: Int] ()
var totalAge = 0.0
for var i = 0; i < ages.count; i++ {
merged[users[i]] = ages[i]
}
for user in sorted(merged.keys) {
let age = merged[user]
totalAge += Double(age!)
let paddedUser = user.trailingSpaces(10)
let paddedAge = "\(age)".leadingSpaces(3)
println("\(paddedUser) \(age!)")
}
println("\n\(merged.count) users")
println("average age: \(totalAge / Double(merged.count))")
}
}
but I can't make it work the leadingSpaces function and I can't understand the reason, it's quite identical to the other extension func that works.
It give the error
fatal error: Can't form Range with end < start
on runtime
in case you run into this kind of problem, always do a println() of the variable you are using
println("\(age)") right before let paddedAge = "\(age!)".leadingSpaces(3)
reveals the problem
age is an optional, meaning that you are trying to do the padding on a String which has this value "Optional(17)"
Thus, your count(s) is higher than 3, and you have an invalid range
Your variable age is not an Int - it's an optional - Int?. You know this already as you are unwrapping it in the lines totalAge += Double(age!) and println("\(paddedUser) \(age!)") - but you are not unwrapping it in the failing line let paddedAge = "\(age)".leadingSpaces(3). The string being passed to leadingSpaces is not "17", it's "Optional(17)", which is why your padding function is failing, as the length is greater than the requested width.
Having said that, as the commentator #milo256 points out, Swift can only iterate upwards, and so unless you put a check on width >= .count in your padding functions they will crash at some point.

Functional programming way of doing array conversion

struct MapVector {
var distance: Double
var bearing: Double
}
func distanceAndBearing() -> [MapVector] {
var points = self.mapPoints
var currPoint:CLLocation = points.first!
points.removeAtIndex(0)
var result: [MapVector] = []
for point: CLLocation in points {
let calc = PointCalculator(initialPoint: currPoint, nextPoint: point)
let v = MapVector(distance: calc.pointDistance, bearing: calc.bearing)
result.append(v)
currPoint = point
}
return result
}
I am working in Swift on an application using map coordinates. I have a an array of CLLocations from which I would like to create an array of distances and bearings. The above code (its slightly simplified for readability, so may not be 100% correct) achieves that but I'd like to do it in a neater way. Is this something that can be done with map or filter? Still trying to get my head around the FP way of doing things.
Here is a simplified example for the same problem except the calculations:
let numbers = [3, 7, 2, 8, 3, 7, 5]
let result = numbers.isEmpty ? [] :
map(zip(numbers, numbers[1..<numbers.count])) {
(x, y) in
return (diff: x - y, mult: x * y)
}
result[0].diff // -4
result[0].mult // 21
Here I compute the differences and the multiplications of the numbers.
Note this will work only for Swift 1.2
In case you need it for earlier version, you should explore the use of Zip2.
For reference here are alternative solutions I came up with:-
func distanceAndBearing2() -> [MapVector]
{
// make the removeAtIndex(0) part safe
if (self.mapPoints.count == 0) {
return []
}
var t2 = self.mapPoints
t2.removeAtIndex(0)
let t3 = zip(self.mapPoints, t2)
return Array(t3).map({
(p1, p2) in
return PointCalculator(initialPoint: p1, nextPoint: p2).toMapVector()
})
}
This uses the new zip method from Xcode 6.3 Beta 2, and I moved the conversion to MapVector into the PointCalculator struct
func distanceAndBearing3() -> [MapVector] {
// make the shift part safe
if (self.mapPoints.count == 0) {
return []
}
var points = self.mapPoints
var currPoint = points.shift()!
return points.map {
point in
let initialPoint = currPoint
currPoint = point
return LocationPair(initialPoint: initialPoint,
nextPoint: point).toMapVector()
}
}
And this version uses the same toMapVector method on the PointCalculator struct, but uses a variable outside the map function which is updated by the map function; this feels like its not "correct"