Nested repeating stream filtering - java

I am learning Java streams and have a situation where I need to use different filter on a stream in case filtering with the first filter didn't return any results. Currently I have this, thinking if there is a better way to write this:
String str = requestParams.entrySet()
.stream()
.filter(e -> e.getKey().contains("string1") && e.getValue().length() > 0)
.map(Map.Entry::getValue)
.findFirst().orElseGet(() -> requestParams.entrySet()
.stream()
.filter(e -> e.getKey().contains("string2") && e.getValue().length() > 0)
.map(Map.Entry::getValue)
.findFirst().orElse(""));

As you want to search the entire source for this criteria e.getKey().contains("string1") && e.getValue().length() > 0 and only then if it's not met then you want to do another search for e.getKey().contains("string2") && e.getValue().length() > 0 I would say your current approach is fine but just encapsulate the logic into a method so you DRY i.e.
static Optional<String> getValueByX(Map<String, String> requestParams, String searchString) {
return requestParams.entrySet()
.stream()
.filter(e -> e.getKey().contains(searchString) &&
e.getValue().length() > 0)
.map(Map.Entry::getValue)
.findFirst();
}
Then you would call it as:
String result = getValueByX(requestParams, "string1")
.orElseGet(() -> getValueByX(requestParams, "string2").orElse(""));
However, if you don't care about searching for "string1" in the entire source before applying the criteria for "string2" then you're better of doing:
String str = requestParams.entrySet()
.stream()
.filter(e -> (e.getKey().contains("string1") || e.getKey().contains("string2")) && e.getValue().length() > 0)
.map(Map.Entry::getValue)
.findFirst().orElse("")
if there could be more options than the aforementioned two then I would create a list to contain those values and then perform:
List<String> search = Arrays.asList("string1", "string2"..........);
String result = requestParams.entrySet()
.stream()
.filter(e -> search.stream().anyMatch(a -> e.getKey().contains(a)) &&
e.getValue().length() > 0)
.map(Map.Entry::getValue)
.findFirst().orElse("");

You can do it using the Collectors.teeing method by processing two streams in parallel and filtering. This returns an Optional and will be either s1, s2, or Non-existent depending on the map contents.
This presumes that you want to favor string1 before accepting string2
Map<String, String> requestParams = Map.of("string1","s1","string3",
"s3", "string2", "s2", "string4","s4");
Optional<String> op = requestParams.entrySet().stream()
.filter(e -> e.getValue().length() > 0)
.collect(Collectors.teeing(Collectors.filtering(
e -> e.getKey().contains("string1"),
Collectors.reducing((a, b) -> a)),
Collectors.filtering(
e -> e.getKey().contains("string2"),
Collectors.reducing((a, b) -> a)),
(opt1, opt2) -> opt1.or(() -> opt2)
.map(Entry::getValue)));
System.out.println(op.orElse("Non Existent"));
The above use of Collectors.teeing was modified to incorporate suggestion by Holger to use Collectors.reducing to get first item.

You can check both conditions simultaneously, since the other logic is the same:
List<String> keys = Arrays.asList("string1", "string2");
requestParams.entrySet()
.stream()
.filter(e -> keys.contains(e.getKey()) && e.getValue().length() > 0)
.map(Map.Entry::getValue)
.findFirst().orElse("");

Related

Check if values exist in Java Streams and if so then map them to integers

I have a Java List and I am trying to check if certain values exist in this list map them to 1 else to 0.
List<String> status1 = Arrays.asList("UPS","USPS","FEDEX","DHL","AMAZON");
status1
.stream()
.map(String::toLowerCase)
.map((s -> s.equals("ups") ? 1 : 0) || (s -> s.equals("fedex") ? 1 : 0))
.forEach(System.out::println);
You should rewrite the lambda in map:
Arrays.asList("UPS","USPS","FEDEX","DHL","AMAZON")
.stream()
.mapToInt(s ->
"ups".equalsIgnoreCase(s) || "fedex".equalsIgnoreCase(s) ? 1 :0)
.forEach(System.out::println);
List<String> status1 = Arrays.asList("USPS","DHL","AMAZON");
int result= status1.stream().anyMatch(s-> s.equals("UPS")||s.equals("FEDEX"))?1:0;

Proper usage of Streams in Java

