Zelko
Zelko

Reputation: 3961

Shift Swift Array

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()
]

Upvotes: 6

Views: 9548

Answers (5)

ULazdins
ULazdins

Reputation: 2025

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
}

Upvotes: 3

Benno Kress
Benno Kress

Reputation: 2809

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]

Upvotes: 10

yalewzj
yalewzj

Reputation: 1179

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

Upvotes: 10

0x141E
0x141E

Reputation: 12753

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()]

Upvotes: 3

Ted Park
Ted Park

Reputation: 21

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

Upvotes: 2

Related Questions