简体   繁体   中英

Java: create copy of ArrayList of HashMaps but include only certain keys

Consider the following data structures:

ArrayList<HashMap<String, String>> entries = new ArrayList<>();

ArrayList<String> keyNamesToInclude = new ArrayList<>();

This code creates a copy of entries, but with hashmaps only including the keys in keyNamesToInclude:

ArrayList<HashMap<String, String>> copies = new ArrayList<>();

for (HashMap<String, String> entry: entries) {
  HashMap<String, String> copy = new HashMap<>();
  for (String keyName: keyNamesToInclude) {
    copy.put(keyName, entry.get(keyName));
  }
  copies.add(copy);
}

How would one create this with Streams in a functional way?

It is better to convert keyNamesToInclude into Set to facilitate lookup of the keys.

Then use List::stream to get Stream<HashMap> and for each map get filtered stream of its entries re-collected into a new map and list accordingly.

Set<String> keys = new HashSet<>(keyNamesToInclude); // removes possible duplicates
List<Map<String, String>> copies = entries.stream() // Stream<HashMap>
    .map(m -> m.entrySet()
        .stream()
        .filter(e -> keys.contains(e.getKey()))
        .collect(Collectors.toMap(
            Map.Entry::getKey, Map.Entry::getValue
        ))
    )
    .collect(Collectors.toList());

If it is very important to have concrete implementations of List and Map in copies , casting or special forms of collectors may be needed even though Collectors.toList() returns ArrayList and Collectors.toMap returns HashMap :

// casting
ArrayList<HashMap<String, String>> copies2 = (ArrayList) entries.stream() // Stream<HashMap>
    .map(m -> (HashMap<String, String>) m.entrySet()
        .stream()
        .filter(e -> keys.contains(e.getKey()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
    )
    .collect(Collectors.toList());

// special collectors
// toMap(keyMapper, valueMapper, mergeFunction, mapFactory)
// toList -> toCollection(ArrayList::new)
ArrayList<HashMap<String, String>> copies3 = entries.stream() // Stream<HashMap>
    .map(m -> m.entrySet()
        .stream()
        .filter(e -> keys.contains(e.getKey()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, HashMap::new))
    )
    .collect(Collectors.toCollection(ArrayList::new));

You can do something like:

Set<String> setNamesToInclude = new HashSet<>(keyNamesToInclude);
List<Map<String, String>> copies = entries.stream()
    .map(hm -> hm.keySet()
        .stream()
        .filter(setNamesToInclude::contains)
        .collect(Collectors.toMap(Function.identity(), hm::get))
    )
    .collect(Collectors.toList());

Maybe somewhat more efficient solution that utilizes hashmap instead of list contains.

List<HashMap<String, String>> copies = maps.stream().map(e -> {
            HashMap<String, String> copy = new HashMap<>();
            keys.forEach(k -> {
                String value = e.get(k);
                if (value != null) 
                    copy.put(k, e.get(k));
            });
            return copy;
        }).collect(Collectors.toList());

You can do something like the following (pseudo code):

return entries.stream()
  .map(hashmap -> {
    return hashmap.stream()
      .filter((k,v) -> keyNamesToInclude.contains(k))
      .collect(toMap());
  })
  .collect(toList());

I recommend using a Set to contain the keysToBeIncluded since it is a little more efficient. This also filters out empty maps from the final list.

List<Map<String, String>> entries = List.of(
        Map.of("1", "one", "2", "two", "3", "three"),
        Map.of("2", "two", "3", "three"), Map.of("1", "one"),
        Map.of("6", "six", "7", "seven"));

entries.forEach(System.out::println);

Set<String> keyNamesToInclude = Set.of("2", "3", "6");
  • stream the original list of maps
  • for each map, filter the required keys
  • build the new map
  • filter to allow non-empty maps.
  • create the list
List<Map<String, String>> copies = entries.stream()
        .map(m -> m.entrySet().stream().filter(
                e -> keyNamesToInclude.contains(e.getKey()))
                .collect(Collectors.toMap(Entry::getKey,
                        Entry::getValue))).filter(m->!m.isEmpty())
        .toList();  // java 16 - can be replaced with a collector

copies.stream().forEach(System.out::println);

Prints four lines of source and three lines of result

{3=three, 2=two, 1=one}
{3=three, 2=two}
{1=one}
{7=seven, 6=six}

{2=two, 3=three}
{2=two, 3=three}
{6=six}

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