smeeb smeeb - 4 months ago 14
Groovy Question

Grouping and sorting nested collections in Groovy

I have the following POGO:

class Widget {
String name
Integer order
// lots of other fields

Widget(Integer order) {
super()
this.order = order
}
}


I have a
List<Widget>
and am trying to put them into a
List<Set<Widget>>
where they are grouped by the
order
field. So if I have 3 widgets with the following respective orders:
{ 3, 1, 2 }
then I would have an outer
List
of size 3 and each element in that list would be a
Set
of size 1. I could manually produce this in code via:

Widget w1 = new Widget(3)
Widget w2 = new Widget(1)
Widget w3 = new Widget(2)

// Now sort them by order manually:
Set<Widget> firstOrderWidgets = []
firstOrderWidgets << w2 // order = 1

Set<Widget> secondOrderWidgets = []
secondOrderWidgets << w3 // order = 2

Set<Widget> thirdOrderWidgets = []
thirdOrderWidgets << w1 // order = 3

List<Set<Widget>> sortedCorrectly = []
sortedCorrectly << firstOrderWidgets // All widgets w/ order = 1
sortedCorrectly << secondOrderWidgets // All widgets w/ order = 2
sortedCorrectly << thirdOrderWidgets // All widgets w/ order = 3


So the idea here is that the "unsorted/ungrouped"
List<Widget>
could be quite large, and many widgets could contain the same order.
We want to group all widgets with the same order (order = 1, order = 2, etc.) into the same inner
Set
and then add those sets to the outer
List
in ascending order. So if we were to add a 4th widget to the example above:

Widget w4 = new Widget(2)


This widget belongs with the other 2nd order widget:

Set<Widget> secondOrderWidgets = []
secondOrderWidgets << w3 // order = 2
secondOrderWidgets << w4 // order = 2


So I'm trying to write a method that take
List<Widget>
as input, groups them by
order
and then sorts those groups/sets in ascending order. The
order
field is guaranteed to be non-null, but could be any valid positive (1+) integer. My best attempt is causing all sorts of runtime/dynamic exceptions:

List<Set<Widget>> sortWidgets(List<Widget> toSort) {
def groupedByOrder = toSort.groupBy({ widget -> widget.order })

groupedByOrder = groupedByOrder.sort()

List<Set<Widget>> sortedList = []
groupedByOrder.each { order, widgets ->
sortedList << new HashSet(widgets)
}

sortedList
}


Can anyone spot where I'm going awry?

Answer

I couldn't manage to reproduce your error. It can happen due to how equals and hashCode are implemented in your Widget class. I managed to get a working example using @Canonical:

@groovy.transform.Canonical
class Widget {
    int order
    String toString() { "Widget(order=$order, ${hashCode()})" }
}

widget = { new Widget(order: it) }

w1 = widget(3)
w2 = widget(1)
w3 = widget(2)
w4 = widget(2)
w5 = widget(2)

allWidgets = [w4, w1, w2, w5, w3]

def sortWidgets(widgets) {
    widgets.sort(false) { it.order }.groupBy { it.order }.values() as List
}

assert sortWidgets(allWidgets) == [
    [w2],
    [w3, w4, w5],
    [w1]
]