bladet
bladet

Reputation: 83

Sorting a list in Groovy in an unusual way

I have a list of, let's say [Cat, Dog, Cow, Horse], that I want to be sorted in the following way

Any suggestions how this could be done in Groovy?

Upvotes: 8

Views: 2216

Answers (6)

epidemian
epidemian

Reputation: 19219

This question is pretty old, but today i found that Groovy has a, rather undocumented, OrderBy comparator that can be used in this case:

def highPriority = ['Cow', 'Cat']
def list = ['Armadillo', 'Cat', 'Dog', 'Cow', 'Zebra', 'Horse', 'Cow']

def sorted = list.sort new OrderBy([{ -highPriority.indexOf(it) }, { it }])

assert sorted == ['Cat', 'Cow', 'Cow', 'Armadillo', 'Dog', 'Horse', 'Zebra']

The OrderBy comparator first compares the animals using their index in the highPriority list negated (therefore the animals that are not high priority (i.e. index -1) are moved to the back of the list) and if the indexes are equal it compares them by the identity function {it}, which, as animals are strings, sorts them alphabetically.

Upvotes: 0

ataylor
ataylor

Reputation: 66059

Here's another alternative that feels simpler to me:

// smaller values get sorted first
def priority(animal) {
    animal in ['Cat', 'Cow'] ? 0 : 1
}

def list = [ 'Armadillo', 'Cat', 'Dog', 'Cow', 'Zebra', 'Horse', 'Cow' ]

def sorted = list.sort{ a, b -> priority(a) <=> priority(b) ?: a <=> b }

assert sorted == ['Cat', 'Cow', 'Cow', 'Armadillo', 'Dog', 'Horse', 'Zebra']

Upvotes: 2

epidemian
epidemian

Reputation: 19219

Inspired on tomas' answer:

def highPriority = [ 'Cat', 'Cow' ]
def list = [ 'Armadillo', 'Cat', 'Dog', 'Cow', 'Zebra', 'Horse', 'Cow' ]

// Group animals by priority.
def groups = list.groupBy { it in highPriority ? it : 'rest' }
// High priority animals are sorted by priority and the rest alphabetically.
def sorted = highPriority.collectMany { groups[it] } + groups['rest'].sort()

assert sorted == ['Cat', 'Cow', 'Cow', 'Armadillo', 'Dog', 'Horse', 'Zebra']

The groups variable is something like [rest:[Armadillo, Dog, Zebra, Horse], Cat:[Cat], Cow:[Cow, Cow]].

Another, arguably less robust, solution might be:

def sorted = list.sort(false) { 
    def priority = highPriority.indexOf(it)
    if (priority == -1) priority = highPriority.size()
    // Sort first by priority and then by the value itself
    "$priority$it"
}

It is less robust in the sense that it sorts by strings like "2Armadillo", "0Cat", etc, and won't work if you have 9 or more high priority animals (because "10Alpaca" < "9Eel". It would be cool if Groovy provided some sort of comparable tuple type, like Python's tuples, so instead of returning "$priority$it" as the comparable key, one could return the tuple (priority, it).

Upvotes: 0

Arturo Herrero
Arturo Herrero

Reputation: 13112

If you don't have duplicate elements, you can try this:

def highPriority = [ 'Cat', 'Cow' ]
def list = [ 'Armadillo', 'Dog', 'Cow', 'Zebra', 'Horse', 'Cat' ]
highPriority + list.minus(highPriority).sort()

Upvotes: 1

Tomas Lin
Tomas Lin

Reputation: 3532

Tim's answer is pretty clever. I'm personally more a fan of just using list operations as the code it generates is slightly easier to read.

def highPriority = [ 'Cat', 'Cow' ]

def list = [ 'Armadillo', 'Dog', 'Cow', 'Zebra', 'Horse', 'Cow', 'Cat' ]

def remainder = ( list - highPriority ).sort()

list.retainAll( highPriority )

list.sort{ highPriority.indexOf( it ) } + remainder

That will give you Cow twice. If you don't want duplicates, using intersect is fairly simple.

def highPriority = [ 'Cat', 'Cow' ]

def list = [ 'Armadillo', 'Dog', 'Cow', 'Zebra', 'Horse', 'Cow', 'Cat' ]

list.intersect( highPriority ).sort{ highPriority.indexOf( it ) } + ( list - highPriority ).sort()

Upvotes: 7

tim_yates
tim_yates

Reputation: 171074

This should do it:

// Define our input list
def list = [ 'Armadillo', 'Cat', 'Dog', 'Cow', 'Zebra', 'Horse', 'Cow' ]

// Define a closure that will do the sorting
def sorter = { String a, String b, List prefixes=[ 'Cat', 'Cow' ] ->
  // Get the index into order for a and b
  // if not found, set to being Integer.MAX_VALUE
  def (aidx,bidx) = [a,b].collect { prefixes.indexOf it }.collect {
    it == -1 ? Integer.MAX_VALUE : it
  }
  // Compare the two indexes.
  // If they are the same, compare alphabetically
  aidx <=> bidx ?: a <=> b
}

// Create a new list by sorting using our closure
def sorted = list.sort false, sorter

// Print it out
println sorted

That prints:

[Cat, Cow, Cow, Armadillo, Dog, Horse, Zebra]

I've commented it to try and explain each step it takes. By adding the default prefix items as an optional parameter on the sorter closure, it means we can do stuff like this to change the default:

// Use Dog, Zebra, Cow as our prefix items
def dzc = list.sort false, sorter.rcurry( [ 'Dog', 'Zebra', 'Cow' ] )
println dzc

Which then prints the list sorted as:

[Dog, Zebra, Cow, Cow, Armadillo, Cat, Horse]

Upvotes: 6

Related Questions