Groovy Map Lists to Map Lists

So, I have something like this:

[a: ["c","d"], b: ["e","f"]] 

The number of items in each list is arbitrary. If there is only one item, the list is no longer a list, and it is a string.

I want to turn it into:

 [ [a:"c", b:"e"], [a:"d",b:"f"] ] 

I don't care if the solution uses Groovy methods or not. Thank you for your help!

+5
source share
5 answers

Here's another way to do this, I think, is less obscure, but still pretty concise:

 def ml = [a: ["c","d"], b: ["e","f"]] // Create an empty list that creates empty maps as needed def lm = [].withDefault{ [:] } ml.each{ k, values -> [values].flatten().eachWithIndex { value, index -> lm[index][k] = value } } assert lm == [[a:"c", b:"e"], [a:"d", b:"f"]] 

If you do not want or cannot use withDefault (because you do not want the list to grow automatically), then this also works:

 def ml = [a: ["c","d"], b: ["e","f"]] def lm = [] ml.each{ k, values -> [values].flatten().eachWithIndex { value, index -> lm[index] = lm[index] ?: [:] lm[index][k] = value } } assert lm == [[a:"c", b:"e"], [a:"d", b:"f"]] 

Edit : code has been added to handle strings not in the list.

Note that this trick ( [values].flatten().eachWithIndex{...} ) is not necessarily very effective. If speed is important, then using it will be a little faster due to readability:

 (values instanceof List ? values : [values]).eachWithIndex{...} 
+8
source

Single-line if x = [a: ["c","d"], b: ["e","f"]] or x = [a: "b", c: "d"] :

 [x*.key, x*.value].transpose()*.combinations().transpose()*.flatten()*.toSpreadMap() 

How it works:

First separate the keys and values:

[x*.key, x*.value] = [[a, b], [[c, d], [e, f]]]

Import them to pair keys and values:

[[a, b], [[c, d], [e, f]]].transpose() = [[a, [c, d]], [b, [e, f]]]

Use combinations to pair the key with your values ​​(the distribution operator used here to apply it to each item in the list). Please note that combinations will work correctly with both [a:b] and [a:[b,c]] :

[[a, [c, d]], [b, [e, f]]]*.combinations() = [[[a, c], [a, d]], [[b, e], [b, f]]]

Move the lists so that we end up with abab instead of aabb (albeit somewhat nested):

[[[a, c], [a, d]], [[b, e], [b, f]]].transpose() = [[[a, c], [b, e]], [[a, d], [b, f]]]

Smooth nested lists (using markup again to smooth nested lists, but not the entire list):

[[[a, c], [b, e]], [[a, d], [b, f]]]*.flatten() = [[a, c, b, e], [a, d, b, f]]

Spread toSpreadMap to convert this list to a list of maps.

[[a, c, b, e], [a, d, b, f]]*.toSpreadMap() = [*:[b:e, a:c], *:[b:f, a:d]]

+7
source

Define some functions:

 // call a 2-element list a "pair" // convert a map entry (where entry.value can be // a single string or a list of strings) into a list of pairs def pairs(entry) { if (entry.value instanceof String) return [[entry.key, entry.value]] entry.value.collect { [entry.key, it]} } // convert list of pairs to a map def toMap(pairs) { pairs.inject([:]){ m,i -> m[i[0]] = i[1]; m } } // kind of like transpose but doesn't stop with shortest list. // (would like to find a less ugly way of doing this) def mytranspose(lists) { def retval = [] def mx = lists.inject(0){x, i -> i.size() > x ? i.size() : x} for (int i = 0; i < mx; i++) { def row = [] lists.each { lst -> if (lst.size() > i) row << lst[i] } retval << row } retval } 

then assemble it and test:

 groovy:000> m = [a: ["c","d"], b: ["e","f"]] groovy:000> mytranspose(m.entrySet().collect{pairs(it)}).collect{toMap(it)} ===> [{a=c, b=e}, {a=d, b=f}] 

Map records in which lines work, and lists of map records can have different lengths:

 groovy:000> m['g'] = 'h' ===> h groovy:000> m['x'] = ['s', 't', 'u', 'v'] ===> [s, t, u, v] groovy:000> m ===> {a=[c, d], b=[e, f], g=h, x=[s, t, u, v]} groovy:000> mytranspose(m.entrySet().collect{pairs(it)}).collect{toMap(it)} ===> [{a=c, b=e, g=h, x=s}, {a=d, b=f, x=t}, {x=u}, {x=v}] 
+2
source

Here is what I ended up doing. If anyone has a better solution, let me know and I will accept this as an answer.

  Map xyz = [a: ["c","d"], b: ["e","f"]] List result = [] Closure updateMap = { list, index, key, value -> if ( !(list[index] instanceof Map) ) { list[index] = [:] } list[index]."$key" = value } xyz.each { k, v -> if (v instanceof ArrayList) { v.eachWithIndex { val, idx -> updateMap(result, idx, k, val) } } else { updateMap(result, 0, k, v) } } 
+1
source

How can this be changed in clockwork?

i.e.

I want to include this:

[[a: "c", b: "e"], [a: "d", b: "f"]]

in: [a: ["c", "d"], b: ["e", "f"]]

0
source

All Articles