0

Input: I have a list of Map (Key - Value pairs) and grouping keys

e.g.List<Map<String,String>> inputData json

[
 {
    'a': 'German',
    'b': 'Audi',
    'e': 'T3'
 },
 {
    'a': 'German',
    'b': 'BMW',
    'c': 'T6'
 },
 {
    'a': 'American',
    'b': 'Tesla',
    'c': 'T6'
 }
]

and groupingKeys= a and b

For Grouping key a -> I will create two buckets, one for German and other American and in each bucket I will again create bucket for value of b, check following

I want to produce following: Map<String, Map<String, List<Map>> - the nesting of maps == number of grouping keys, here it's 2

{
   German: {
       Audi: [
            {
               'a': 'German',
               'b': 'Audi',
               'e': 'T3'
            }
       ],
       BMW: [
            {
               'a': 'German',
               'b': 'BMW',
               'c': 'T6'
            }
       ]
   },
   American: {
      Tesla: [
          {
            'a': 'American',
            'b': 'Tesla',
            'c': 'T6'
          }
      ]
   }
}

Things I tried:

inputData.stream().collect(Collectors.groupingBy(mapItem -> this.constructGroupingKey(groupingKeys, mapItem)));

//constructGroupingKey - this joins the values of of given grouping keys

This way I can construct concatenated key e.g German-Audi OR German-BMW and save the matching item against it but it's not what I want exactly.

As @Naman said here about concrete class - following makes it one liner but I can't make assumptions about data, it's of type Map

Map<String, Map<String, List<ConcreteClass>>> output = inputData.stream().collect(Collectors.groupingBy(ConcreteClass::getCountry, Collectors.groupingBy(ConcreteClass::getCarType)));

Would appreciate other ideas, thanks. I am also curious if I can derive grouping based on given data without specification of grouping keys.

Input is of dynamic nature, key and value are defined by the input

4
  • Use concrete objects! Another way, try and share the type of your output and look at the complexity driven there. Commented Mar 21, 2019 at 6:52
  • Could you use nested Maps? Map<String, Map<String, Map<String, String>>> where the first Map's key is a country, second Map's is a company and the last one stores models? Commented Mar 21, 2019 at 6:57
  • yeah I can, Can I derive it based on the nature of keys automatically, the nesting will increase based on number of grouping keys, I can't assume it's just two levels of nesting Commented Mar 21, 2019 at 6:59
  • @Naman, the input is of type Map<String, String> , I can not make assumptions about data, creating concrete class would require me to specify field names? Commented Mar 21, 2019 at 7:00

3 Answers 3

1

Just to double click on the representation of the input as concrete class. It would look much simpler and clean as :

Map<String, Map<String, List<ThrowAway>>> multipleGrouping(List<ThrowAway> inputData) {
    return inputData.stream()
            .collect(Collectors.groupingBy(ThrowAway::getA,
                    Collectors.groupingBy(ThrowAway::getB)));
}

where I just created a sample class same as your map representation:

class ThrowAway {
    String a;
    String b;
    String c;

    String getA() {
        return a;
    }

    String getB() {
        return b;
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

Poor naming convention! Agreed, yet it's more about portraying the approach here.
Are you saying from given input of Map<String,String> --> derive class ThrowAway ? The input will change, we can't map everything to ThrowAway ?
@Pradeep What I am trying to convey is that there are certain guarantees assumed as per the question itself. For e.g. is it possible to have the input in the form [ { 'a': 'German', 'b': 'Audi', 'e': 'T3' }, { 'a': 'German', 'b': 'BMW', 'c': 'T6' }, { 'd': 'American', 'e': 'Tesla', 'f': 'T6' } ] and then what do you expect the output to be? There has to be some kind of contracts to determine the keys to group with at least.
we can not assume input form but I can ask caller to preregister the grouping keys, the output is always nested based on the grouping keys
@Pradeep and preregistering the keys is defining a concrete object in my understanding. Also, seems like you're ignoring the kind of role inheritance can play in here.
|
0

Since you are looking for a dynamic solution, here is another approach:

You can use the following grouping method to pass two keys for:

public Map<String, Map<String, List<Map<String, String>>>> group(List<Map<String, String>> list, String key1, String key2) {
    return list.stream().collect(
            Collectors.groupingBy(m -> m.get(key1),
                    Collectors.groupingBy(m -> m.get(key2))
            )
    );
}

The problem here is if the key does not exist in the map you are getting a NullPointerException. To prevent this you can use map.getOrDefault() with a default key:

public Map<String, Map<String, List<Map<String, String>>>> group(List<Map<String, String>> list, String key1, String key2, String noneKey) {
    return list.stream().collect(
            Collectors.groupingBy(m -> m.getOrDefault(key1, noneKey),
                    Collectors.groupingBy(m -> m.getOrDefault(key2, noneKey))
            )
    );
}

Call one of the methods with the following options:

Map<String, Map<String, List<Map<String, String>>>> result = group(list, "a", "b");

Or

Map<String, Map<String, List<Map<String, String>>>> result = group(list, "a", "b", "none");

Comments

0

You can use JSON library such as Josson to do the transformation.

https://github.com/octomix/josson

Josson josson = Josson.fromJsonString(
    "[" +
    "    {" +
    "        \"a\": \"German\"," +
    "        \"b\": \"Audi\"," +
    "        \"e\": \"T3\"" +
    "    }," +
    "    {" +
    "        \"a\": \"German\"," +
    "        \"b\": \"BMW\"," +
    "        \"c\": \"T6\"" +
    "    }," +
    "    {" +
    "        \"a\": \"American\"," +
    "        \"b\": \"Tesla\"," +
    "        \"c\": \"T6\"" +
    "    }" +
    "]");
JsonNode node = josson.getNode(
    "group(a).map(a::elements.group(b).map(b::elements).mergeObjects()).mergeObjects()");
System.out.println(node.toPrettyString());

Output

{
  "German" : {
    "Audi" : [ {
      "a" : "German",
      "b" : "Audi",
      "e" : "T3"
    } ],
    "BMW" : [ {
      "a" : "German",
      "b" : "BMW",
      "c" : "T6"
    } ]
  },
  "American" : {
    "Tesla" : [ {
      "a" : "American",
      "b" : "Tesla",
      "c" : "T6"
    } ]
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.