在 Swift 中如何将数组项重新排列到新位置?

考虑数组 [1,2,3,4]。如何将数组项重新排列到新位置。

例如:

put 3 into position 4 [1,2,4,3]

put 4 in to position 1 [4,1,2,3]

put 2 into position 3 [1,3,2,4].

75743 次浏览

Swift 3.0+:

let element = arr.remove(at: 3)
arr.insert(element, at: 2)

and in function form:

func rearrange<T>(array: Array<T>, fromIndex: Int, toIndex: Int) -> Array<T>{
var arr = array
let element = arr.remove(at: fromIndex)
arr.insert(element, at: toIndex)


return arr
}

Swift 2.0:

This puts 3 into position 4.

let element = arr.removeAtIndex(3)
arr.insert(element, atIndex: 2)

You can even make a general function:

func rearrange<T>(array: Array<T>, fromIndex: Int, toIndex: Int) -> Array<T>{
var arr = array
let element = arr.removeAtIndex(fromIndex)
arr.insert(element, atIndex: toIndex)


return arr
}

The var arr is needed here, because you can't mutate the input parameter without specifying it to be in-out. In our case however we get a pure functions with no side effects, which is a lot easier to reason with, in my opinion. You could then call it like this:

let arr = [1,2,3,4]
rearrange(arr, fromIndex: 2, toIndex: 0) //[3,1,2,4]

We can use swap method to swap items in an array :

var arr = ["one", "two", "three", "four", "five"]


// Swap elements at index: 2 and 3
print(arr)
swap(&arr[2], &arr[3])
print(arr)

There is no move functionality in swift for arrays. you can take an object at an index by removing it from there and place it in your favourite index by using 'insert'

var swiftarray = [1,2,3,4]
let myobject = swiftarray.removeAtIndex(1) // 2 is the object at 1st index
let myindex = 3
swiftarray.insert(myobject, atIndex: myindex) // if you want to insert the    object to a particular index here it is 3
swiftarray.append(myobject) // if you want to move the object to last index

edit/update: Swift 3.x

extension RangeReplaceableCollection where Indices: Equatable {
mutating func rearrange(from: Index, to: Index) {
precondition(from != to && indices.contains(from) && indices.contains(to), "invalid indices")
insert(remove(at: from), at: to)
}
}

var numbers = [1,2,3,4]
numbers.rearrange(from: 1, to: 2)


print(numbers)  // [1, 3, 2, 4]

nice tip from Leo.

for Swift 3...5.5:

extension Array {
mutating func rearrange(from: Int, to: Int) {
insert(remove(at: from), at: to)
}
}


var myArray = [1,2,3,4]
myArray.rearrange(from: 1, to: 2)
print(myArray)

Update with Swift 4, Swipe array index

for (index,addres) in self.address.enumerated() {
if addres.defaultShipping == true{
let defaultShipping = self.address.remove(at: index)
self.address.insert(defaultShipping, at: 0)
}
}
var arr = ["one", "two", "three", "four", "five"]


// Swap elements at index: 2 and 3
print(arr)
arr.swapAt(2, 3)
print(arr)

All great answers! Here's a more complete Swift 5 solution with performance in mind and bonus for benchmark and GIF fans. ✌️

extension Array where Element: Equatable
{
mutating func move(_ element: Element, to newIndex: Index) {
if let oldIndex: Int = self.firstIndex(of: element) { self.move(from: oldIndex, to: newIndex) }
}
}


extension Array
{
mutating func move(from oldIndex: Index, to newIndex: Index) {
// Don't work for free and use swap when indices are next to each other - this
// won't rebuild array and will be super efficient.
if oldIndex == newIndex { return }
if abs(newIndex - oldIndex) == 1 { return self.swapAt(oldIndex, newIndex) }
self.insert(self.remove(at: oldIndex), at: newIndex)
}
}

GIF

Leo Dabus's solution is great however using precondition(from != to && indices.contains(from != to && indices.contains(to), "invalid indexes"), will crash the app if the conditions are not met. I changed it to guard and an if statement - if for some reason the conditions are not met, nothing happens and the app continues. I think we should avoid making extensions that may crash the app. If you wish you could make the rearrange function return a Bool - true if successful and false if failed. The safer solution:

