1

I have a dictionary from which I need to derive an Array of keys and an Array of values sorted EITHER on the keys or on the values.

My use case is a list of folders. The dictionary holds folder names (keys) and a count of items in the folders (values). I want to sort by key name (A to Z or Z to A) and also by count size (large to small or small to large).

Sorting the keys is easy in Swift. But I'm resorting to iteration to provide the sorted list of values. This does not seem like the Swift way of doing things, but my understanding of map / sort / etc. in Swift is not good enough for me to see a smarter way of doing this.

Can anyone explain a smart and concise Swift way of achieving this goal?

My current code is:

let dictionary = ["Alpha" : 24, "Beta" : 47, "Gamma" : 12, "Delta" : 33]

enum FolderOrder : Int {
    case AlphaAtoZ
    case AlphaZtoA
    case SizeLargeToSmall
    case SizeSmallToLarge
}

func folderList(fromDictionary: [String: Int], orderedBy : FolderOrder = .AlphaAtoZ) -> [String] {
    switch orderedBy {
    case .AlphaAtoZ:
        return fromDictionary.keys.sort() {$0 < $1}

    case .AlphaZtoA:
        return fromDictionary.keys.sort() {$1 < $0}

    case .SizeSmallToLarge:
        return fromDictionary.keys.sort(){fromDictionary[$0] < fromDictionary [$1]}

    case .SizeLargeToSmall:
        return fromDictionary.keys.sort(){fromDictionary[$1] < fromDictionary [$0]}
    }
}

func folderCounts(fromDictionary: [String: Int], orderedBy : FolderOrder = .AlphaAtoZ) -> [Int]
{
    let orderedKeys = folderList(fromDictionary, orderedBy: orderedBy)
    var orderedValues = [Int]()

    for key in orderedKeys {
        orderedValues.append(fromDictionary[key] ?? 0)
    }

    return orderedValues
}

folderList(dictionary, orderedBy: .AlphaAtoZ)
// ["Alpha", "Beta", "Delta", "Gamma"]

folderList(dictionary, orderedBy: .AlphaZtoA)
// ["Gamma", "Delta", "Beta", "Alpha"]

folderList(dictionary, orderedBy: .SizeSmallToLarge)
// ["Gamma", "Alpha", "Delta", "Beta"]

folderList(dictionary, orderedBy: .SizeLargeToSmall)
//["Beta", "Delta", "Alpha", "Gamma"]

folderCounts(dictionary, orderedBy: .AlphaAtoZ)
// [24, 47, 33, 12]

folderCounts(dictionary, orderedBy: .SizeLargeToSmall)
// [47, 33, 24, 12]

Update

Thanks to two helpful answers, but especially @nRewik, I've streamlined my code and improved my understanding of Swift.

Revised code, with comments spelling out what wasn't initially clear to me and so may be helpful to others:

let dictionary = ["Alpha" : 24, "Beta" : 47, "Gamma" : 12, "Delta" : 33]

enum FolderOrder {
    case AlphaAtoZ
    case AlphaZtoA
    case SizeLargeToSmall
    case SizeSmallToLarge
}

func folderListAndCounts(fromDictionary: [String: Int], orderedBy : FolderOrder = .AlphaAtoZ) -> ([String], [Int]) {

    var sortedDictionary : [(String, Int)]

    switch orderedBy {

    // The closure when sort() is applied to a dictionary takes two tuples as parameters
    // where the tuples are of the form (key, value). The first tuple can be accessed as $0.
    // Its key can be accessed as $0.0 and its value as $0.1

    case .AlphaAtoZ:
        sortedDictionary = fromDictionary.sort{ $0.0 < $1.0 } // item(n).key < item(n+1).key
    case .AlphaZtoA:
        sortedDictionary = fromDictionary.sort{ $0.0 > $1.0 } // item(n).key > item(n+1).key
    case .SizeSmallToLarge:
        sortedDictionary = fromDictionary.sort{ $0.1 < $1.1 } // item(n).value < item(n+1).value
    case .SizeLargeToSmall:
        sortedDictionary = fromDictionary.sort{ $0.1 > $1.1 } // item(n).value < item(n+1).value
    }

    // The sorted dictionary has the type: [(String, Int)], i.e. it's an array of tuples.
    // The closure when map is applied to an array of tuples is a tuple. The tuple can be
    // accessed as $0. Its key can be accessed as $0.0 and its value as $0.1

    let sortedKeys = sortedDictionary.map{$0.0}
    let sortedValues = sortedDictionary.map{$0.1}

    // Returns a tuple (arrayOfKeys, arrayOfValues)
    return (sortedKeys, sortedValues)
}

