1

Considering we have the following:

typealias Fruit = String
typealias Count = Int

struct Smoothie {
   let uuid: String
   let fruits: [Fruit: Count]
}

let smoothies: [Smoothie] = ...

Given a conditions array set by the user:

let conditions: [Fruit] = ...

The lowest the index in the conditions array, the most important we consider it is for sorting.

I would like to sort the smoothies array so that the smoothies that contain the highest number of fruits (Count) from the conditions array, appear first.

If a fruit doesn't appear in the fruits dictionary, we consider its count as 0.

For example:

let fruitsA = ["Apple": 3, "Mango": 4, "Pear": 8, "Cherry": 1, "Banana": 2]
let smoothieA = Smoothie(uuid: "smoothie_a", fruits: fruitsA)

let fruitsB = ["Apple": 10, "Mango": 9, "Grapes": 8, "Cherry": 9]
let smoothieB = Smoothie(uuid: "smoothie_b", fruits: fruitsB)

let fruitsC = ["Apple": 23, "Kiwi": 4, "Pear": 1, "Cherry": 17, "Banana": 8]
let smoothieC = Smoothie(uuid: "smoothie_c", fruits: fruitsC)

let fruitsD = ["Apple": 1, "Orange": 6, "Pear": 8]
let smoothieD = Smoothie(uuid: "smoothie_d", fruits: fruitsD)

let conditions: [Fruit] = ["Apple", "Banana", "Cherry"]

Should return the following:

let sortedSmoothies = [smoothieC, smoothieB, smoothieA, smoothieD]

Because we're first sorting by Apple, then by Banana, then by Cherry count.

If I knew it would always be about Apple, Banana & Cherry, I could do the following and it would just work:

let sortedSmoothies = smoothies.sorted {
     if $0.fruits["Apple"] != $1.fruits["Apple"] {
         return $0.fruits["Apple"] > $1.fruits["Apple"]
     } else if $0.fruits["Banana"] != $1.fruits["Banana"] {
         return $0.fruits["Banana"] > $1.fruits["Banana"]
     } else {
         return $0.fruits["Cherry"] > $1.fruits["Cherry"]
     }
}

But in my case I do not know what are the fruits (and how many of them) that the user is going to select for filtering in the conditions array.

Does anyone has an idea how to sort this out please?

Thank you!

2 Answers2

1
let sorted = [smoothieA, smoothieB, smoothieC, smoothieD].sorted { (a, b) in
    for condition in conditions {
        let aAmount = a.fruits[condition] ?? 0
        let bAmount = b.fruits[condition] ?? 0
        if aAmount == bAmount {
            continue
        }
        return aAmount > bAmount
    }
    return true
}

Keep in mind that with your sample data, since "Apple" doesn't have any smoothies with equal values, any test will return that same order. But, you could test with some more interesting numbers that have smoothies that have equal numbers of fruits:

let fruitsA = ["Apple": 10, "Mango": 4, "Pear": 8, "Cherry": 20, "Banana": 2]
let smoothieA = Smoothie(uuid: "smoothie_a", fruits: fruitsA)

let fruitsB = ["Apple": 10, "Mango": 9, "Grapes": 8, "Cherry": 9, "Banana": 2]
let smoothieB = Smoothie(uuid: "smoothie_b", fruits: fruitsB)

let fruitsC = ["Apple": 23, "Kiwi": 4, "Pear": 1, "Cherry": 17, "Banana": 8]
let smoothieC = Smoothie(uuid: "smoothie_c", fruits: fruitsC)

let fruitsD = ["Apple": 1, "Orange": 6, "Pear": 8]
let smoothieD = Smoothie(uuid: "smoothie_d", fruits: fruitsD)
jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • You should extract the sort function and write test cases for it. It’s current incorrect, it’ll return `true` when applied to two equal instances. – Alexander Jan 30 '21 at 03:53
  • I must be missing something, but what would we expect to see with two equal instances in this case? – jnpdx Jan 30 '21 at 04:55
  • Hi thank you so much for your answer its very helpful. When two instances of Apple, it should compare the 2ndd element of the conditions array, so Banana in my example. If two instances of Banana are equal, then it should sort using Cherry, then because there is no more elements in conditions array, it can just return false with no ordering. Do you have any ideas how to make it work with your example? –  Jan 30 '21 at 06:22
  • As far as I can tell, that's what mine does. – jnpdx Jan 30 '21 at 06:28
  • 1
    In my example (with my inputs), C is first, because it has 23 apples. Then, A and C are tied for numbers of bananas, so cherries are compared, which A wins. So, the result is C, A, B, D. Change the number of bananas to 3 on B and the order changes to C, B, A, D. As far as I can tell, this is what you're describing. – jnpdx Jan 30 '21 at 06:31
  • Thank you! I will try your code as soon as I can! –  Jan 30 '21 at 06:34
  • @jnpdx Not just "in this case", but in general, you're implementing a `<` operator. `a < a` should be `false`, for all values `a`. – Alexander Jan 30 '21 at 17:24
  • Okay, so basically this (https://stackoverflow.com/questions/45929474/why-must-stdsort-compare-function-return-false-when-arguments-are-equal)? If I change the last `return` to `false`, although it wouldn't change the outcome of any non-equal values, it would satisfy it for equal ones, correct? – jnpdx Jan 30 '21 at 19:20
-1

You could create an array of the fruit arrays - String[][] which you might call FruitsArr. You would also create an array or list of fruits that can hold as many fruits as the user puts in.

This is all in loose terms, because the swift syntax is unfamiliar. It might just confuse you to see too much Java syntax, sorry. The logic is universal, though.

for (int i = 0; i < fruits.length; i++){ 
/*fruits is our imaginary array of user-input fruits. This is sorting the arrays one by one by each fruit in fruits*/
String[] mostFruit; //fill it with zeroes or something.
    for (int j = 0; j < FruitsArr.length; j++){
        String[] thisFruit = FruitsArr[j]l
        if (thisFruit[i] > mostFruit[i]){mostFruit = thisFruit;}
     }
}

That checks the question: is the value at index i in thisFruit greater than the value in index i in mostFruit? If so, mostFruit becomes thisFruit. The first thisFruit that has at least 1 of the fruit becomes mostFruit, because 1 > 0. Then the comparison becomes more simple.

Your loop will continue through fruit 1, fruit 2, fruit 3, and the rest that the user puts in. It will check each fruit against every array:

Fruit 1:

Does thisFruit have more than 0 of fruit1?

(next)

Does thisFruit have more of this fruit than the current mostFruit?

(next)

Does thisFruit have more of this fruit than the current mostFruit?

Thus it continues through the rest of the fruit arrays and the rest of the fruits.

Cole Henrich
  • 155
  • 3
  • 17