Working with Arrays in Swift It's an exercise from a Swift-course I'm currently taking.


  You are given an array of integers.  Make an additional array named
  computedNumbers. computedNumbers should multiply each element in the
  numbers array by the next following element. The last element shall be
  multiplied by the first element.
  
  Example: If numbers was equal to ... [3, 1, 4, 2] ... then the
  computedNumbers should computed [3 x 1, 1 x 4, 4 x 2, 2 x 3], so that
  they become: [3, 4, 8, 6]


Here my solution:

var numbers = [45, 73, 195, 53]

var computedNumbers = [Int]()
let count = numbers.count
var i = 0

while i &lt; count - 1 {
    computedNumbers.append(numbers[i] * numbers[i + 1])
    i = i + 1
}

computedNumbers.append(numbers[count - 1] * numbers[0])

print(computedNumbers)


It has passed the automated tests, but it isn't very elegant.

It there a better way to accomplish the task?

What should I have done different and why?

Looking forward to reading your comments and answers.
Handle edge cases

If the given numbers array is empty then the first loop will do nothing, but

computedNumbers.append(numbers[count - 1] * numbers[0])


aborts with a runtime exception. The correct result in this case would be to set computedNumbers to an empty array as well.

Better loops

This

var i = 0
while i &lt; count - 1 {
    // ...
    i = i + 1
}


is shorter and better done as

for i in 0 ..&lt; count - 1 {
    // ...
}


The advantages are


The scope of i  is limited to the loop.
i is a constant.
The range of i is clearly seen right at the start of the loop.


In this particular case we iterate of adjacent array elements, which is conveniently by by zip()ping the array with a shifted view of itself:

var computedNumbers = zip(numbers, numbers.dropFirst()).map(*)
computedNumbers.append(numbers[count - 1] * numbers[0])


If we append the first element to the shifted view then the complete operation reduces to

let computedNumbers = zip(numbers, [numbers.dropFirst(), numbers.prefix(1)].joined()).map(*)


Note that this handles also the case of an empty array gracefully.

Use constants if possible

The numbers array is never mutated, therefore it should be declared as a constant with let:

let numbers = [3, 1, 4, 2]


Using a constant 


makes it clear to the reader that this value is never mutated,
helps to avoid unintentional mutation,
possibly allows better compiler optimization.


Make it a function

If we put the functionality in a separate function then


it becomes reusable,
test cases can be added more easily,
we can give it a meaningful name,
we can add documentation.


In our case the function would simply be

/// Compute an array where each element in the numbers array is multipled by
/// the next following element. The last element is multiplied by the first element.
/// - Parameter numbers: An array of integers.
func circularAdjacentProducts(of numbers: [Int]) -&gt; [Int] {
    return zip(numbers, [numbers.dropFirst(), numbers.prefix(1)].joined()).map(*)
}


and would then be used as

let numbers = [3, 1, 4, 2]
let computedNumbers = circularAdjacentProducts(of: numbers)
print(computedNumbers)


Make it generic on the element type

The same operation could be done with an array of floating point values (Float or Double), or with other integer types (e.g. Int16) as long as the multiplication does not overflow. Therefore we can generalize it to array of type [T] where T conforms to a protocol which required a multiplication operation.

A possible choice is the Numeric protocol which is adopted by all floating point and integer types:

func circularAdjacentProducts&lt;T: Numeric&gt;(of numbers: [T]) -&gt; [T] {
    return zip(numbers, [numbers.dropFirst(), numbers.prefix(1)].joined()).map(*)
}


Example:

let floats = [3.3, 1.1, 4, 2.2]
let computedFloats = circularAdjacentProducts(of: floats)
print(computedFloats) // [3.63, 4.4, 8.8, 7.26]


Make it generic on the collection type

The next possible generalization is to apply this operation not only to arrays, but to arbitrary collections:

extension Collection where Element: Numeric {
    func circularAdjacentProducts() -&gt; [Element] {
        return zip(self, [dropFirst(), prefix(1)].joined()).map(*)
    }
}


Example with an ArraySlice:

let numbers = Array(1...10).dropFirst(3)
print(numbers) // [4, 5, 6, 7, 8, 9, 10]
let computedFloats = numbers.circularAdjacentProducts()
print(computedFloats) // [20, 30, 42, 56, 72, 90, 40]