Home > Mobile >  Swift, How can I reverse specific elements in arrays?
Swift, How can I reverse specific elements in arrays?

Time:11-09

I have an Array with different elements inside, it looks like this:

Vehicles = [ Auto(brand: "bmw", color: "red"),
             Auto(brand: "mazda", color: "red"),
             Auto(brand: "alfa romeo", color: "red"),
             Bike(brand: "suzuki", color: "blue"),
             Bike(brand: "yamaha", color: "black") ]

I need to reverse it based on "color" in this case, so the array would be like:

Vehicles = [ Auto(brand: "alfa romeo", color: "red"),
             Auto(brand: "mazda", color: "red"),
             Auto(brand: "bmw", color: "red"),
             Bike(brand: "suzuki", color: "blue"),
             Bike(brand: "yamaha", color: "black") ]

Elements with the same colors are following each others, like "sub groups", and I'd need to reverse each of these subgroups. ie: [a,b,c,d,e,f,g,h,i], if a,b,c are color 1, d,e,f color 2, and g,h,i of color 3, it's like [(a,b,c), (d,e,f), (g,h,i)], the order should be [(c,b,a), (f, e, d), (i,h,g)], and so [c,b,a,f,e,d,i,h,g]

CodePudding user response:

You can do this in three steps:

  1. Break your collection into subcollections based on color changes. Apple's swift-algorithms package has an operator named chunked(by:) that is perfect for this. Read here about adding package dependencies to your app.

  2. Reverse each subcollection. You can use the standard library reversed() method for this.

  3. Concatenate the subcollections. You can use the standard library joined() method for this.

vehicles = vehicles
    .chunked { $0.color == $1.color }
    .map { $0.reversed() }
    .joined()

If you want more complex logic inside the chunked predicate, you need to use named arguments so you can declare its return type:

vehicles = vehicles
    .chunked { a, b -> Bool in
        switch (a.type, b.type) {
        case (.car, .car): return a.color == b.color
        case (.bike, .bike): return a.color == b.color
        default: return false
        }
    }
    .map { $0.reversed() }
    .joined()

CodePudding user response:

With model & input:

protocol Vehicle: CustomStringConvertible {
    var brand: String { get set }
    var color: String { get set }
}

extension Vehicle {
    var description: String {
        "Brand: \(brand) - color: \(color)"
    }
}
struct Bike: Vehicle {
    var brand: String
    var color: String
}
struct Auto: Vehicle {
    var brand: String
    var color: String
}

let vehicles: [Vehicle] = [ Auto(brand: "bmw", color: "red"),
                            Auto(brand: "mazda", color: "red"),
                            Auto(brand: "alfa romeo", color: "red"),
                            Bike(brand: "suzuki", color: "blue"),
                            Bike(brand: "suzuki2", color: "blue"),
                            Bike(brand: "suzuki3", color: "blue"),
                            Bike(brand: "yamaha", color: "black"),
                            Bike(brand: "yamaha2", color: "black")]

A possible solution, without needing Apple's extra Collection Packages as mentionned by rob mayoff

// Here we create batches, ie subgroups of Vehicle with the same color using `reduce(into:_)`

let batched = vehicles.reduce(into: [[Vehicle]]()) { partialResult, aVehicle in
    //If there is already a batch, we check the last one, and inside that batch, we check the last Vehicle color
   // If it's the same color of the one currently checked (aVehicle), then we append it
    if var lastBatch = partialResult.last, let lastVehicle = lastBatch.last, lastVehicle.color == aVehicle.color {
        lastBatch.append(aVehicle)
        partialResult[partialResult.count - 1] = lastBatch
    } else {
        partialResult.append([aVehicle]) //We create a new batch
    }
}

print("batched: \(batched)")

// Here we reverse each batch
let reversedBatch = batched.map { aBatch -> [Vehicle] in
    return aBatch.reversed() //return can be omitted here
}
print("reversedBatch: \(reversedBatch)")

//We remove the subgroup, flattening the array
let reversed = reversedBatch.flatMap { $0 }
print("reversed: \(reversed)")

Now, that we got the main idea, we can do the first operation and the second one, in one go. Instead of appending, we put it as the beginning of the batch

let batchedAndReversed = vehicles.reduce(into: [[Vehicle]]()) { partialResult, aVehicle in
    if var lastBatch = partialResult.last, let lastVehicle = lastBatch.last, lastVehicle.color == aVehicle.color {
        lastBatch.insert(aVehicle, at: 0)
        partialResult[partialResult.count - 1] = lastBatch
    } else {
        partialResult.append([aVehicle]) //We create a new batch
    }
}
let reversed2 = batchedAndReversed.flatMap { $0 }
print("reversed2: \(reversed2)")

CodePudding user response:

You can sort the array by color and then reverse it using the .sorted(by:) function. Then you can reverse it using the .reversed() function.

let sortedVehicles = vehicles.sorted(by: { car1, car2 -> Bool in
    car1.color < car2.color
}).reversed()

The provided closure determines, whether the the car1 should precede car2 in the sorted array.

If you want to work in-place, you can use the sort(by:) and the reverse() function (or alternatively invert the sort predicate):

vehicles.sort(by: ...)
vehicles.reverse()
  • Related