0

I'm looking for the most efficient way to go about this. Say there's an array of objects, mapped out in alphabetical order by names:

let objectArray = [AnyObject]()
let abcNameObjectArray = ["Amy", "Bernadette", "Brian", "Chris", "Candice"]

How would I access all the values in this array that have "A" , "B", or "C" based on the first character of each String value in abcNameObject array, in alphabetical order? I'm trying to unload data and return a specific number of rows in the UITableView's cellForRowAt method and numberOfRowsInSection method based on the number of friends one might have in alphabetical order. The goal is to split the array into a SET of arrays to populate a table view like the Contacts app and ALSO ACCESS the values for a given section. Perhaps utilizing a dictionary might be better to solve this?

saikik
  • 13
  • 1
  • 6
  • 1
    Possible duplicate of [Sorting of an array alphabetically in swift](https://stackoverflow.com/questions/36394813/sorting-of-an-array-alphabetically-in-swift) – Max von Hippel Jul 06 '17 at 21:59
  • If it is already sorted, then what are you trying to do? Why dont you iterate through array and create an array of dictionaries with key as "A" and value as array of members? there is nothing to sort here. just keep adding elements to this array inside dictionary in the order you find them. – adev Jul 06 '17 at 22:01
  • You need to [edit] your question with the desired results given your input. It's not clear what you expect here. – rmaddy Jul 06 '17 at 22:25
  • I need to unload the sorted array into a UITableView with multiple sections. There'd be a UITableView with 26 sections (# of letters in the alphabet) with n number of rows for each section... @rmaddy – saikik Jul 06 '17 at 22:34
  • 1
    @JoshuaChoi As I said, [edit] your question. Don't make people read all of the comments to understand your question. – rmaddy Jul 06 '17 at 22:36
  • @rmaddy edited! – saikik Jul 06 '17 at 22:39
  • Not quite a duplicate but see https://stackoverflow.com/questions/36521556/swift-how-to-make-alphabetically-section-headers-in-table-view-with-a-mutable-d for what you need. – rmaddy Jul 06 '17 at 22:40
  • so did you try the one mentioned in my comment then? Looks like that is what you need. – adev Jul 06 '17 at 22:40
  • @adev The goal is not to simply sort the array. The goal is to split the array into a set of arrays to populated a table view like the Contacts app. – rmaddy Jul 06 '17 at 22:41
  • I can't user your code @adev. It causes Xcode to return a signal bat 11 error lol – saikik Jul 06 '17 at 22:48
  • @JoshuaChoi, did you try my code in the answer? I dont see any such error. – adev Jul 06 '17 at 23:45
  • @rmaddy, I have added your suggestion as a code in Solution 2 in my answer. – adev Jul 07 '17 at 01:58

2 Answers2

1

Solution 1:

Posting my comment as answer with code. Check if this will work for you. You can use sections from first array printed below(store dict.keys.sorted() into some array) and use that as key for displaying cells in that section.

let abcNameObjectArray = ["Amy", "Bernadette", "Brian", "Chris", "Candice", ""]

let dict = abcNameObjectArray.reduce([String: [String]]()) { (key, value) -> [String: [String]] in
    var key = key
    if let first = value.characters.first {
        let prefix = String(describing: first).lowercased()
        var array = key[prefix]
        
        if array == nil {
            array = []
        }
        array!.append(value)
        key[prefix] = array!.sorted()
    }
    return key
}

print(dict.keys.sorted())
print(dict)

Output:

["a", "b", "c"]

["b": ["Bernadette", "Brian"], "a": ["Amy"], "c": ["Candice", "Chris"]]

Solution 2:

Here is one more solution using array of arrays as suggested by rmaddy

let abcNameObjectArray = ["Amy", "Bernadette", "Brian", "Chris", "Candice", ""]
let unicodeScalarA = UnicodeScalar("a")!

var arrayOfArrays = abcNameObjectArray.reduce([[String]]()) { (output, value) -> [[String]] in
    var output = output
    
    if output.count < 26 {
        output = (1..<27).map{ _ in return []}
    }
    
    if let first = value.characters.first {
        let prefix = String(describing: first).lowercased()
        let prefixIndex = Int(UnicodeScalar(prefix)!.value - unicodeScalarA.value)
        var array = output[prefixIndex]
        array.append(value)
        output[prefixIndex] = array.sorted()
    }
    return output
}

arrayOfArrays = arrayOfArrays.filter { $0.count > 0}
print(arrayOfArrays)

Output:

[["Amy"], ["Bernadette", "Brian"], ["Chris", "Candice"]]

You can have number of sections as count of this array and each member will give number of cells required in that section.

Community
  • 1
  • 1
adev
  • 2,052
  • 1
  • 21
  • 33
  • Sorry I haven't been able to respond, but I think your solution is the best one yet so I'm going to mark this as the answer. But, is there a way to modify the output to return an array of Objects, as opposed to an array of Strings? – saikik Aug 01 '17 at 21:12
  • Thanks for accepting. Yes, you can return anything. This is just an example. You can modify reduce method as shown above and make it to return array of objects. Please feel free to try it out and then ask question if you are unable to do it. We can help you. – adev Aug 01 '17 at 21:53
0

A Set is not a good choice for the data source for a table view. A Dictionary is not a good choice either. Both are unordered collections, and a table view's data source needs a definite order. I suggest breaking the source array into an array of arrays, where each sub-array contains all the words that begin with a specific letter, sorted in alphabetical order. The code do that might look like this:

import UIKit

let strings = ["arf",
               "woofing",
               "flounder",
               "Manbaby",
               "Dogs",
               "baseballs",
               "Suet",
               "Cards",
               "Tiny-fingered, cheeto-faced, ferret-wearing sh*tgibbon",
               "apple",
               "Acorn",
               "Achy-breaky heart",
               "songbirds",
               "Aces"]

let aValue = "a".unicodeScalars.first!.value
let zValue = "z".unicodeScalars.first!.value


var result = [[String]]()
for value in aValue...zValue {
  let aChar = Character(UnicodeScalar(value)!)
  let thisArray = strings
    .filter{$0.lowercased().characters.first! == aChar}
    .sorted{$0.caseInsensitiveCompare($1) == .orderedAscending}
  if !thisArray.isEmpty {
    result.append(thisArray)
  }
}

result.forEach{print($0)}

That yields the following output:

["Aces", "Achy-breaky heart", "Acorn", "apple", "arf"]
["baseballs"]
["Cards"]
["Dogs"]
["flounder"]
["Manbaby"]
["songbirds", "Suet"]
["Tiny-fingered, cheeto-faced, ferret-wearing sh*tgibbon"]
["woofing"]

If you want an array with an entry for every letter, even when there are no words beginning with that letter, get rid of the if !thisArray.isEmpty test.

Duncan C
  • 128,072
  • 22
  • 173
  • 272