Details
-
Improvement
-
Status: Closed
-
Minor
-
Resolution: Duplicate
-
None
-
None
-
None
-
n/a
Description
I use groupBy() on lists frequently. Groovy adds findAll(), sort(), and reverse() to Lists; using these with my own groupBy enable any SQL statement for database tables to be simulated. Should it be in the Groovy class libraries? Here's the code I use, and some testing...
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import groovy.lang.Closure;
class MyClassWithExtraMethods{
/**
- Group a List of similarly-keyed Maps by one or more of their fields,
- returning a map keyed on the grouped fields, with a list of attached fields as the value.
- (This simulates SQL's "GROUP BY" clause.)
* - @param theList a List with this method added
- @param genGroupField a Closure that generates the grouped fields
- @param genAttachedField a Closure that generates the fields attached to the grouped ones
- @return a Map keyed on the grouped fields, with a list of attached fields
*/
public static Map groupBy( List theList, Closure genGroupField, Closure genAttachedField ){
Map theGroupedItems = new HashMap();
for( Object eachListItem: theList )Unknown macro: { Object groupField = genGroupField.call(eachListItem); List anItem = (List) theGroupedItems.get( groupField ); if( anItem == null ){ List newArrayList = new ArrayList(); theGroupedItems.put( groupField, newArrayList ); } ArrayList theListToAddTo = (ArrayList) theGroupedItems.get( groupField ); theListToAddTo.add( genAttachedField.call(eachListItem) ); }return theGroupedItems;
}
/**
- Group a List of similarly-keyed Maps by one or more of their fields,
- returning a map keyed on the grouped fields, with a count of records as the value.
- (This simulates SQL's "GROUP BY" clause.)
* - @param theList a List with this method added
- @param genGroupField a Closure that generates the grouped fields
- @return a Map keyed on the grouped fields, with a count of records
*/
public static Map groupBy( List theList, Closure genGroupField ){
Map theGroupedItems = new HashMap();
for( Object eachListItem: theList )Unknown macro: { Object groupField = genGroupField.call(eachListItem); Integer theCount = (Integer) theGroupedItems.get( groupField ); if( theCount == null ){ Integer newCount = new Integer( 1 ); theGroupedItems.put( groupField, newCount ); }else{ Integer newCount = new Integer ( theCount + 1 ); theGroupedItems.put( groupField, newCount ); } }return theGroupedItems;
}
}
-----------------------------------------------------------------------------------
AND THE TESTING...
use( MyClassWithExtraMethods.class ){
items = [
[name:'Hanson', prod:'BSc', addr:'Glendowie', cost: 32 ],
[name:'Smith', prod:'BA', addr:'Pakuranga', cost:130 ],
[name:'Smith', prod:'BEng', addr:'Panmure', cost: 18 ],
[name:'Greene', prod:'BA', addr:'Ellerslie', cost: 3 ],
[name:'Greene', prod:'BEd', addr:'Glendowie', cost:260 ],
[name:'Greene', prod:'BCom', addr:'St Heliers', cost: 60 ],
[name:'Greene', prod:'BCom', addr:'St Heliers', cost: 60 ],
[name:'Greene', prod:'BCom', addr:'Glen Innes', cost: 36 ],
]
// -----------------------------------------------------------------
// >>> >>> >>> tests for single-param version of groupBy <<< <<< <<<
// -----------------------------------------------------------------
//check grouping one field...
assert items.groupBy
== [ BEng:1, BEd:1, BCom:3, BA:2, BSc:1 ]
//check grouping two fields using a map...
assert items.groupBy
== [
[grName:'Greene', grProd:'BEd' ]:1,
[grName:'Greene', grProd:'BCom']:3,
[grName:'Hanson', grProd:'BSc' ]:1,
[grName:'Greene', grProd:'BA' ]:1,
[grName:'Smith', grProd:'BA' ]:1,
[grName:'Smith', grProd:'BEng']:1,
]
//check grouping two fields using a list...
assert items.groupBy
== [
['Greene', 'BEd' ]:1,
['Greene', 'BCom']:3,
['Hanson', 'BSc' ]:1,
['Greene', 'BA' ]:1,
['Smith', 'BA' ]:1,
['Smith', 'BEng']:1,
]
//check using findAll before groupBy, and so simulate 'WHERE' query...
assert items.findAll
.groupBy
{ [grName:it.name, grProd:it.prod] } == [
[grName:'Greene', grProd:'BEd' ]:1,
[grName:'Greene', grProd:'BCom']:3,
[grName:'Hanson', grProd:'BSc' ]:1,
[grName:'Smith', grProd:'BA' ]:1,
]
//check using findAll after groupBy, and so simulate 'HAVING' query...
assert items.groupBy
.findAll
{ it.value > 1 } == [
[grName:'Greene', grProd:'BCom']:3,
]
// -----------------------------------------------------------------
// >>> >>> >>> tests for two-param version of groupBy <<< <<< <<<
// -----------------------------------------------------------------
//check grouping one field, and attaching one field...
assert items.groupBy(
,
{it.addr} ) == [
Smith: ['Pakuranga', 'Panmure'],
Hanson: ['Glendowie'],
Greene: ['Ellerslie', 'Glendowie', 'St Heliers', 'St Heliers', 'Glen Innes'],
]
//check grouping two fields, and attaching the other two fields...
assert items.groupBy(
,
{[attAddr:it.addr, attCost:it.cost]} ) == [
[grName:'Greene', grProd:'BEd']: [[attAddr:'Glendowie', attCost:260] ],
[grName:'Greene', grProd:'BA']: [[attAddr:'Ellerslie', attCost:3] ],
[grName:'Hanson', grProd:'BSc']: [[attAddr:'Glendowie', attCost:32] ],
[grName:'Smith', grProd:'BA']: [[attAddr:'Pakuranga', attCost:130] ],
[grName:'Greene', grProd:'BCom']:[[attAddr:'St Heliers', attCost:60],
[attAddr:'St Heliers', attCost:60],
[attAddr:'Glen Innes', attCost:36], ],
[grName:'Smith', grProd:'BEng']:[[attAddr:'Panmure', attCost:18] ],
]
}//use