6

Array of Colors

let colorArray = [
    UIColor.redColor(),
    UIColor.orangeColor(),
    UIColor.yellowColor(),
    UIColor.greenColor(),
    UIColor.blueColor()
]

The goal is to shift the array:

  1. To start with a different color.
  2. To preserve the circular order of colors.

Example #1

If we wanted to start with the orange color (the color at index 1 in the original array), the array would look like this:

let colorArray = [
    UIColor.orangeColor(),
    UIColor.yellowColor(),
    UIColor.greenColor(),
    UIColor.blueColor(),
    UIColor.redColor(),
]

Example #2

If we wanted to start with the green color (the color at index 3 in the original array), the array would look like this:

let colorArray = [
    UIColor.greenColor(),
    UIColor.blueColor(),
    UIColor.redColor(),
    UIColor.orangeColor(),
    UIColor.yellowColor()
]
Zelko
  • 3,793
  • 3
  • 34
  • 40
  • just remove the colours before the colour you want to start with, then just append them to the end of the array? – Fonix Jul 22 '15 at 05:43

5 Answers5

10

I know this might be late. But the easiest way to rotate or shift an array is

func shifter(shiftIndex: Int) {
   let strArr: [String] = ["a","b","c","d"]
   var newArr = strArr[shiftIndex..<strArr.count]
   newArr += strArr[0..<shiftIndex]       
   println(newArr)  }

shifter(2) //[c, d, a, b] you can modify the function to take array as input
zizutg
  • 1,070
  • 14
  • 20
  • getting error: Cannot subscript a value of type '[CGColor]' with an index of type 'CountableRange' My code is: ``` guard let indexOfStartColor = possibleColors.index(of: startColor) else { return } let index = indexOfStartColor.distance(to: 0) rotatedColors = possibleColors[index.. – v-i-s-h-a-l Jan 06 '17 at 10:28
  • Does not shift with negative index i.e. the other way! – StackUnderflow Jun 05 '17 at 20:12
10

Short & clear Swift 3 & 4 solution I came up with:

extension Array {

    func shifted(by shiftAmount: Int) -> Array<Element> {

        // 1
        guard self.count > 0, (shiftAmount % self.count) != 0 else { return self }

        // 2
        let moduloShiftAmount = shiftAmount % self.count
        let negativeShift = shiftAmount < 0
        let effectiveShiftAmount = negativeShift ? moduloShiftAmount + self.count : moduloShiftAmount

        // 3
        let shift: (Int) -> Int = { return $0 + effectiveShiftAmount >= self.count ? $0 + effectiveShiftAmount - self.count : $0 + effectiveShiftAmount }

        // 4
        return self.enumerated().sorted(by: { shift($0.offset) < shift($1.offset) }).map { $0.element }

    }

}

Explanation:

  1. Arrays with no elements and shifts producing the identity of the original array are returned immediately
  2. To get the effective shift amount regardless of the amount passed with the function, we do some modulo calculation to get rid of shifts that would rotate the elements in the array more than once (e.g. in an Array with 5 Objects, a shift of +7 is the same as a shift of +2). Since we always want to shift to the right, in order to be done with one simple function instead of two, negative inputs have to be dealt with (e.g. in an Array with 5 Objects, a shift of -2 is the same as a shift of +3). Therefore we adjust the negative results of the modulo calculation by the length of the array. Of course those 3 lines could be done in one, but I wanted to make this as readable as possible.
  3. Now we prepare the actual shift by taking the index ($0) of element and returning the shifted index by adding the amount calculated in step 2. If the new index lands outside of the array length, it needs to be wrapped around to the front.
  4. And finally we apply all the preparation to our array with some trickery: enumerated() gives us an array of tuples [(offset: Int, element: Int)], which is simply the original index of every element and the element itself. We then sort this enumerated array by the manipulated offset (aka the element's index) via applying the function from step 3. Lastly we get rid of the enumeration by mapping the sorted elements back into an array.

This extension works with arrays of any type. Examples:

let colorArray = [
    UIColor.red,
    UIColor.orange,
    UIColor.yellow,
    UIColor.green,
    UIColor.blue
]

let shiftedColorArray = [
    UIColor.green,
    UIColor.blue,
    UIColor.red,
    UIColor.orange,
    UIColor.yellow
]

colorArray.shifted(by: 2) == shiftedColorArray // returns true

[1,2,3,4,5,6,7].shifted(by: -23) // returns [3,4,5,6,7,1,2]
Benno Kress
  • 2,637
  • 27
  • 39
  • I like optimization on your solution. Is it possible to refactor this extension to mutate existing array instead of creating new one? @benno-kress – muhasturk May 16 '20 at 23:06
  • Yes, totally. This extension contains both methods: https://gist.github.com/bennokress/a12f967e809575be3820c76578640143 – Benno Kress May 17 '20 at 15:16
3

You can extend Array to include a method to return an array containing the elements of the original array rotated by one element:

extension Array {
    func rotate(shift:Int) -> Array {
        var array = Array()
        if (self.count > 0) {
            array = self
            if (shift > 0) {
                for i in 1...shift {
                    array.append(array.removeAtIndex(0))
                }
            }
            else if (shift < 0) {
                for i in 1...abs(shift) {
                    array.insert(array.removeAtIndex(array.count-1),atIndex:0)
                }
            }
        }
        return array
    }
}

To shifts the elements of an array once

let colorArray:[UIColor] = [
    .redColor(),
    .orangeColor(),
    .yellowColor(),
    .greenColor(),
    .blueColor()
]

let z = colorArray.rotate(1)

// z is [.orangeColor(), .yellowColor(), .greenColor(), .blueColor(), .redColor()]

and twice

let z = colorArray.rotate(2)

// z is [.yellowColor(), .greenColor(), .blueColor(), .redColor(), .orangeColor()]
0x141E
  • 12,613
  • 2
  • 41
  • 54
3

A variation of @zizutg's answer, that can shift in both directions (positive and negative)

extension Array {
    public func shifted(by index: Int) -> Array {
        let adjustedIndex = index %% self.count

        return Array(self[adjustedIndex..<self.count] + self[0..<adjustedIndex])
    }
}

// True modulo function https://stackoverflow.com/a/41180619/683763
infix operator %%
public func %%(_ dividend: Int, _ divisor: Int) -> Int {
    precondition(divisor > 0, "modulus must be positive")
    let reminder = dividend % divisor
    return reminder >= 0 ? reminder : reminder + divisor
}
ULazdins
  • 1,975
  • 4
  • 25
  • 31
2

You can iterate by handling starting index.

func iterate<T>(array:Array<T>, start:Int, callback:(T) -> ()) {
    let count = array.count
    for index in start..<(start + count) {
        callback(array[index % count])
    }
}

If you want to start from index 3

iterate(colors, 3, { (color) -> () in println("color - \(color)")})
Ted Park
  • 21
  • 2