Элементы сдвига в массиве по индексу

Задан массив из n элементов, т.е.

var array = [1, 2, 3, 4, 5]

Я могу написать расширение для Array чтобы изменить массив для получения такого результата: [2, 3, 4, 5, 1]:

mutating func shiftRight() {
  append(removeFirst())
}

Есть ли способ реализовать такую функцию, которая бы сдвигала массив на любой индекс, положительный или отрицательный. Я могу реализовать эту функцию в императивном стиле с помощью предложений if-else, но я ищу функциональную реализацию.

Алгоритм прост:

  1. Разбить массив на две части по предоставленному индексу
  2. добавить первый массив в конец второго

Есть ли способ реализовать его в функциональном стиле?

Код, с которым я закончил:

extension Array {
  mutating func shift(var amount: Int) {
    guard -count...count ~= amount else { return }
    if amount < 0 { amount += count }
    self = Array(self[amount ..< count] + self[0 ..< amount])
  }
}

Ответ 1

Вы можете использовать индексирование по диапазону и объединить результаты. Это даст вам то, что вы ищете, с именами, похожими на стандартную библиотеку:

extension Array {
    func shiftRight(var amount: Int = 1) -> [Element] {
        assert(-count...count ~= amount, "Shift amount out of bounds")
        if amount < 0 { amount += count }  // this needs to be >= 0
        return Array(self[amount ..< count] + self[0 ..< amount])
    }

    mutating func shiftRightInPlace(amount: Int = 1) {
        self = shiftRight(amount)
    }
}

Array(1...10).shiftRight()
// [2, 3, 4, 5, 6, 7, 8, 9, 10, 1]
Array(1...10).shiftRight(7)
// [8, 9, 10, 1, 2, 3, 4, 5, 6, 7]

Вместо подписи, вы также можете вернуть Array(suffix(count - amount) + prefix(amount)) из shiftRight().

Ответ 2

В Swift 5 вы можете создать shift(withDistance:) и shiftInPlace(withDistance:) в расширении Array со следующей реализацией, чтобы решить вашу проблему:

extension Array {

    func shift(withDistance distance: Int = 1) -> Array<Element> {
        let offsetIndex = distance >= 0 ?
            self.index(startIndex, offsetBy: distance, limitedBy: endIndex) :
            self.index(endIndex, offsetBy: distance, limitedBy: startIndex)

        guard let index = offsetIndex else { return self }
        return Array(self[index ..< endIndex] + self[startIndex ..< index])
    }

    mutating func shiftInPlace(withDistance distance: Int = 1) {
        self = shift(withDistance: distance)
    }

}

Использование:

let array = Array(1...10)
let newArray = array.shift(withDistance: 3)
print(newArray) // prints: [4, 5, 6, 7, 8, 9, 10, 1, 2, 3]
var array = Array(1...10)
array.shiftInPlace(withDistance: -2)
print(array) // prints: [9, 10, 1, 2, 3, 4, 5, 6, 7, 8]
let array = Array(1...10)
let newArray = array.shift(withDistance: 30)
print(newArray) // prints: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let array = Array(1...10)
let newArray = array.shift(withDistance: 0)
print(newArray) // prints: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var array = Array(1...10)
array.shiftInPlace()
print(array) // prints: [2, 3, 4, 5, 6, 7, 8, 9, 10, 1]
var array = [Int]()
array.shiftInPlace(withDistance: -2)
print(array) // prints: []

Ответ 3

Я попытался написать несколько расширений для этого. У него есть несколько приятных особенностей:

  • Сдвиг на величину, превышающую count вызывает циклическое изменение.
  • Сдвиг на отрицательные суммы меняет направление
  • Выставляет функции как двоичные операторы со сдвигом битов (<<, <<=, >>, >>=)


extension Array {
    public func shiftedLeft(by rawOffset: Int = 1) -> Array {
        let clampedAmount = rawOffset % count
        let offset = clampedAmount < 0 ? count + clampedAmount : clampedAmount
        return Array(self[offset ..< count] + self[0 ..< offset])
    }

