List Sorting with Multiple Attributes and Mixed Order

List sorting with multiple attributes and mixed order

If your attributes are numeric, you have this.

def mixed_order( a ):
return ( a.attribute1, -a.attribute2 )

someList.sort( key=mixed_order )

If your attributes includes strings or other more complex objects, you have some choices.

The .sort() method is stable: you can do multiple passes. This is perhaps the simplest. It's also remarkably fast.

def key1( a ): return a.attribute1
def key2( a ): return a.attribute2

someList.sort( key=key2, reverse=True )
someList.sort( key=key1 )

If this is the only sort, you can define your own special-purpose comparison operators. Minimally, you need __eq__ and __lt__. The other four can be derived from these two by simple logic.

Sorting by multiple columns where each column is in different order

You can reverse the sort of one of the columns if either of the columns is numeric, or can be expressed as numbers as well. You can then simply negate the numeric column to reverse the sort for that column.

If the t_l[-1] column is numeric, use:

sorted(t_l, key=lambda i: (-i[-1], i[1]))

(using different names for the input list and the lambda argument reduces confusion)

If that column is not numeric but the other one is, you can still use the same trick, but need to reverse the whole sort:

sorted(t_l, key=lambda i: (i[-1], -i[1]), reverse=True)

Single-character columns can be made numeric by using the ord() function; using ord() will result in the same sorting order, but since it is numeric you can now reverse it:

sorted(t_l, key=lambda i: (-ord(i[-1]), i[1]))

sort a python list based on multiple criteria

If the key returns a tuple, sorted will consider them in order when sorting:

In [3]: sorted(my_list, key=lambda k: (k['type'], k['val']), reverse=True)
Out[3]:
[{'type': 2, 'val': 6},
{'type': 1, 'val': 2},
{'type': 0, 'val': 9},
{'type': 0, 'val': 5}]

If you want the indices, just throw an enumerate in there as well:

In [7]: sorted(enumerate(my_list), key=lambda k: (k[1]['type'], k[1]['val']), reverse=True)
Out[7]:
[(1, {'type': 2, 'val': 6}),
(2, {'type': 1, 'val': 2}),
(3, {'type': 0, 'val': 9}),
(0, {'type': 0, 'val': 5})]

In [8]: [k for k, v in sorted(enumerate(my_list), key=lambda k: (k[1]['type'], k[1]['val']), reverse=True)]
Out[8]: [1, 2, 3, 0]

How to sort a list with two keys but one in reverse order?

Two keys will be used when we need to sort a list with two constraints: one in ascending order and the other in descending, in the same list or any

In your example,

sortedList = sorted(myList, key = lambda y: (y[0].lower(), y[1]))

you can sort entire list only in one order.

You can try these and check what's happening:

sortedList = sorted(myList, key = lambda y: (y[0].lower(), -y[1]))
sortedList = sorted(myList, key = lambda y: (-y[0].lower(), y[1]))
sortedList = sorted(myList, key = lambda y: (-y[0].lower(), -y[1]))

Sort by multiple keys using different orderings

There is no built-in way to handle this. For the general case, you must sort twice: first by the secondary sort, then by the primary sort. As @Mark Ransom mentioned in his comment, in many cases the variables are numeric, and so you can use the negative value to flip the ordering.

If you know the type of the variable you're trying to sort on and how to work with it, you could also write a key function that returns a decreasing value for increasing keys. See this thread for an example for strings. (Basically, you take the negative of the ASCII numerical value of the characters.)

In Python 2, you could also use a cmp function instead of a key, but this will likely make the sort slower. Whether it will make it too slow depends on how big and unsorted the list is. In Python 3, the cmp argument is gone, but as @Mark Ransom notes you could use cmp_to_key.

Collections.sort with multiple fields

Do you see anything wrong with the code?

Yes. Why are you adding the three fields together before you compare them?

I would probably do something like this: (assuming the fields are in the order you wish to sort them in)

@Override public int compare(final Report record1, final Report record2) {
int c;
c = record1.getReportKey().compareTo(record2.getReportKey());
if (c == 0)
c = record1.getStudentNumber().compareTo(record2.getStudentNumber());
if (c == 0)
c = record1.getSchool().compareTo(record2.getSchool());
return c;
}

Sort mixed list using subtype specific behaviour Kotlin

Here is something that might serve your needs.

We supply separate comparator for all subtypes, and simply group all by type and sort each group with it's own comparator, after that simply sort groups by their class name and flatten elements from each group in consecutive manner.

Here is code example demonstrating it.

  • added utility function sortedUsing to perform this sorting
  • added an utility type TypeComparator just to have more convenient way to pass type safe Class + Comparator
fun <T : Any> List<T>.sortedUsing(
vararg typeComparators: TypeComparator<out T>
): List<T> {

@Suppress("UNCHECKED_CAST")
fun <R : T> TypeComparator<R>.sort(list: List<T>): List<R> = (list as List<R>).sortedWith(comparator)

val comparators = typeComparators.associateBy { it.type }
return groupBy { it::class }
.mapValues { (klass, list) ->
val typeComparator = comparators[klass]
?: typeComparators.firstOrNull { klass.isSubclassOf(it.type) }
?: list.firstOrNull()?.tryMakeComparator()
?: throw IllegalArgumentException("Missing comparator for type: $klass")
typeComparator.sort(list)
}
.map { it }
.sortedBy { it.key.qualifiedName }
.flatMap { it.value }
}

@Suppress("UNCHECKED_CAST")
private fun <T : Any> T.tryMakeComparator(): TypeComparator<T>? {
if (this !is Comparable<*>) {
return null
}
return TypeComparator(this::class as KClass<T>, Comparator { o1, o2 ->
val c1 = o1 as Comparable<Comparable<T>>
val c2 = o2 as Comparable<T>
c1.compareTo(c2)
})
}

data class TypeComparator<T : Any>(
val type: KClass<T>,
val comparator: Comparator<T>
)

You could also supply comparator for type if you'd like to, because default in snippet above is to order typed groups by class full name.

With some better way of accumulating elements of some type, you could get rid of unchecked list cast.

Usage example:

open class Base

data class SubtypeA(
val foo: Int
) : Base()

data class SubtypeB(
val bar1: String,
val bar2: String
) : Base()

fun main() {
val list = listOf(
SubtypeA(5),
SubtypeB("foo", "x"),
SubtypeA(42),
SubtypeA(2),
SubtypeB("bar", "y"),
SubtypeB("bar", "x")
)
val sorted = list.sortedUsing(
TypeComparator(SubtypeA::class, Comparator.comparing { a: SubtypeA -> a.foo }),
TypeComparator(SubtypeB::class, Comparator.comparing { b: SubtypeB -> b.bar1 }.thenComparing { b: SubtypeB -> b.bar2 })
)
sorted.forEach { println(it) }
// prints:
// SubtypeA(foo=2)
// SubtypeA(foo=5)
// SubtypeA(foo=42)
// SubtypeB(bar1=bar, bar2=x)
// SubtypeB(bar1=bar, bar2=y)
// SubtypeB(bar1=foo, bar2=x)

}



Related Topics



Leave a reply



Submit