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()
]
1
  • just remove the colours before the colour you want to start with, then just append them to the end of the array? Commented Jul 22, 2015 at 5:43

5 Answers 5

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]
Sign up to request clarification or add additional context in comments.

2 Comments

I like optimization on your solution. Is it possible to refactor this extension to mutate existing array instead of creating new one? @benno-kress
Yes, totally. This extension contains both methods: gist.github.com/bennokress/a12f967e809575be3820c76578640143
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

2 Comments

getting error: Cannot subscript a value of type '[CGColor]' with an index of type 'CountableRange<Int>' My code is: ``` guard let indexOfStartColor = possibleColors.index(of: startColor) else { return } let index = indexOfStartColor.distance(to: 0) rotatedColors = possibleColors[index..<possibleColors.count] rotatedColors += possibleColors[0..<index]```
Does not shift with negative index i.e. the other way!
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()]

1 Comment

This would crash with an empty array
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
}

Comments

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)")})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.