    public func shiftedRight(by rawOffset: Int = 1) -> Array {
        return self.shiftedLeft(by: -rawOffset)
    }

    public mutating func shiftLeftInPlace(by rawOffset: Int = 1) {
        if rawOffset == 0 { return /* no-op */ }

        func shiftedIndex(for index: Int) -> Int {
            let candidateIndex = (index + rawOffset) % self.count

            if candidateIndex < 0 {
                return candidateIndex + self.count
            }

            return candidateIndex
        }

        // Create a sequence of indexs of items that need to be swapped.
        //
        // For example, to shift ["A", "B", "C", "D", "E"] left by 1:
        // Swapping 2 with 0: ["C", "B", "A", "D", "E"]
        // Swapping 4 with 2: ["C", "B", "E", "D", "A"]
        // Swapping 1 with 4: ["C", "A", "E", "D", "B"]
        // Swapping 3 with 1: ["C", "D", "E", "A", "B"] <- Final Result
        //
        // The sequence here is [0, 2, 4, 1, 3].
        // It turned into [(2, 0), (4, 2), (1, 4), (3, 1)] by the zip/dropFirst trick below.
        let indexes = sequence(first: 0, next: { index in
            let nextIndex = shiftedIndex(for: index)
            if nextIndex == 0 { return nil } // We've come full-circle
            return nextIndex
        })

        print(self)
        for (source, dest) in zip(indexes.dropFirst(), indexes) {
            self.swapAt(source, dest)
            print("Swapping \(source) with \(dest): \(self)")
        }
        print(Array<(Int, Int)>(zip(indexes.dropFirst(), indexes)))
    }

    public mutating func shiftRightInPlace(by rawOffset: Int = 1) {
        self.shiftLeftInPlace(by: rawOffset)
    }
}

public func << <T>(array: [T], offset: Int) -> [T] { return array.shiftedLeft(by: offset) }
public func >> <T>(array: [T], offset: Int) -> [T] { return array.shiftedRight(by: offset) }
public func <<= <T>(array: inout [T], offset: Int) { return array.shiftLeftInPlace(by: offset) }
public func >>= <T>(array: inout [T], offset: Int) { return array.shiftRightInPlace(by: offset) }

Вы можете увидеть это в действии здесь.

Вот более общее решение, которое лениво реализует эту функциональность для любого типа, отвечающего требованиям:

extension RandomAccessCollection where
    Self: RangeReplaceableCollection,
    Self.Index == Int,
    Self.IndexDistance == Int {
    func shiftedLeft(by rawOffset: Int = 1) -> RangeReplaceableSlice<Self> {
        let clampedAmount = rawOffset % count
        let offset = clampedAmount < 0 ? count + clampedAmount : clampedAmount
        return self[offset ..< count] + self[0 ..< offset]
    }

    func shiftedRight(by rawOffset: Int = 1) -> RangeReplaceableSlice<Self> {
        return self.shiftedLeft(by: -rawOffset)
    }

    mutating func shiftLeft(by rawOffset: Int = 1) {
        self = Self.init(self.shiftedLeft(by: rawOffset))
    }

    mutating func shiftRight(by rawOffset: Int = 1) {
        self = Self.init(self.shiftedRight(by: rawOffset))
    }

    //Swift 3
    static func << (c: Self, offset: Int) -> RangeReplaceableSlice<Self> { return c.shiftedLeft(by: offset) }
    static func >> (c: Self, offset: Int) -> RangeReplaceableSlice<Self> { return c.shiftedRight(by: offset) }
    static func <<= (c: inout Self, offset: Int) { return c.shiftLeft(by: offset) }
    static func >>= (c: inout Self, offset: Int) { return c.shiftRight(by: offset) }
}

Ответ 4

Здесь представлена ​​функциональная реализация для вращения "на месте", которая не требует дополнительной памяти или временной переменной и выполняет не более одного свопа на элемент.