I've a use-case where I need to parse key-value pairs (separated by =) and put these key-value pairs in a LinkedHashMap.
I want to ignore the following type of Strings
key is empty or contains only spaces
value is empty or contains only spaces
those Strings which don't contain a =.
Now, I have solved it using imperative style and by using streams also.
The following are the 2 variants:
Solution by iterative style - for loop and lots of if
public static Map<String, String> getMap1(String[] array) {
Map<String, String> map = new LinkedHashMap<>();
for (int i = 0; i < array.length; i++) {
String currentString = array[i];
int index = currentString.indexOf('=');
// ignoring strings that don't contain '='
if (index == -1) continue;
String key = currentString.substring(0, index).trim();
String value = currentString.substring(index + 1).trim();
// ignoring strings with empty key or value
if (key.length() == 0 || value.length() == 0) continue;
map.put(key, value);
}
return map;
}
Solution that uses Streams - pretty clean code
public static Map<String, String> getMap(String[] array) {
return Arrays.stream(array)
.filter(s -> s.indexOf('=') != -1) // ignore strings that don't contain '='
.filter(s -> s.substring(0, s.indexOf('=')).trim().length() != 0) // key should be present
.filter(s -> s.substring(s.indexOf('=') + 1).trim().length() != 0) // value should be present
.collect(Collectors.toMap(
s -> s.substring(0, s.indexOf('=')).trim(),
s -> s.substring(s.indexOf('=') + 1).trim(),
(first, second) -> second,
LinkedHashMap::new));
}
I'm worried here because while using Streams, I'm calling the indexOf method multiple times. (And for big strings, I can end-up recalculating the same thing again and again).
Is there a way I can avoid re-computation done by indexOf method in such a way that the code is still clean. (I know talking about clean-code is very subjective, but I want don't want to open multiple streams, of loop through the original string-array and subsequently pre-computing the indices of = and re-using that).
Clubbing multiple filters into a single filter again seem to be an option but that would make my predicate pretty ugly.
(This is a result of my idle musing where I wish to learn/improve).
What about this:
String[] array = {"aaa2=asdas","aaa=asdasd"};
LinkedHashMap<String, String> aaa = Arrays.stream(array)
.map(s -> s.split("=", 2))
.filter(s -> s.length == 2) // ignore strings that don't contain '='
.peek(s -> { s[0] = s[0].trim(); })
.peek(s -> { s[1] = s[1].trim(); })
.filter(s -> s[0].length() != 0) // key should be present
.filter(s -> s[1].length() != 0) // value should be present
.collect(Collectors.toMap(
s -> s[0],
s -> s[1],
(first, second) -> second,
LinkedHashMap::new));
I'd use split instead of indexOf and StringUtils to check that your keys and values are not empty.
public static Map<String, String> getMap(String[] array) {
return Arrays.stream(array)
.filter(s -> s.contains("="))
.map(s -> s.split("="))
.filter(s -> s.length == 2 && isNotBlank(s[0]) && isNotBlank(s[1]))
.collect(Collectors.toMap(
s -> s[0].trim(),
s -> s[1].trim()));
}

using java 8 groupBy with conditions

Group by number to find groups with size bigger then 1
2. Check if a group contains a condition (string !=null)
2a. if yes ---> remove all rows which do not have a condition (string == null)
2b. if no ---> return the group as it is (no filtering at all)
I am trying below code but I am not able to filter the condition here. Is there any simple way to do it?
groupByList = list.stream().collect(Collectors.groupingBy(student::no));
groupByList.values().stream().map(group -> group.size() > 1)
.collect(Collectors.toList());
You need to use both Stream.filter(..) and Stream.map(..)
// Groups students by group number
Map<String, List<Student>> allGroups = list.stream().collect(Collectors.groupingBy(student::no));
// Filters groups that have more than 1 member
Map<String, List<Student>> filteredGroups = allGroups.entrySet().stream().filter(entry -> entry.getValue().size() > 1).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// Removes all non-null members
Map<String, List<Student>> cleanGroups = filteredGroups.entrySet().stream()
.map(entry -> {
entry.setValue(entry.getValue().stream().filter(student -> student.name != null).collect(Collectors.toList()));
return entry;
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
You can pipe the execution into one big chain:
Map<String, List<Student>> cleanGroups = list.stream()
.collect(Collectors.groupingBy(student::no))
.entrySet().stream()
.filter(entry -> entry.getValue().size() > 1)
.map(entry -> {
entry.setValue(entry.getValue().stream()
.filter(student -> student.name != null)
.collect(Collectors.toList()));
return entry;
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Please note, that you have a hidden object transformation withing the .filter(..).collect(..) above. If you need to skip that (at cost of double comparison), you can extend the lambda method to:
.map(entry -> {
if (entry.getValue().stream.anyMatch(student.name == null)) {
entry.setValue(entry.getValue().stream().filter(student -> student.name != null).collect(Collectors.toList()));
}
return entry;
})

Collecting after filtering a stream with Java 8

i have a list of DTOs, these dtos contains a list of tags. I'm searching to find dtos that contain 2 tags each with its own key and value. this code will work - but it would only find first on the inner filters, i would like to collect instead of finding the first, in case there is more than one object with that criteria
List<myDTO> responsesList = getAllData(parameters);
List<myDTO> result = responsesList.stream()
.filter(d ->
d.getData().getTags().stream()
.filter(t -> t.getKey().equals(key1) && t.getValue().equals(value1))
.findFirst().isPresent())
.filter(d ->
d.getData().getTags().stream()
.filter(t -> t.getKey().equals(key2) && t.getValue().equals(value2))
.findFirst().isPresent())
.collect(Collectors.toList());
what am I missing to collect a collection instead of the findFirst().isPresent()? if I do Collect(collectors.toList) I get an error message like "inference variable T has incompatible bounds"?
It's not actually clear what do you want. If you need to collect all the myDTO objects which have both key1/value1 tag and key2/value2 tag, then your code already works. You can just shorten it as filter(predicate).findFirst().isPresent() could be replaced with anyMatch(predicate):
List<myDTO> result = responsesList.stream()
.filter(d ->
d.getData().getTags().stream()
.anyMatch(t -> t.getKey().equals(key1) && t.getValue().equals(value1)))
.filter(d ->
d.getData().getTags().stream()
.anyMatch(t -> t.getKey().equals(key2) && t.getValue().equals(value2)))
.collect(Collectors.toList());
You can also join both filters into single predicate, though this is a matter of taste:
List<myDTO> result = responsesList.stream()
.filter(d ->
d.getData().getTags().stream()
.anyMatch(t -> t.getKey().equals(key1) && t.getValue().equals(value1))
&&
d.getData().getTags().stream()
.anyMatch(t -> t.getKey().equals(key2) && t.getValue().equals(value2)))
.collect(Collectors.toList());
If you actually want to collect matching tags, you may need a flatMap:
List<myTag> result = responsesList.stream()
.flatMap(d -> d.getData().getTags().stream())
.filter(t -> t.getKey().equals(key1) && t.getValue().equals(value1) ||
t.getKey().equals(key2) && t.getValue().equals(value2))
.collect(Collectors.toList());

Is this the right use of java.util.stream?

Update
Ok, I think I know how to work with streams now. The code in the old post is a mess and I'm not proud of it. So, thank you for you help for directing me in the right direction. I wrote a supplier class, which provides me with items and used static filters and mapper functions:
final TradeSupplier tradeSupplier = new TradeSupplier();
Stream.generate(tradeSupplier).map(TradeSupplier::getPrice)
.map(TradeSupplier::getTradePartner)
.map(TradeSupplier::getTradeInfo)
.filter(TradeSupplier::validateInfo)
.map(TradeSupplier::getPartnerAssetId)
.filter(TradeSupplier::validatePartnerAssetId).forEach(t -> {
if (trade.sendTrade(t)) {
tradeSupplier.finishedItem();
TradeCache.save(t);
}
});
With this design, I don't need flatMap, because it's just an one by one mapping. Additional information is filed into the item, which is just in the stream
I hope, this code is better than the code below... What do you think?
I'm appreciative for any help to improve my understanding of streams :)
Old post
I'm looking for help for the "new" stream api of java 8: first I get a list of items, for every item I collect a list of strings and after that, i want to combine the string with their corresponding item:
input:
item1
item2
wanted output:
item1; string1
item1; string2
item2; string1
item2; string2
item2; string3
Is the following code the right way to use this api?
Code (with stream api)
// input is a list of items
analyst.getInRange(wantMinValue, wantMaxValue)
.stream()
.filter(i -> !haveItem.getName().contains(i.getName())
|| (!haveItem.getModel().contains(i.getModel()) && haveItem
.getQuality() > i.getQuality()))
// get extra information and add it to a list (key: item; value: string)
.collect(Collectors.toMap(s -> s, s -> lounge.getItemId(s)))
.entrySet()
.stream()
// delete all null and empty strings
.filter(e -> e.getValue() != null && !e.getValue().isEmpty())
// for every entry in list, create an list and add to result
.forEach(
e -> {
lounge.getListOfValue(e.getValue(), 1)
.stream()
.filter(s -> s != null && !s.isEmpty())
.map(s -> lounge.getStringFromOldString(s))
.filter(s -> s != null && !s.isEmpty())
.collect(
Collectors
.toCollection(HashSet::new))
// add list to resulting list
.forEach(
s -> {
result.add(new TradeLink(s,
haveItem, e.getKey()));
});
});
First thing: .filter(s -> s != null && !s.isEmpty())
Don't include these things unless these are actually things that can happen. Are empty strings or null strings actually going to come up in your application? (If so, that probably reflects a design flaw in the first place. It may be better to let your application crash, because nulls generally shouldn't be in your program in ways like this.)
Second: don't do the mutable thing you're doing here:
.forEach(
e -> {
lounge.getListOfValue(e.getValue(), 1)
.stream()
.filter(s -> s != null && !s.isEmpty())
.map(s -> lounge.getStringFromOldString(s))
.filter(s -> s != null && !s.isEmpty())
.collect(
Collectors
.toCollection(HashSet::new))
// add list to resulting list
.forEach(
s -> {
result.add(new TradeLink(s,
haveItem, e.getKey()));
});
});
Instead, do something like:
.flatMap(e ->
lounge.getListOfValue(e.getValue(), 1)
.stream()
.map(lounge::getStringFromOldString)
.distinct()
.map(s -> new TradeLink(s, haveItem, e.getKey()))
.collect(toList())

Categories