Home > Mobile >  Combining values related to multiple keys in a hashmap based on a pattern
Combining values related to multiple keys in a hashmap based on a pattern

Time:06-11

I have a requirement in which I would like to combine values related to multiple keys in a HashMap based on a pattern.

Example : I have below key value pairs in a HashMap:

Key Value
skim fac1 ccy1 EUR
skim fac1 ccy1rate 0.15
skim fac1 ccy2 USD
skim fac1 ccy2rate 0.20
skim fac2 ccy1 GBP
skim fac2 ccy1rate 0.17

Now if we look at the keys in HashMap, those are following specific pattern : skim fac1,skim fac2....skim facn which is segregation criteria for information.

I would like to segregate and combine the information within a HashMap<String,List<Map<String,String>>> in the following manner:

Key Value
skim fac1 EUR,0.15
skim fac2 USD,0.20,GBP,0.17

Could someone please help me in this?

Thanks.

CodePudding user response:

There is a discrepancy between the output structure you described:

  • HashMap<String,List<Map<String,String>>>

and the expected output in your table:

  • HashMap<String,List<String>>

However, in both cases the problem lies mostly in how to find the substrings to group by your values. After getting them, your problem can be solved with a single operation stream where a collect(Collectors.toMap()) is employed. The only thing that changes is how you represent your values within the List (Map<String, String> or simply String).

Here is an implementation with comments detailing step by step:

public class Main {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>(Map.of(
                "skim fac1 ccy1", "EUR",
                "skim fac1 ccy1rate", "0.15",
                "skim fac1 ccy2", "USD",
                "skim fac1 ccy2rate", "0.20",
                "skim fac2 ccy1", "GBP",
                "skim fac2 ccy1rate", "0.17"
        ));

        //Using a set to have unique keys to group by in the resulting map
        Set<String> newKeys = new HashSet<>();

        //Checking for each key if its substring is equal to any other key's beginning:
        //  - if it does, then the substring is collected as a key to group by within the final map
        //
        //  - if it doesn't, then another substring is generated from the previous substring until a matching value is found.
        //          If no value is found, then the key is collected entirely for the resulting map.
        for (String key : map.keySet()) {

            //This loop keeps creating substrings of the current key until:
            //  - the substring matches another key's beginning
            //  - or no more substrings can be generated
            int lastIndex = key.lastIndexOf(" ");
            while (lastIndex > 0) {

                //Checking if the substring matches the beginning of any key except the current one
                String subStr = key.substring(0, lastIndex);
                if (map.keySet().stream().anyMatch(s -> !s.equals(key) && s.startsWith(subStr))) {

                    //If a match is found then the current substring is added to the set and the substring iteration is interrupted
                    newKeys.add(key.substring(0, lastIndex));
                    break;
                }

                //Creating a new substring from the previous substring if no match has been found
                lastIndex = key.substring(0, lastIndex).lastIndexOf(" ");
            }

            //If no substrings of the current key matches the beginning of any other key, then the current key is collected
            if (lastIndex < 0) {
                newKeys.add(key);
            }
        }

        //Creating the resulting map as a map of lists of maps
        Map<String, List<Map<String, String>>> mapRes = map.entrySet().stream()
                .collect(Collectors.toMap(
                        entry -> {
                            //Looking for the newKey which matches the beginning of the current entry's key.
                            return newKeys.stream().filter(s -> entry.getKey().startsWith(s)).findFirst().orElseThrow(() -> new RuntimeException("No key found"));
                        },
                        entry -> {
                            //Retrieving, like above, the newKey that will be used to map the current value
                            String newKey = newKeys.stream().filter(s -> entry.getKey().startsWith(s)).findFirst().orElseThrow(() -> new RuntimeException("No key found"));

                            //Returning a List with a single entry map.
                            //The entry's key corresponds to the rest of the substring between newKey and key (the portion of key not matched by newKey).
                            //while the value remains the original value.
                            return new ArrayList<>(List.of(Map.of(entry.getKey().substring(newKey.length()).trim(), entry.getValue())));
                        },
                        //Handling colliding cases by merging the lists together
                        (list1, list2) -> {
                            list1.addAll(list2);
                            return list1;
                        }
                ));