extension Array 
{
    mutating func rotateLeft(by rotations:Int) 
    { 
       let _ =                                              // silence warnings
       (1..<Swift.max(1,count*((rotations+1)%(count+1)%1))) // will do zero or count - 1 swaps
       .reduce((i:0,r:count+rotations%count))               // i: swap index r:effective offset
       { s,_ in let j = (s.i+s.r)%count                     // j: index of value for position i
         swap(&self[j],&self[s.i])                          // swap to place value at rotated index  
         return (j,s.r)                                     // continue with next index to place
       }
    }
}

Оптимально поддерживает нулевые, положительные и отрицательные вращения, а также вращения большей величины, чем размер массива и поворот пустого массива (т.е. он не может не работать).

Использует отрицательные значения для вращения в другом направлении (вправо).

Вращение массива из 3 элементов на 10 подобно вращению на 1, первые девять вращений вернут его в исходное состояние (но мы не хотим перемещать элементы более одного раза).

Вращение массива 5 элементов справа на 3, т.е. rotateLeft (на: -3) эквивалентно rotateLeft (по: 2). Функция "эффективное смещение" учитывает это.

Ответ 5

Простое решение,

 public func solution(_ A : [Int], _ K : Int) -> [Int] {

    if A.count > 0 {
        let roundedK: Int = K % A.count

        let rotatedArray = Array(A.dropFirst(A.count - roundedK) + A.dropLast(roundedK))

        return rotatedArray
    }

    return []
}

Ответ 6

Следуя ответам Nate Cook, мне также нужно сдвинуть массив, возвращающий обратный порядок, поэтому я сделал:

//MARK: - Array extension 
Array {
    func shiftRight( amount: Int = 1) -> [Element] {
        var amountMutable = amount
        assert(-count...count ~= amountMutable, "Shift amount out of bounds")
        if amountMutable < 0 { amountMutable += count }  // this needs to be >= 0
        return Array(self[amountMutable ..< count] + self[0 ..< amountMutable])
    }
    func reverseShift( amount: Int = 1) -> [Element] {
        var amountMutable = amount
        amountMutable = count-amountMutable-1
        let a: [Element] = self.reverse()
        return a.shiftRight(amountMutable)
    }

    mutating func shiftRightInPlace(amount: Int = 1) {
        self = shiftRight(amount)
    }

    mutating func reverseShiftInPlace(amount: Int = 1) {
        self = reverseShift(amount)
    }
}

Мы имеем, например:

Array(1...10).shiftRight()
// [2, 3, 4, 5, 6, 7, 8, 9, 10, 1]
Array(1...10).shiftRight(7)
// [8, 9, 10, 1, 2, 3, 4, 5, 6, 7]
Array(1...10).reverseShift()
// [2, 1, 10, 9, 8, 7, 6, 5, 4, 3]
Array(1...10).reverseShift(7)
// [8, 7, 6, 5, 4, 3, 2, 1, 10, 9]

Ответ 7

В объекте C вы можете просто получить сдвинутый слева массив следующим образом:

- (NSMutableArray *)shiftedArrayWithOffset:(NSInteger)offset
{
    NSMutableArray *bufferArray = [[NSMutableArray alloc] initWithArray:originalArray];
    for (int i = 0; i < offset; i++)
    {
        id object = [bufferArray firstObject];
        [bufferArray removeObjectAtIndex:0];
        [bufferArray addObject:object];
    }
    return bufferArray;
}

Ответ 8

Самый быстрый способ (но берет двойную память!):

:

var arr = [1,2,3,4,5]
let k = 1 (num steps to rotate)
let n = arr.count ( a little but faster )

вращение LEFT:

    var temp = arr
    for i in 0..<n {
        arr[(n-i+k)%n] = temp[i]
    }

result: [2, 1, 4, 3, 5]

вращение ВПРАВО:

    var temp = arr
    for i in 0..<n {
    arr[(i+k)%n] = temp[i]
    }

result: [4, 1, 2, 3, 5]