Uploaded image for project: 'Groovy'
  1. Groovy
  2. GROOVY-1320

add groupBy() method to Lists

    XMLWordPrintableJSON

Details

    • Improvement
    • Status: Closed
    • Minor
    • Resolution: Duplicate
    • None
    • 1.0-RC-1
    • 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

      { it.prod }

      == [ BEng:1, BEd:1, BCom:3, BA:2, BSc:1 ]

      //check grouping two fields using a map...
      assert items.groupBy

      { [grName:it.name, grProd:it.prod] }

      == [
      [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

      { [it.name, it.prod] }

      == [
      ['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

      { it.cost >= 20 }

      .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

      { [grName:it.name, grProd:it.prod] }

      .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.name}

      ,

      {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(

      {[grName:it.name, grProd:it.prod]}

      ,

      {[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

      Attachments

        Activity

          People

            guillaume Guillaume Sauthier
            gavingrover Gavin Grover
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: