1

I am trying to do something that with a C-style loop was easy but I am struggling with in Swift3.

I have a loop that I need to increment by different amounts depending on a value stored in an array. The code below describes it better.

let scaleIntervals: [Int] = [2,2,1,2,2,2,1] 
let notes: [String] = ["A", "A#", "B", "C", "C#" "D", "D#" "E", "F" ,"G", "G#"]
var scale: [String] = []

for(var i = 0; i < notes.length; i++){

/* *Sketchy - untested* If the note in the first index of the output array 
matches the current note we have completed the scale. Exit loop */

    if scale[0] == notes[i]{ 
       break
    }

//Add the note to the output array and increment by the next interval

    scale.append(notes[i])
    i += scaleIntervals![i]

//If interval makes i larger than the notes array, loop back round

    if i >= notes.length{
        i -= notes.length
    }


}

If you have read this far and are thinking 'that code doesn't look much like swift', that is because I am currently transitioning from JavaScript to Swift and some habits die hard.

I am after an alternative looping arrangement as for in creates i as a let making it immutable throwing an error at the increment line i += scaleIntervals![i]. I thought stride might work but I can't get my head around how to set it up.

Also using a for in where loop with the modulo operator only works to a point because I make have large increments that could cause a false positive. That being said if I am wrong and you can make it work I would love to learn how.

I'd even accept a completely different structure (i.e. non for-loop).

Glenn Holland
  • 580
  • 3
  • 19
  • I think you meant `scale[0] ==` rather than `scaleInterval[0] ==` ? – jtbandes Jan 21 '17 at 21:15
  • I did indeed. Thanks. I dumbed down my code to make the question easier to write and messed around with my variable names. Thanks for pointing that out. – Glenn Holland Jan 21 '17 at 21:17
  • Really it's a shame the [Swift team rejected](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160502/016543.html) a `scan(_:_:)` method on `Sequence` – you could've said `scaleIntervals.lazy.scan(tonicIndex, {($0 + $1) % notes.count}).map{notes[$0]}` otherwise (you still can actually, the code to implement it is [documented here](https://developer.apple.com/reference/swift/lazysequenceprotocol) – but may or may not be worth the bother). – Hamish Jan 21 '17 at 23:05

1 Answers1

0

Here's a corrected version using a regular for-in loop over the scaleIntervals. You also forgot about F# :-)

let scaleIntervals: [Int] = [2,2,1,2,2,2,1]
let notes: [String] = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"]

func scale(fromNote tonicIndex: Int) -> [String] {
    var result: [String] = []

    var currentIndex = tonicIndex
    for interval in scaleIntervals {
        result.append(notes[currentIndex])
        currentIndex = (currentIndex + interval) % notes.count
    }

    return result
}

print(scale(fromNote: 0))  // ["A", "B", "C#", "D", "E", "F#", "G#"]
print(scale(fromNote: 3))  // ["C", "D", "E", "F", "G", "A", "B"]

Just for fun, here's a single-expression version using reduce. This is really slow to typecheck and probably less efficient, but it's cute:

func scale(fromNote tonicIndex: Int) -> [String] {
    return scaleIntervals.reduce((notes: [], index: 0), {
        ($0.notes + [notes[$0.index]], ($0.index + $1) % notes.count)
    }).notes
}
jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • Poor F# haha. I typed that in a hurry. Thank you so much. Flipping it and using the interval array as the one I iterate over. Didn't think of that. Now I feel a little silly. – Glenn Holland Jan 21 '17 at 21:43
  • Wow, you know your Swift! I see from your profile you are into math. Music math escapes me at times. I guess I am so used to counting base ten that I struggle with getting my head around the 12 part tone equal temperament system. That Reduce method is "cute" as you put it but a bit beyond me at the moment. I never used Redux in JS but get the concept. That's an excellent little example, though. Thanks! – Glenn Holland Jan 21 '17 at 21:51
  • Yes, I studied both music and math :) Just remember that you are working on a logarithmic scale: equal intervals ⟷ equal frequency ratios. The ratio between half-tones is 2^(1/12) so that 12 of them makes an octave: (2^(1/12))^12 = 2. It's only very tangentially related, but if you like math you might find this enlightening: https://jackschaedler.github.io/circles-sines-signals/ – jtbandes Jan 21 '17 at 21:59
  • I like math; I just struggle with it. Had a terrible teacher at college (US high school level) that turned me off of it for a while. I'd benefit from getting back into it for this kind of stuff. Any recommendations of good places to learn online? Keeping in mind that my current level is rather tragic, though. :D – Glenn Holland Jan 21 '17 at 22:10
  • I haven't tried these myself for this kind of math, but I imagine [Khan Academy](https://www.khanacademy.org/) and [Coursera](https://www.coursera.org/) might be good starting points. – jtbandes Jan 21 '17 at 22:16
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/133729/discussion-between-glenn-holland-and-jtbandes). – Glenn Holland Jan 21 '17 at 22:20