Get the last element of each entry in a LinkedHashMap?

Viewed 117

I have a map and I want to extract the last value for each key from that map, but I can't get it:

import java.util.LinkedHashMap;
import java.util.Map;

class MyTestClass {

    private static final String almacenNombreClase = "Professor";

    private static final String TXT = "TXT";
    private static final String NUM = "NUM";

    public static void main(String[] args) {
        Map<String, Map<String, String>> mapNombreClaseYPropiedades = new LinkedHashMap<>();
        mapNombreClaseYPropiedades.put("Professor", Map.of("texto", TXT, 
                                                            "tipo", TXT, 
                                                            "prueba", NUM, 
                                                            "dificultad", NUM ));
        mapNombreClaseYPropiedades.put("Exam", Map.of("texto", TXT,
                                                      "nivel", NUM ));

        //values of mapNombreClaseYPropiedades
        Map<String, String> valores = mapNombreClaseYPropiedades.get(almacenNombreClase);

        valores.forEach((k, v) -> {
            // Here I want to check the last element of each entry
            if (v instanceof String ) {
                System.out.println("String " + k + ",");
            } else {
                System.out.println("Integer " + k + ",");
            }
        });
        // I want to see output
        "dificultad=NUM"
        "nivel=NUM"
    }
}

Extract:

dificultad=NUM
nivel=NUM

i.e. the last NUM entries for each key

3 Answers

You can use reduce to extract the last value from Map<String,String> and then collect them using toMap into LinkedHashMap to save the insertion order

But as a note to preserve the order you have to use LinkedHashMap for input and output as well

    Map<String,String> valores = mapNombreClaseYPropiedades.values()
            .stream()
            .map(map->map.entrySet().stream().reduce((prev,next)->next))
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue, (prev,next)->next,LinkedHashMap::new));
    Map<String, Map<String, String>> mapNombreClaseYPropiedades = new LinkedHashMap<>();
    mapNombreClaseYPropiedades.put("Professor", new LinkedHashMap<>() {{
        put("texto", "TXT");
        put("tipo", "TXT");
        put("prueba", "NUM");
        put("dificultad", "NUM");
    }});
    mapNombreClaseYPropiedades.put("Exam", new LinkedHashMap<>() {{
        put("texto", "TXT");
        put("nivel", "NUM");
    }});

    mapNombreClaseYPropiedades.values().stream()
            .map(lhm -> lhm.entrySet().stream()
                    .skip(lhm.size() - 1)
                    .findFirst()
                    .map(last -> last.getKey() + "," + last.getValue()))
            .forEach(System.out::println);

Here is a solution that will ensure the order of the map, and it shows that you can skip, rather than reduce, since that's really what you're doing when you want the last element.

I recommend you do the following as a simple stream won't work because the inner maps are not ordered and thus may give inconsistent results.

  • first, the inner map needs to be a LinkedHashMap. This guarantees the order will not change from one run to the next.
  • write a utility method that takes the keys just like Map.of() but returns a LinkedHashMap.

Now create the map

private static final String TXT = "TXT";
private static final String NUM = "NUM";

Map<String, Map<String, String>> mapNombreClaseYPropiedades =
            new LinkedHashMap<>();
    
mapNombreClaseYPropiedades.put("Professor", createMap(
            "texto", TXT, "tipo", TXT,
                    "prueba", NUM, "dificultad", NUM));
    
mapNombreClaseYPropiedades.put("Exam", createMap(
            "texto", TXT, "nivel", NUM));

Now simply stream the values and create a List of the entry sets. This works by pulling off the last entry in the created list. The entrySet is guaranteed to maintain the order because it is a linked list. Null and empty maps are filtered out.

List<Entry<String,String>> last = mapNombreClaseYPropiedades.values()
            .stream().filter(s->s != null && !s.isEmpty())
            .map(m -> new ArrayList<>(m.entrySet()))
            .map(lst -> lst.get(lst.size() - 1))
            .toList(); // java 16 - or use .collect(Collectors.toList());
    
System.out.println(last);

Prints

[dificultad=NUM, nivel=NUM]

Here is the method that returns a LinkedHashMap

    
private static Map<String,String> createMap(String...keyValues) {
    Map<String,String> map = new LinkedHashMap<>();
    for (int i = 0; i < keyValues.length; i+=2) {
        map.put(keyValues[i], keyValues[i+1]);
    }
    return map;
}
Related