Sorting Struct Array by Elements in Another Array (Swift)

Ok so I'm wanting to sort an array based off of another array. Ok so that probably sounds pretty confusing so let me explain.


Alright so let's make an example of an app about grocery shopping. Ok so I have an array like this:


    var fruitsArray = [String:Int]()


Let's say that the values of the array are



    [apple:32, banana:45, grape:7, strawberry:23]


Ok so first off, I sort this array by the `Int`, descendingly, which gives us


    [banana:45 apple:32, strawberry:23, grape:7]

Ok great. But now I want an array of shopping lists and each element of that array contains an array of food. So let's make a `Struct`...


    struct List {
        var name:String = ""
        var items:[String] = [""]
       
        init(name: String, items: [String) {
            self.name = name
            self.items = items
        }
    }


and then an array of `List`...


    var shoppingLists = [List]()


finally, let's add some elements to `shoppingLists`...


    [List(name: List 1, items: [banana, pizza, watermelon, apple]),
    List(name: List 2, items: [cookie, water, grape, apple]),
    List(name: List 3, items: [apple, strawberry, banana, cheese]),
    List(name: List 4, items: [apple, strawberry, grape, watermelon])]


Ok so this is where it gets kinda difficult. I want to sort `shoppingLists` by looking at the `items` array in `shoppingLists` and comparing them with `fruitsArray`. Let me show you what I want the final product would be and then I'll explain why.


    sortedShoppingList =
    [List(name: List 3, items: [apple, strawberry, banana, cheese]),
    List(name: List 4, items: [apple, strawberry, grape, watermelon]),
    List(name: List 1, items: [banana, pizza, watermelon, apple]),
    List(name: List 2, items: [cookie, water, grape, apple])]


Ok so why in the world is it sorted like this? Let's take it element by element. So List 3 is at the top because it contains 3 elements from fruitsArray. Now you might be saying list 4 also contains 3 elements from `fruitsArray` and yes that's true, but list 3 contains elements from `fruitsArray` that are higher in "ranking". Both list 3 and 4 contain [apple, strawberry] but list 3 contains `banana` while list 4 contains `grape`. `Banana` "outranksks" `grape` therefore making list 3 first. So moving on, List 1 is after list 4 because it contains [apple, banana] which is higher in "rank" than the [apple, grape] in list 2.


Alright well, I really hope that wasn't confusing. If you have any questions or need me to clarify, please ask! Thanks so much for reading this and I hope you can help me out.

Replies

So, you have to write a. ompletion closure for sorted() function on the first array that implements yopur logic : what makes an item before another item.


If you are not familiar, you'll get details in Swift book:


Sy,ntax is like (for an array of Int, in your case, array of List, so you have to write the < func

numbers.sorted {(n1:Int, n2:Int) -> Bool in return n1 < n2}

Let's say that the values of the array are …

First up, you’re mixing up your terminology.

fruitsArray
is not an array, it’s a dictionary, one that maps
String
keys to
Int
values. For the rest of my response I’m going to call this
fruitsDict

Beyond that, what I’d do here is create a function that, given a

List
and
fruitsDict
, returns a ranking value. I’m not entirely sure I understand what ranking you’re looking for here, but I think you want it to be a tuple, where the first item is a count of the number of matches and the second item is the sum of the matched values. So, something like this:
func rank(for list: List, given weights: [String:Int]) -> (Int, Int) {
    var matchCount: Int = 0
    var weightSum: Int = 0
    for item in list.items {
        if let weight = weights[item] {
            matchCount += 1
            weightSum += weight
        }
    }
    return (matchCount, weightSum)
}

You can then compare ranks using the standard

<
operator:
let fruitsDict = ["apple": 32, "banana": 45, "grape": 7, "strawberry": 23]

let list1 = List(name: "List 1", items: ["banana", "pizza", "watermelon", "apple"])
print(rank(for: list1, given: fruitsDict))     // -> (2, 77)
let list3 = List(name: "List 3", items: ["apple", "strawberry", "banana", "cheese"])
print(rank(for: list3, given: fruitsDict))     // -> (3, 100)
let list4 = List(name: "List 4", items: ["apple", "strawberry", "grape", "watermelon"])
print(rank(for: list4, given: fruitsDict))     // -> (3, 62)
print(rank(for: list1, given: fruitsDict) < rank(for: list3, given: fruitsDict))  // -> true
print(rank(for: list3, given: fruitsDict) < rank(for: list4, given: fruitsDict))  // -> false

And from there sort using a closure, as outlined by Claude31.

There’s a bunch of things to be concern about here:

  • When posting code it really helps to post exactly the code you’re testing with. Your code snippets have lots of minor problems (missing quotes, unbalanced brackets, and so on) and that makes it harder for someone reading your question to be sure they’ve understood you properly.

  • You’re using strings to identify your fruit. That might make sense but in most situations it’s better to use a custom type for this sort of thing. That way you’ll be able to compare apples and oranges, which are both fruit, but not, say, apples and user names.

  • The code I’ve shown above, and any sorting code you base on it, will end up calling

    rank(for:given:)
    redundantly. That’s fine if your data structures are small. If you are working with thousands of items then it would make sense to remember the results of such calls so that you only call that function once for each list.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"