-1

I have an array tags which include several elements with id and name. I also have an array order where some of the elements in tags are present. Here is what I want to achieve:

  1. All elements in tags should be sorted according to order.
  2. Those elements which is not present in order should be ordered alphabetically after the elements which are present in order.

I have solved it using a for loop (the code runs in Playground):

import Foundation
import UIKit

struct Tag: Identifiable {
    var id: Int
    var name: String
}

// Ccc > Bbb > Aaa > Ddd > Eee
var tags = [Tag(id: 1000, name: "Ccc"), Tag(id: 1001, name: "Bbb"), Tag(id: 1002, name: "Aaa"), Tag(id: 1003, name: "Ddd"), Tag(id: 1004, name: "Eee")]

// Eee > Ddd > Ccc > Bbb > Aaa
tags.sort(by: { $0.name < $1.name })

// Bbb > Ddd
var idOrdering = [1001, 1003]

// Bbb > Ddd > Aaa > Ccc > Eee
for orderIndex in idOrdering.indices {
    // Get tag id.
    let tagId = idOrdering[orderIndex]

    let tagIndex = tags.firstIndex(where: { $0.id == tagId })

    // Remove tag from original array and place it according to the `order`.
    let removedTag = tags.remove(at: tagIndex!)
    tags.insert(removedTag, at: orderIndex)
}

// Print the result.
tags.forEach {
    print($0.name)
}

The order of the elements in the original tags is Ccc > Bbb > Aaa > Ddd > Eee. Two of the elements named Bbb and Ddd should be ordered based on order, that is, Bbb > Ddd. The rest should be ordered alphabetically. In other words, the end result should be Bbb > Ddd > Aaa > Ccc > Eee. Although the for loop above works, how can I solve this problem more efficiently?

Simen
  • 417
  • 6
  • 13
  • Seems like this answer of mine is an exact match for what you want: https://stackoverflow.com/a/43056896/3141234 – Alexander Apr 24 '20 at 21:04
  • 2
    The `UUID`s in this question are unwieldy and distract from the core problem. I recommend you replace them with small `Int`s, and produce an expected `output` array. – Alexander Apr 24 '20 at 21:06
  • `x = x.sorted { ... }` is a bad practice. Either just use the in-place variant (`x.sort { ... }`), or better yet, just create a new variable. – Alexander Apr 24 '20 at 21:15
  • Code updated according to feedback. – Simen Apr 25 '20 at 07:43

2 Answers2

0

I would combine two bits of code:

  1. HardCodedOrdering from this answer of mine, which uses a fixed-ordering (such as your order array) to derive sort keys for objects.
  2. Use this pattern to sort an object according to multiple criteria: https://stackoverflow.com/a/37612765/3141234

import Foundation

struct Tag: Identifiable {
    var id = UUID()
    var name: String
}

// Bbb > Ddd
let idOrdering = HardCodedOrdering(ordering:
    UUID(uuidString: "007D8DD7-7DF7-4937-BE53-C8CCE9C34A09")!,
    UUID(uuidString: "B1792EF4-CD4C-4437-90C2-37FE56D3A18F")!,
    sortUnspecifiedItems: .last
)

// Ccc > Bbb > Aaa > Ddd > Eee
let tags = [
    Tag(id: UUID(uuidString: "20FCD62B-97B8-4097-B659-29357EABE786")!, name: "Ccc"),
    Tag(id: UUID(uuidString: "007D8DD7-7DF7-4937-BE53-C8CCE9C34A09")!, name: "Bbb"),
    Tag(id: UUID(uuidString: "36FCEBF2-9912-4D67-A0F3-5EE1BF6EFE86")!, name: "Aaa"),
    Tag(id: UUID(uuidString: "B1792EF4-CD4C-4437-90C2-37FE56D3A18F")!, name: "Ddd"),
    Tag(id: UUID(uuidString: "463927D5-F6D8-4099-99FC-D3DE226B00C9")!, name: "Eee"),
]

let result = tags.sorted {
    (idOrdering.sortKey(for: $0.id), $0.name) <
    (idOrdering.sortKey(for: $1.id), $1.name)
}

result.forEach { print($0) }
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Is your `HardCodedOrdering()` more efficient than my `for` loop above? – Simen Apr 25 '20 at 07:42
  • @Simen Theoretically, it has better asymptotic time complexity, but obviously you have to profile it to be sure. But frankly, it doesn't even matter how efficient it is, it's actually understandable, which is *way* more important. – Alexander Apr 25 '20 at 14:20
0

Based on this answer mentioned by Alexander I ended up with the following solution:

tags.sort {
    (idOrdering.firstIndex(of: $0.id) ?? idOrdering.count, $0.name) <
    (idOrdering.firstIndex(of: $1.id) ?? idOrdering.count, $1.name)
}
Simen
  • 417
  • 6
  • 13