【问题标题】:Shift elements in array by index按索引移动数组中的元素
【发布时间】:2015-10-21 16:50:20
【问题描述】:

给定 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])
  }
}

【问题讨论】:

    标签: ios arrays swift generics


    【解决方案1】:

    您可以使用范围下标并连接结果。这将为您提供您正在寻找的内容,其名称类似于标准库:

    extension Array {
        func shiftRight(var amount: Int = 1) -> [Element] {
            guard count > 0 else { return self }
            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]
    

    您也可以从shiftRight() 返回Array(suffix(count - amount) + prefix(amount)),而不是下标。

    【讨论】:

    • 很好的解决方案。我最终得到的函数有点不同,但我特别喜欢如果移位量为负数时你如何处理移位。
    • 我已将完成的代码添加到我的问题中。有点不同,但思路完全一样。
    • 完美解决方案!我想重命名该函数,因为这种转变不会发生就地。不过我理解这是为了暗示它的变异特性。
    • 1. ~= 是什么意思?我找不到任何关于它的文档?如果金额在一个范围内,这可能意味着返回 true。正确的? 2. 我也看不到shiftRightInPlace 用在什么地方。 3 连接结果。您的意思是通过简单的+ 连接 2 个 数组 对吗?
    • 您能回答我的评论吗?
    【解决方案2】:

    使用 Swift 5,您可以使用以下实现在 Array 扩展中创建 shift(withDistance:)shiftInPlace(withDistance:) 方法以解决您的问题:

    extension Array {
    
        /**
         Returns a new array with the first elements up to specified distance being shifted to the end of the collection. If the distance is negative, returns a new array with the last elements up to the specified absolute distance being shifted to the beginning of the collection.
    
         If the absolute distance exceeds the number of elements in the array, the elements are not shifted.
         */
        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])
        }
    
        /**
         Shifts the first elements up to specified distance to the end of the array. If the distance is negative, shifts the last elements up to the specified absolute distance to the beginning of the array.
    
         If the absolute distance exceeds the number of elements in the array, the elements are not shifted.
         */
        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: []
    

    【讨论】:

    • 你好,我知道这是大约 5 年前发布的,但我想问一下,你的这个解决方案在 Hackerrank 上运行良好,但我想知道为什么如果我设置距离它不起作用到 99 的数组 [1,2,3,4,5,6]?
    • 如果您移动距离超过数组大小,此解决方案将不起作用。 XCTest 将失败。 func testShift() { var holder = [1,2,3,4] holder.shiftInPlace() XCTAssertEqual(holder, [2,3,4,1]) holder.shiftInPlace(withDistance: -2) XCTAssertEqual(holder, [4,1,2,3]) holder.shiftInPlace(withDistance: 5) XCTAssertEqual(holder, [2,3,4,1]) }
    • @muhasturk 这是为了模仿prefix(_:) 的行为而设计的。第三个例子已经表明了这一点。我添加了一些 cmets 以使其更清晰。
    • @ImanouPetit,确实是一个很酷的解决方案!我只建议重命名这些方法,这样更符合现有的 Swift API。 shiftshiftedshiftInPlaceshift。请参阅排序方法进行比较。
    【解决方案3】:

    我尝试为此编写一些扩展。它有一些不错的功能:

    • 移动量大于count 会导致回绕。
    • 按负数移动会反转方向
    • 将函数公开为位移二元运算符(&lt;&lt;&lt;&lt;=&gt;&gt;&gt;&gt;=


    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's 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) }
    

    你可以在here看到它。

    这是一个更通用的解决方案,它对任何满足要求的类型都懒惰地实现了这个功能:

    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 次,前 9 次旋转会将其恢复到初始状态(但我们不想移动元素超过一次)。

      将 5 个元素的数组向右旋转 3,即 rotateLeft(by:-3) 等效于 rotateLeft(by: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】:

          我知道我迟到了,但这个基于问题的答案效果很好?

          extension Array {
            mutating func shiftRight(p: Int) {
              for _ in 0..<p {
                append(removeFirst())
              }
            }
          }
          
          start [5, 0, 4, 11, 0]
          shift [5, 0, 4, 11, 0] shift 0
          shift [0, 4, 11, 0, 5] shift 1
          shift [4, 11, 0, 5, 0] shift 2
          shift [11, 0, 5, 0, 4] shift 3
          

          更好的是,如果你要求它移动比数组中更多的元素,它只会继续循环。

          【讨论】:

          • 很好的解决方案,但它不允许向左移动,而且它实际上会多次改变数组,所以这应该有一个可怕的复杂性
          • 当然,地狱的复杂性:) 仅用于非常小的数组:)
          • 也许你可以重写它,让它继续循环,但不会受到这种复杂性的影响?基本上,我的问题是关于不必多次重复 removeFirst()append 并使其更优雅:)
          • 理查德,你的问题的答案就在这里。 github.com/apple/swift-algorithms.
          • 很好,问题是 2015 年 Swift 算法尚未公布时
          【解决方案7】:

          Nate Cook answers 之后,我还需要移动一个返回相反顺序的数组,所以我做了:

          //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]
          

          【讨论】:

            【解决方案8】:

            在目标 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;
            }
            

            【讨论】:

            • 问题是关于 Swift 的实现。使用 NSArray 初始化 NSMutableArray 可以使用[array mutableCopy]; 方法,它更短。
            • Richard,如果您使用复制 - 您将通过复制指针而不是新数组来接收副本。所以你更短的方法会破坏逻辑;)
            【解决方案9】:

            最快的方法是(但需要双倍内存!):

            输入:

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

            旋转向左

                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]
            

            【讨论】:

            • 原理很清楚,但您提供的代码不正确。两个序列 [2, 1, 4, 3, 5] 和 [4, 1, 2, 3, 5] 都不是旋转。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-03-21
            相关资源
            最近更新 更多