        //Printing the resulting map
        System.out.println("Map of lists of maps");
        for (Map.Entry<String, List<Map<String, String>>> entry : mapRes.entrySet()) {
            System.out.println(entry.getKey()   " => "   entry.getValue());
        }


        //Creating the resulting map as a map of lists of strings
        Map<String, List<String>> mapRes2 = map.entrySet().stream()
                .collect(Collectors.toMap(
                        entry -> {
                            //Looking for the newKey which matches the beginning of the current entry's key.
                            return newKeys.stream().filter(s -> entry.getKey().startsWith(s)).findFirst().orElseThrow(() -> new RuntimeException("No key found"));
                        },
                        entry -> {
                            //Returning a List with the original value.
                            return new ArrayList<>(List.of(entry.getValue()));
                        },
                        //Handling colliding cases by merging the lists together
                        (list1, list2) -> {
                            list1.addAll(list2);
                            return list1;
                        }
                ));

        //Printing the resulting map
        System.out.println("\nMap of lists of strings");
        for (Map.Entry<String, List<String>> entry : mapRes2.entrySet()) {
            System.out.println(entry.getKey()   " => "   entry.getValue());
        }
    }
}

Here is a link to test the code:

https://www.jdoodle.com/iembed/v0/s6e

Output

The output is shown for both output structures you were thinking of.

Map of lists of maps
skim fac1 => [{ccy2=USD}, {ccy1=EUR}, {ccy1rate=0.15}, {ccy2rate=0.20}]
skim fac2 => [{ccy1rate=0.17}, {ccy1=GBP}]

Map of lists of strings
skim fac1 => [USD, EUR, 0.15, 0.20]
skim fac2 => [0.17, GBP]

CodePudding user response:

As per my understanding, you want something like this Map<String, Map<String, String>> which will store the value such as skim fac1 = {ccy2rate=0.20, ccy1=EUR, ccy2=USD, ccy1rate=0.15}. I am giving my suggestion based on this output pattern .

Map<String, String> mainMap = new HashMap<>(Map.of(
                            "skim fac1 ccy1", "EUR", 
                            "skim fac1 ccy1rate", "0.15",
                            "skim fac1 ccy2", "USD", 
                            "skim fac1 ccy2rate","0.20",
                            "skim fac2 ccy1", "GBP",
                            "skim fac2 ccy1rate", "0.17"
                        ));

String pattern = "(.*?)(\\d )(.*)";
//here considering you will only have pattern like `skim facn ...` , collect distinct  `skim facn`values only.
/**
 * using pattern we can eaasily identify and group the string.
 * if pattern get matched then it will give out put as {skim fac}{n}{rest of the value}
 * here we only interested in {skim fac}{2} hence contcatanation of 2 strings 
 * 
 */
List<String> distinctKeys = mainMap.keySet().stream()
    .map(Pattern.compile(pattern)::matcher)
    .filter(Matcher::matches)   //considering only valid matches
    .map(m -> m.group(1)   m.group(2)) //concatanation of 2 string
    .distinct()//to get distinct resultset
    .collect(Collectors.toList());
System.out.println(distinctKeys);

Map<String, Map<String, String>> output = new HashMap<>(); 

for (String key : distinctKeys) {

Map<String, String> temp = new HashMap<>();

//filtering out the required keys for each iterations            
List<String> mainKeys = mainMap.keySet().stream().filter(m -> m.startsWith(key))
        .collect(Collectors.toList());

for (String mainKey : mainKeys) {                
        //substring the last part of the key
        String lastPart = mainKey.substring(key.length()   1, mainKey.length()).trim();
        temp.put(lastPart, mainMap.get(mainKey));
}
output.put(key, temp);

}

for (Map.Entry<String, Map<String, String>> entry : output.entrySet()) {
System.out.println(entry.getKey()   " ----> "   entry.getValue());
}

Output

skim fac1 ----> {ccy2rate=0.20, ccy1=EUR, ccy2=USD, ccy1rate=0.15}
skim fac2 ----> {ccy1=GBP, ccy1rate=0.17}
  • Related