简体   繁体   中英

Processing a list of maps using Java 8 streams

How can I simplify this code into a single lambda expression? The idea is that there is a list of maps and I would like to create a new list of maps, using a filter on the key. In this example, I want to remap it so that it only keeps the keys "x" and "z".

    Map<String, String> m0 = new LinkedHashMap<>();
    m0.put("x", "123");
    m0.put("y", "456");
    m0.put("z", "789");

    Map<String, String> m1 = new LinkedHashMap<>();
    m1.put("x", "000");
    m1.put("y", "111");
    m1.put("z", "222");

    List<Map> l = new ArrayList<>(Arrays.asList(m0, m1));
    List<Map> tx = new ArrayList<>();
    for(Map<String, String> m : l) {
        Map<String, String> filtered = m.entrySet()
                .stream()
                .filter(map -> map.getKey().equals("x") || map.getKey().equals("z"))
                .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
        tx.add(filtered);
    }
    System.err.println("l: " + l);
    System.err.println("tx: " + tx);

Output:

    l: [{x=123, y=456, z=789}, {x=000, y=111, z=222}]
    tx: [{x=123, z=789}, {x=000, z=222}]

Of course, you can convert your entire operation into one Stream operation.

// no need to copy a List (result of Array.asList) to an ArrayList, by the way
List<Map<String, String>> l = Arrays.asList(m0, m1);

List<Map<String, String>> tx = l.stream().map(m -> m.entrySet().stream()
        .filter(map -> map.getKey().equals("x") || map.getKey().equals("z"))
        .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue())))
    .collect(Collectors.toList());

But note that streaming over a Map and filtering is an operation with a linear time complexity, as it will check each key of each map against the filter, while you have only a very small number of actual keys you want to retain. So here, it is much simpler and more efficient (for larger maps) to use

List<Map<String, String>> tx = l.stream()
    .map(m -> Stream.of("x", "y")
                    .filter(m::containsKey).collect(Collectors.toMap(key->key, m::get)))
    .collect(Collectors.toList());

which will only perform four lookups per map. If it bothers you, you could even reduce it to two lookups, however, the constant factor is irrelevant for the overall time complexity, which will be constant time, if the map has a constant time lookup, like HashMap . Even for map's with O(log(n)) lookup time complexity, like TreeMap , this will be more efficient than the linear scan, if the maps are larger than the three mappings of the example code.

You can try something like this:

List<Map<String, String>> l = Arrays.asList(m0, m1);

l.forEach(map -> {
    map.entrySet().removeIf(e -> !e.getKey().equals("x") && !e.getKey().equals("z"));
});

It simply removes all of the mappings in every Map<String, String> if the entry key is not x or z .

Edit: You should utilize Radiodef's equivalent, but shorter method!

List<Map<String, String>> l = Arrays.asList(m0, m1);

l.forEach(map -> map.keySet().retainAll(Arrays.asList("x", "z"));

Try the following code(I declared a list for desiredKeys ):

public class Main {
    public static void main(String[] args) {
        Map<String, String> m0 = new HashMap<>();
        m0.put("x", "123");
        m0.put("y", "456");
        m0.put("z", "789");

        Map<String, String> m1 = new HashMap<>();
        m1.put("x", "000");
        m1.put("y", "111");
        m1.put("z", "222");

        List<Map<String, String>> l = new ArrayList<>(Arrays.asList(m0, m1));

        List<String> desiredKeys = Lists.newArrayList("x", "z");

        List<Map<String, String>> transformed = l.stream().map(map -> map.entrySet().stream()
                .filter(e -> desiredKeys.stream().anyMatch(k -> k.equals(e.getKey())))
                .collect(Collectors.toMap(e -> e.getKey(), p -> p.getValue()))).filter(m -> !m.isEmpty()).collect(Collectors.toList());

        System.err.println(l);
        System.err.println(transformed);
    }
}

Try this, it should work :

Map<String, String> m0 = new HashMap<>();
        m0.put("x", "123");
        m0.put("y", "456");
        m0.put("z", "789");

        Map<String, String> m1 = new HashMap<>();
        m1.put("x", "000");
        m1.put("y", "111");
        m0.put("z", "222");

        List<Map> l = new ArrayList<>(Arrays.asList(m0, m1));
        List<Map> transformed = new ArrayList<Map>() ;
        l.stream().map(map -> {
            Set<String> keys = map.keySet() ;
            Map<String, String> newMap = new HashMap<>();
            for(String key : keys){
               if(key.equals("x")|| key.equals("z")) 
                    newMap.put(key, map.get(key).toString()) ;
            }
            return newMap ;
        }).forEach(map -> transformed.add(map)); 

        System.out.println(transformed);

How about:

 tx = StreamEx.of(l)
              .map(m -> EntryStream.of(m).filterKeys(k -> k.equals("x") || k.equals("z")).toMap())
              .toList();

By StreamEx

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM