3

I want to split up a map into an array of maps. For example, if there is a map with 25 key/value pairs. I want an array of maps with no more than 10 elements in each map.

How would I do this in groovy?

I have a solution which I am not excited about, is there better groovy version:

  static def splitMap(m, count){
    if (!m) return

    def keys = m.keySet().toList()
    def result = []
    def num = Math.ceil(m?.size() / count)
    (1..num).each {
      def min = (it - 1) * count
      def max = it * count > keys.size() ? keys.size() - 1 : it * count - 1
      result[it - 1] = [:]
      keys[min..max].each {k ->
        result[it - 1][k] = m[k]
      }
    }
    result
  }

m is the map. Count is the max number of elements within the map.

tim_yates
  • 167,322
  • 27
  • 342
  • 338
Tihom
  • 3,384
  • 6
  • 36
  • 47

1 Answers1

6

Adapting my answer to this question on partitioning a List, I came up with this method:

Map.metaClass.partition = { size ->
  def rslt = delegate.inject( [ [:] ] ) { ret, elem ->
    ( ret.last() << elem ).size() >= size ? ret << [:] : ret
  }
  rslt.last() ? rslt : rslt[ 0..-2 ]
}

So if you take this map:

def origMap = [1:'a', 2:'b', 3:'c', 4:'d', 5:'e', 6:'f']

All of the following assertions pass :-)

assert [ [1:'a'], [2:'b'], [3:'c'], [4:'d'], [5:'e'], [6:'f'] ] == origMap.partition( 1 )
assert [ [1:'a', 2:'b'], [3:'c', 4:'d'], [5:'e', 6:'f'] ]       == origMap.partition( 2 )
assert [ [1:'a', 2:'b', 3:'c'], [4:'d', 5:'e', 6:'f'] ]         == origMap.partition( 3 )
assert [ [1:'a', 2:'b', 3:'c', 4:'d'], [5:'e', 6:'f'] ]         == origMap.partition( 4 )
assert [ [1:'a', 2:'b', 3:'c', 4:'d', 5:'e'], [6:'f'] ]         == origMap.partition( 5 )
assert [ [1:'a', 2:'b', 3:'c', 4:'d', 5:'e', 6:'f'] ]           == origMap.partition( 6 )

Or, as a Category (to avoid having to add anything to the metaClass of Map:

class MapPartition {
  static List partition( Map delegate, int size ) {
    def rslt = delegate.inject( [ [:] ] ) { ret, elem ->
      ( ret.last() << elem ).size() >= size ? ret << [:] : ret
    }
    rslt.last() ? rslt : rslt[ 0..-2 ]
  }
}

Then, where you need this functionality, you can simply use the Category like so:

use( MapPartition ) {
  assert [ [1:'a'], [2:'b'], [3:'c'], [4:'d'], [5:'e'], [6:'f'] ] == origMap.partition( 1 )
  assert [ [1:'a', 2:'b'], [3:'c', 4:'d'], [5:'e', 6:'f'] ]       == origMap.partition( 2 )
  assert [ [1:'a', 2:'b', 3:'c'], [4:'d', 5:'e', 6:'f'] ]         == origMap.partition( 3 )
  assert [ [1:'a', 2:'b', 3:'c', 4:'d'], [5:'e', 6:'f'] ]         == origMap.partition( 4 )
  assert [ [1:'a', 2:'b', 3:'c', 4:'d', 5:'e'], [6:'f'] ]         == origMap.partition( 5 )
  assert [ [1:'a', 2:'b', 3:'c', 4:'d', 5:'e', 6:'f'] ]           == origMap.partition( 6 )
}
Community
  • 1
  • 1
tim_yates
  • 167,322
  • 27
  • 342
  • 338
  • cool. I have a grails app. where do I put the Map.metaClass.partition code? Does it get invoked on startup? – Tihom Jan 06 '11 at 21:20
  • @Tihom @Gareth or, if you don't like polluting the metaClass (which can make things hard to test as well), I've amended my answer to show how to do the same thing as a Category – tim_yates Jan 06 '11 at 22:14