let (keys, counts) = folderListAndCounts(dictionary, orderedBy: .SizeSmallToLarge)

2 Answers 2

5

How's about functional programming style ?

typealias DictSorter = ((String,Int),(String,Int)) -> Bool

let alphaAtoZ: DictSorter = { $0.0 < $1.0 }
let alphaZtoA: DictSorter = { $0.0 > $1.0 }
let sizeSmallToLarge: DictSorter = { $0.1 < $1.1 }
let sizeLargeToSmall: DictSorter = { $0.1 > $1.1 }

// selector
let listSelector: (String,Int)->String = { $0.0 }
let countSelector: (String,Int)->Int = { $0.1 }

// Usage
let dict = ["Alpha" : 24, "Beta" : 47, "Gamma" : 12, "Delta" : 33]

let folderListByAlphaAtoZ = dict.sort(alphaAtoZ).map(listSelector)
let folderListByAlphaZtoA = dict.sort(alphaZtoA).map(listSelector)
let folderListBySizeSmallToLarge = dict.sort(sizeSmallToLarge).map(listSelector)
let folderListBySizeLargeToSmall = dict.sort(sizeLargeToSmall).map(listSelector)

let folderCountByAlphaAtoZ = dict.sort(alphaAtoZ).map(countSelector)
let folderCountByAlphaZtoA = dict.sort(alphaZtoA).map(countSelector)
let folderCountBySizeSmallToLarge = dict.sort(sizeSmallToLarge).map(countSelector)
let folderCountBySizeLargeToSmall = dict.sort(sizeLargeToSmall).map(countSelector)
Sign up to request clarification or add additional context in comments.

1 Comment

This is nice. I've adapted this approach for my code. Thanks.
3

What about this? This changes your code to sort the Dictionary entries then fetch the key or value afterwards.

let dictionary = ["Alpha" : 24, "Beta" : 47, "Gamma" : 12, "Delta" : 33]

enum FolderOrder : Int {
  case AlphaAtoZ
  case AlphaZtoA
  case SizeLargeToSmall
  case SizeSmallToLarge
}

func entryList(fromDictionary: [String: Int], orderedBy : FolderOrder = .AlphaAtoZ) -> [(String, Int)] {
  switch orderedBy {
  case .AlphaAtoZ:
    return fromDictionary.sort { $0.0 < $1.0 }

  case .AlphaZtoA:
    return fromDictionary.sort { $0.0 > $1.0 }

  case .SizeSmallToLarge:
    return fromDictionary.sort { $0.1 < $1.1 }

  case .SizeLargeToSmall:
    return fromDictionary.sort { $0.1 > $1.1 }
  }
}

func folderList(fromDictionary: [String: Int], orderedBy : FolderOrder = .AlphaAtoZ) -> [String] {
  return entryList(fromDictionary, orderedBy: orderedBy).map { $0.0 }
}

func folderCounts(fromDictionary: [String: Int], orderedBy : FolderOrder = .AlphaAtoZ) -> [Int] {
  return entryList(fromDictionary, orderedBy: orderedBy).map { $0.1 }
}

folderList(dictionary, orderedBy: .AlphaAtoZ)
// ["Alpha", "Beta", "Delta", "Gamma"]

folderList(dictionary, orderedBy: .AlphaZtoA)
// ["Gamma", "Delta", "Beta", "Alpha"]

folderList(dictionary, orderedBy: .SizeSmallToLarge)
// ["Gamma", "Alpha", "Delta", "Beta"]

folderList(dictionary, orderedBy: .SizeLargeToSmall)
//["Beta", "Delta", "Alpha", "Gamma"]

folderCounts(dictionary, orderedBy: .AlphaAtoZ)
// [24, 47, 33, 12]

folderCounts(dictionary, orderedBy: .SizeLargeToSmall)
// [47, 33, 24, 12]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.