-1

I have two lists that I need to check that every product (from products) has a code (from productCodes)

List<String> productCodes = List.of("X_14_AA_85", "X_14_BB_85", "X_14_ZZ_85");
List<String> products = List.of("AA", "BB", "CC", "ZZ");

// I want to achieve a collection of (product code, product)
// according if product name exists in productCode name

// key - product code, value - product
/*
Map<String, String> map = Map.of(
  "AA", "X_14_AA_85",
  "BB", "X_14_BB_85",
  "CC", null,          // null if code doesn't exist
  "ZZ", "X_14_ZZ_85" 
);
*/
// after a filter with null keys I could return a message something like this
// List<String> nullableProducts = List.of("CC");
// return "I could prompt that there's no code for product/s: " + nullableProducts;

Is there a way with streams to filter by list item values?

Nowhere Man
  • 19,170
  • 9
  • 17
  • 42
lpkej
  • 445
  • 6
  • 23

3 Answers3

0

You can stream the keySet and filter null values:

Java 16+:

List<String> list = map.keySet().stream()
        .filter(key -> map.get(key) == null).toList();

Java 15 and older:

List<String> list = map.keySet().stream()
        .filter(key -> map.get(key) == null)
        .collect(Collectors.toList());

Note: You can't instantiate an unmodifiable Map using Map.of() with null keys or values. Instead, you can do:

Map<String, String> map = new HashMap<>();
map.put("AA", "X_14_AA_85");
map.put("BB", "X_14_BB_85");
map.put("CC", null);
map.put("ZZ", "X_14_ZZ_85");
Oboe
  • 2,643
  • 2
  • 9
  • 17
0

If the purpose is to get a map containing null value, this has to be implemented using a custom collector, because existing implementation throws NullPointerException when putting null:

List<String> productCodes = List.of("X_14_AA_85", "X_14_BB_85", "X_14_ZZ_85");
List<String> products = List.of("AA", "BB", "CC", "ZZ");

Map<String, String> mapCodes = products.stream()
    .distinct()
    .collect(
        HashMap::new, 
        (m, p) -> m.put(p, productCodes
            .stream()
            .filter(pc -> pc.contains(p))
            .findFirst()
            .orElse(null)
        ), 
        HashMap::putAll
    );
// -> {AA=X_14_AA_85, BB=X_14_BB_85, CC=null, ZZ=X_14_ZZ_85}

Then the list of non-matched products may be retrieved as follows:

List<String> nonMatchedProducts = mapCodes.entrySet()
    .stream()
    .filter(e -> e.getValue() == null)
    .map(Map.Entry::getKey)
    .collect(Collectors.toList());
// -> [CC]

However, as the result of findFirst is returned as Optional it may be used along with Collectors::toMap, and then the non-matched values can be filtered out using Optional::isEmpty:

Map<String, Optional<String>> mapCodes2 = products.stream()
    .distinct()
    .collect(Collectors.toMap(
        p -> p,
        p -> productCodes.stream().filter(pc -> pc.contains(p)).findFirst()
    ));
// -> {AA=Optional[X_14_AA_85], BB=Optional[X_14_BB_85], CC=Optional.empty, ZZ=Optional[X_14_ZZ_85]}

List<String> nonMatchedProducts2 = mapCodes2.entrySet()
    .stream()
    .filter(e -> e.getValue().isEmpty())
    .map(Map.Entry::getKey)
    .collect(Collectors.toList());
// -> [CC]

Also, the null/empty values may not be stored at all, then non-matched products can be found after removing all the matched ones:

Map<String, String> map3 = new HashMap<>();
for (String p : products) {
    productCodes.stream()
        .filter(pc -> pc.contains(p))
        .findFirst()
        .ifPresent(pc -> map3.put(p, pc)); // only matched pairs
}
// -> {AA=X_14_AA_85, BB=X_14_BB_85, ZZ=X_14_ZZ_85}
List<String> nonMatchedProducts3 = new ArrayList<>(products);
nonMatchedProducts3.removeAll(map3.keySet());
// -> [CC]
Nowhere Man
  • 19,170
  • 9
  • 17
  • 42
0

Given your two lists, I would do something like this. I added two products that contain non-existent codes.

List<String> products =
        List.of("X_14_AA_85", "X_14_SS_88", "X_14_BB_85", "X_14_ZZ_85", "X_16_RR_85");
List<String> productCodes = List.of("AA", "BB", "CC", "ZZ");

Declare a lambda to extract the code and copy the codes to a set for efficient lookup. In fact, since duplicates codes aren't necessary, a set would be the preferred data structure from the start.

Assuming the product code is the same place and length, you can do it like this using substring. Otherwise you may need to use a regular expression to parse the product string.

Function<String, String> extractCode =
        code -> code.substring(5,7);
Set<String> productCodeSet = new HashSet<>(productCodes);

And run it like this.

List<String> missingCodes = products.stream()
        .filter(product -> !productCodeSet
                .contains(extractCode.apply(product)))
        .toList();

System.out.println("There are no codes for the following products: " + missingCodes);

Prints

There are no codes for the following products: [X_14_SS_88, X_16_RR_85]
WJS
  • 36,363
  • 4
  • 24
  • 39