extension Array {
mutating func rearrange(from: Int, to: Int) {
guard from != to else { return }
//precondition(from != to && indices.contains(from) && indices.contains(to), "invalid indexes")
if indices.contains(from) && indices.contains(to) {
insert(remove(at: from), at: to)
}
}

Swift 4 - Solution for moving a group of items from an IndexSet of indices, grouping them and moving them to a destination index. Realised through an extension to RangeReplaceableCollection. Includes a method to remove and return all items in an IndexSet. I wasn't sure how to constrain the extension to a more generalised form than to constrain the element than integer while maintaining the ability to construct IndexSets as my knowledge of Swift Protocols is not that extensive.

extension RangeReplaceableCollection where Self.Indices.Element == Int {


/**
Removes the items contained in an `IndexSet` from the collection.
Items outside of the collection range will be ignored.


- Parameter indexSet: The set of indices to be removed.
- Returns: Returns the removed items as an `Array<Self.Element>`.
*/
@discardableResult
mutating func removeItems(in indexSet: IndexSet) -> [Self.Element] {


var returnItems = [Self.Element]()


for (index, _) in self.enumerated().reversed() {
if indexSet.contains(index) {
returnItems.insert(self.remove(at: index), at: startIndex)
}
}
return returnItems
}




/**
Moves a set of items with indices contained in an `IndexSet` to a
destination index within the collection.


- Parameters:
- indexSet: The `IndexSet` of items to move.
- destinationIndex: The destination index to which to move the items.
- Returns: `true` if the operation completes successfully else `false`.


If any items fall outside of the range of the collection this function
will fail with a fatal error.
*/
@discardableResult
mutating func moveItems(from indexSet: IndexSet, to destinationIndex: Index) -> Bool {


guard indexSet.isSubset(of: IndexSet(indices)) else {
debugPrint("Source indices out of range.")
return false
}
guard (0..<self.count + indexSet.count).contains(destinationIndex) else {
debugPrint("Destination index out of range.")
return false
}


let itemsToMove = self.removeItems(in: indexSet)


let modifiedDestinationIndex:Int = {
return destinationIndex - indexSet.filter { destinationIndex > $0 }.count
}()


self.insert(contentsOf: itemsToMove, at: modifiedDestinationIndex)


return true
}
}

Efficient solution:

extension Array
{
mutating func move(from sourceIndex: Int, to destinationIndex: Int)
{
guard
sourceIndex != destinationIndex
&& Swift.min(sourceIndex, destinationIndex) >= 0
&& Swift.max(sourceIndex, destinationIndex) < count
else {
return
}


let direction = sourceIndex < destinationIndex ? 1 : -1
var sourceIndex = sourceIndex


repeat {
let nextSourceIndex = sourceIndex + direction
swapAt(sourceIndex, nextSourceIndex)
sourceIndex = nextSourceIndex
}
while sourceIndex != destinationIndex
}
}

Swift 5

extension Array where Element: Equatable {
mutating func move(_ item: Element, to newIndex: Index) {
if let index = index(of: item) {
move(at: index, to: newIndex)
}
}
    

mutating func bringToFront(item: Element) {
move(item, to: 0)
}
    

mutating func sendToBack(item: Element) {
move(item, to: endIndex-1)
}
}


extension Array {
mutating func move(at index: Index, to newIndex: Index) {
insert(remove(at: index), at: newIndex)
}
}

@ian has provided good solution but it will be crash when array become out of bound added check for that too

extension Array where Element: Equatable {
public mutating func move(_ element: Element, to newIndex: Index) {
if let oldIndex: Int = index(of: element) {
self.move(from: oldIndex, to: newIndex)
}
}


public mutating func moveToFirst(item: Element) {
self.move(item, to: 0)
}


public mutating func move(from oldIndex: Index, to newIndex: Index) {
// won't rebuild array and will be super efficient.
if oldIndex == newIndex { return }
// Index out of bound handle here
if newIndex >= self.count { return }
// Don't work for free and use swap when indices are next to each other - this
if abs(newIndex - oldIndex) == 1 { return self.swapAt(oldIndex, newIndex) }
// Remove at old index and insert at new location
self.insert(self.remove(at: oldIndex), at: newIndex)
}
}

Function(not swift but universal.. lookup/remove/insert):

func c_move_to(var array:Array,var from:Int,var to:Int):


var val = array[from]
array.remove(from)
array.insert(to,val)
return array

How to use:

print("MOVE 0 to 3  [1,2,3,4,5]"  , c_move_to([1,2,3,4,5],0,3))
print("MOVE 1 to 2  [1,2,3,4,5]"  , c_move_to([1,2,3,4,5],1,2))

spits out:

MOVE 0 to 3  [1,2,3,4,5][2, 3, 4, 1, 5]
MOVE 1 to 2  [1,2,3,4,5][1, 3, 2, 4, 5]

How about this solution? The element to be changed and the element to be changed have been changed.

// Extenstion


extension Array where Element: Equatable {
mutating func change(_ element: Element, to newIndex: Index) {
if let firstIndex = self.firstIndex(of: element) {
self.insert(element, at: 0)
self.remove(at: firstIndex + 1)
}
}
}


// Example


var testArray = ["a", "b", "c", "EE", "d"]
testArray.change("EE", to: 0)


// --> Result
// ["EE", "a", "b", "c", "d"]
func adjustIndex(_ index: Int, forRemovalAt removed: Int) -> Int {
return index <= removed ? index : index - 1
}


extension Array
{
mutating func move(from oldIndex: Index, to newIndex: Index) {
insert(remove(at: oldIndex), at: adjustIndex(newIndex, forRemovalAt: oldIndex))
}
}

Here's a solution with functions to both change the array in-place and to return a changed array:

extension Array {
func rearranged(from fromIndex: Int, to toIndex: Int) -> [Element] {
var arr = self
let element = arr.remove(at: fromIndex)
        

if toIndex >= self.count {
arr.append(element)
} else {
arr.insert(element, at: toIndex)
}
return arr
}
    

mutating func rearrange(from fromIndex: Int, to toIndex: Int) {
let element = self.remove(at: fromIndex)
if toIndex >= self.count {
self.append(element)
} else {
self.insert(element, at: toIndex)
}
}
}

Since macOS 10.15, iOS 14, MutableCollection has the method move(fromOffsets:toOffset:).

https://developer.apple.com/documentation/swift/mutablecollection/move(fromoffsets:tooffset:)

Swift 5 Tested

Just to add extra toppings on cake, I've added functionality to handle Array<Dictionary<String,Any>>

Main Source of my answer here https://stackoverflow.com/a/50205000/4131763,

here is my version,

//Array+Extension.swift,
extension Array where Element: Equatable
{
mutating func move(_ element: Element, to newIndex: Index) {
if let oldIndex: Int = self.firstIndex(of: element) { self.move(from: oldIndex, to: newIndex) }
}
}


extension Array where Element == Dictionary<String, Any> {
    

mutating func move(_ element:Element, to newIndex: Index) {
if let oldIndex = self.firstIndex(where: { ($0.keys.first ?? "") == (element.keys.first ?? "") }) {
self.move(from: oldIndex, to: newIndex)
}
}
}


extension Array
{
mutating func move(from oldIndex: Index, to newIndex: Index) {
// Don't work for free and use swap when indices are next to each other - this
// won't rebuild array and will be super efficient.
if oldIndex == newIndex { return }
if abs(newIndex - oldIndex) == 1 { return self.swapAt(oldIndex, newIndex) }
self.insert(self.remove(at: oldIndex), at: newIndex)
}
}

HOW TO USE,

if let oldIndex = array.firstIndex(where: { ($0["ValidationTitle"] as! String) == "MEDICALNOTICEREQUIRED" }) {
let obj = array[oldIndex]
array.move(obj, to: array.startIndex)
}
                

if let oldIndex = array.firstIndex(where: { ($0["ValidationTitle"] as! String) == "HIGHRISKCONFIRMATION" }) {
let obj = array[oldIndex]
let oldIndexMEDICALNOTICEREQUIRED = array.firstIndex(where: { ($0["ValidationTitle"] as! String) == "MEDICALNOTICEREQUIRED" })!
array.move(obj, to: oldIndexMEDICALNOTICEREQUIRED + 1)
}
                

if let oldIndex = array.firstIndex(where: { ($0["ValidationTitle"] as! String) == "UNLICENCEDCONFIRMATION" }) {
let obj = array[oldIndex]
let oldIndexHIGHRISKCONFIRMATION = array.firstIndex(where: { ($0["ValidationTitle"] as! String) == "HIGHRISKCONFIRMATION" })!
array.move(obj, to: oldIndexHIGHRISKCONFIRMATION + 1)
}