0

I have an array of chapters and units.

var chaptersAndUnits: [String] = ["Chapter 2", "Chapter 3", "Chapter 5", "Unit 12", "Chapter 40"]

I am loading them into a UIPickerView so I order them with chaptersAndUnits = chaptersAndUnits.sorted().

However, after this process I get an erratic value: ["Chapter 2", "Chapter 3", "Chapter 40", "Chapter 5", "Unit 12"].

I would like to eliminate that Chapter 40 is inserted after Chapter 3. This also occurs with values like Chapter 22, it will insert it after Chapter 1.

What do I need to do to remove these errors? I have looked into regex expressions, but couldn't make them work.

How would I go about writing my own sorter to fix this?

TIA!

Ars_Codicis
  • 440
  • 1
  • 4
  • 16
  • 2
    Does the order of the `Units` matter? Like is "Unit 12" supposed to go after Chapter 5? – goatofanerd Oct 04 '22 at 23:50
  • "I get an erratic value" it's not erratic; it's alphabetical order. If you don't like that, write a sort function – matt Oct 05 '22 at 00:11
  • 1
    If you're looking for fancier ways to work with hierarchal data you may be interested in https://developer.apple.com/documentation/swiftui/outlinegroup – carlynorama Oct 05 '22 at 01:21
  • @matt that's my problem, I don't know how to write a "sort" function effectively – Ars_Codicis Oct 05 '22 at 13:43

2 Answers2

1

I'd recommend making a struct holding the type of section it is (Chapters vs Units), and holding the number. This will make it possible for your .sorted() method to properly sort it.

To understand your "erratic" value, it's not all that erratic. It's sorting your strings by alphabetical order, which makes sense. Technically "Chapter 40" is before "Chapter 5" because "4" < "5". We can fix this by using the struct as followed:

struct Section: Comparable {
var type: String! //Could also make this an enum with 2 values: .chapter and .unit.
var sectionNumber: Int!

//This is the function that is called every time a "<" operator is used on two Section objects.
//if the left hand side's sectionNumber (lhs) is less than the right hand side's, then we return "true"
static func < (lhs: Section, rhs: Section) -> Bool {
    return lhs.sectionNumber < rhs.sectionNumber
}

//Same thing, but greater than.
static func > (lhs: Section, rhs: Section) -> Bool {
    return lhs.sectionNumber > rhs.sectionNumber
}
}

Now, we can run .sorted() Because remember, all a sorting algorithm relies on is if one number is greater than the other, so we're doing that by implementing our own > and < signs.

var arr = [Section(type: "Chapter", sectionNumber: 5), 
Section(type: "Chapter", sectionNumber: 1),
Section(type: "Chapter", sectionNumber: 8),
Section(type: "Chapter", sectionNumber: 10),
Section(type: "Chapter", sectionNumber: 1),
Section(type: "Chapter", sectionNumber: 9),
Section(type: "Unit", sectionNumber: 2)]
arr = arr.sorted()

(This is all assuming Unit is the same thing as Chapter, not sure if that's true).

goatofanerd
  • 468
  • 2
  • 12
1

This is the expected behaviour. You are comparing strings, not numbers, and "Chapter 40" > "Chapter 5" == true as "4" > "5" alphabetically and they are the characters in the same positions in the strings.

If you want to retain the data as strings and order the way you want you will need a custom sorting function. Based on the example strings you could use something like


func bookLessThan(_ lhs: String, _ rhs: String) -> Bool {
   let lhsComponents = lhs.components(separatedBy: " ")
   let rhsComponents = rhs.components(separatedBy: " ")
   return lhsComponents.first! != rhsComponents.first! ? lhsComponents.first! < rhsComponents.first! :
   Int(lhsComponents.last!)! < Int(rhsComponents.last!)!
}

var chaptersAndUnits: [String] = ["Chapter 2", "Chapter 3", "Chapter 5", "Unit 12", "Chapter 40"]

let sorted = chaptersAndUnits.sorted(by: bookLessThan)

print(sorted)
// ["Chapter 2", "Chapter 3", "Chapter 5", "Chapter 40", "Unit 12"]

NB: In practice you'd want to safely unwrap all the optionals in case your strings aren't all in the expected format, and then handle that, maybe by returning a false value so they are all at the end.

flanker
  • 3,840
  • 1
  • 12
  • 20