How to sort an array of String triplets

I'm trying to convert some code from Java. I have an array of Triplets. The Triplets are defined as:

class Triplet<T> {
	var p0: T
	var p1: T
	var p2: T
	
	init(p0: T, p1: T, p2: T) {
		self.p0 = p0
		self.p1 = p1
		self.p2 = p2
	}
}

This is the start of my func:

	func sortPluginTripletsByManufacturerAndPlugin(plugins: [Triplet<String>]) ->  [Triplet<String>] {
		var pluginsMut = plugins
		pluginsMut.sort { 
			(a: Triplet<String>, b: Triplet<String>) in
				if a.p0 < b.p0 {
					return -1    // ERROR OCCURRING HERE
				}

In each Triplet, p0 represents the Manufacturer and p2 represents the plugin name, and, as the name of the func suggests, I would like to end up with a new array sorted first by Manufacturer (p0) and then the Plugin Name (p2).

My problem, at the moment, is that I'm getting build errors occurring where shown above. The error message reads: Integer literal value '1' cannot be used as a boolean; did you mean 'true'?

This is my first day of Swift, and I cannot get my head around how to sort an array of Triplets. Can anyone help?

this is probably what you want:

func sortPluginTripletsByManufacturerAndPlugin(
    plugins:consuming [Triplet<String>]) -> [Triplet<String>]
{
    plugins.sort
    {
        ($0.p0, $0.p2) < ($1.p0, $1.p2)
    }
    return plugins
}

i would also make Triplet<T> a struct and not a class, unless you have a different reason for it to be a class.

the predicate of MutableCollection.sort(by:) should return a boolean indicating if two elements are in ascending order, it should not return a sort key.


notes:

  1. you should use consuming and sort instead of sorted, until someone fixes this bug

  2. swift tuples always compare in lexicographical order, so you can just use ($0.p0, $0.p2) < ($1.p0, $1.p2) as the predicate. you cannot return a sort key because tuples are not Comparable.

3 Likes

This should do:

func sortPluginTripletsByManufacturerAndPlugin(plugins: [Triplet<String>]) -> [Triplet<String>] {
    plugins.sorted { lhs, rhs in
        if lhs.p0 == rhs.p0 {
            lhs.p2 < rhs.p2
        } else {
            lhs.p0 < rhs.p0
        }
    }
}

BTW, if you don't have to use class here for other reason - change it struct. And you may as well remove the initialiser, as struct will have a built-in member wise initialiser out of the box.

1 Like

Nice! Two birds with one stone.

1 Like

Thanks for the replies @taylorswift and @tera! That gets me a bit further :wink:

1 Like

Could you sum up, what bad would happen if here we use "sorted" without "consuming"?

Nothing major, it'd just trigger copy-on-write even if the caller no longer needs the array. With consuming, the copy is only performed if the caller needs the original array after the call. This can have a performance impact for larger arrays.

3 Likes

Are you sure that COW will happen here with "sorted" (not "sort")? Where is "write"? Sorted doesn't (and can't) change the original array.

Look at the assembly in the linked post -- it actually calls sort in both versions, there's just additional refcounting traffic when the source says sorted instead.

4 Likes

Pedagogically, this is irrelevant to the OP's query, particularly as it's their first day of using Swift. They should not be attempting to use ownership features, and they should not be trying to optimize refcounting traffic.

10 Likes

the ownership features (and noncopyables) are the future, and refcounting traffic is one of the most well-studied performance concepts in swift, so i’m not sure why you would discount them as “irrelevant”.

No, they’re right, you don’t thrust this level of performance optimization on a beginner. It makes sense to us because we are so familiar with the language and the concepts, but for someone who’s learning to program, they shouldn’t worry about it.

8 Likes

that’s fair, it probably doesn’t qualify as a day one thing. i figured since he was doing

it’d be a good time to mention consuming.

Think the Swifty way is just to declare you element Comparable and that will do the trick.

So in short, it would look like this:

struct Triplet<T>: Comparable where T: Comparable {
  var p0: T
  var p1: T
  var p2: T
  
  init(p0: T, p1: T, p2: T) {
    self.p0 = p0
    self.p1 = p1
    self.p2 = p2
  }
  
  static func < (lhs: Triplet<T>, rhs: Triplet<T>) -> Bool {
    (lhs.p0, lhs.p2) < (rhs.p0, rhs.p2)
  }
}

let newPlugins = plugins.sorted(by: <)

Of course you always can sort by custom condition:

let alsoNewPlugins = plugins.sorted(by: { $0.p1 < $1.p1 })

And then if there are multiple conditions you can just define functions and pass them:

func sortPluginTripletsByManufacturerAndPlugin<T: Comparable>(lhs: Triplet<T>, rhs: Triplet<T>) -> Bool {
  (lhs.p0, lhs.p2) < (rhs.p0, rhs.p2)
}

func sortPluginTripletsBySomeOtherCondition<T: Comparable>(lhs: Triplet<T>, rhs: Triplet<T>) -> Bool {
  (lhs.p0, lhs.p1) < (rhs.p0, rhs.p1)
}

let anotherOnePlugins = plugins.sorted(by: sortPluginTripletsByManufacturerAndPlugin)
let andOnotherOnePlugins = plugins.sorted(by: sortPluginTripletsBySomeOtherCondition)

Also as noted here already it's better to start with struct s always and then when you need to use classes.