Apply map() to only a subset of elements in a Java stream? - java

Is it possible, with the Java stream API to apply a map() not on the whole stream (a.k.a. not on every element which is streamed), but only on a set of elements which pass a filter? The filter however should not filter out elements. The result should be a stream of the original elements, but on some of them, a map() has been applied.
Pseudocode:
List<Integer>.stream.filterAndMap(x -> if (x>10) {x+2}).toList();

if … else …
Just return the value unchanged if it does not meet your requirement for modification.
List < Integer > integers = List.of( 1 , 7 , 42 );
List < Integer > modified =
integers
.stream()
.map( integer -> {
if ( integer > 10 ) { return integer + 2; }
else { return integer; }
} )
.toList();
modified.toString() = [1, 7, 44]
Ternary operator
Or shorten that code by using a ternary operator as commented by MC Emperor.
List < Integer > integers = List.of( 1 , 7 , 42 );
List < Integer > modified =
integers
.stream()
.map( integer -> ( integer > 10 ) ? ( integer + 2 ) : integer )
.toList();

See how starting with intention-named pseudo-code pays always off.
Naming
The pseudo-instruction filterAndMap(x -> if (x>10) {x+2}) is violating SRP at least by the name containing "And" to clue two responsibilities.
To resolve with Java Streams-API would suggest:
list.stream()
.filter(predicate) // filter means discarding elements from result
.map(mappingFunction) // map applies to filtered elements only
.toList();
But then the resulting list is filtered by the predicate or condition, hence not having original size.
Rephrase intention: Conditionally Map
Search for [java] conditionally map. Some answers show if statements or ternary operator to implement the conditional.
Still the primary step is map. Inside implement the conditional. Whether using a lambda or a function-reference, this decides how to map:
if (predicate.apply(x)) {
return modify(x);
}
return x; // default: unmodified identity
This is Basil's approach .map( integer -> ( integer > 10 ) ? ( integer + 2 ) : integer ).
Why streams?
Assume some of the list elements stay the same, where only some are modified on condition. Like your requirement states:
stream of the original elements, but on some of them, a map() has been applied.
Wouldn't it be clearer then, to use a for-each loop with conditional modification to have the list elements modified in-place:
List<Integer> intList = List.of(1, 7, 42);
// can also be a intList.forEach with Consumer
for (i : intList) {
if (i > 10) {
i = i + 2;
}
});

Related

Iterating Java Arrays with Streams

So here's a simple algorithmic problem,
Given a list of integers, check if there are two numbers in this list that when added together give eight (8).
Here's my solution,
import java.util.List;
public class Main {
static List<Integer> arrayOne = List.of(1,3,6,9);
static List<Integer> arrayTwo = List.of(1,6,2,10);
static boolean validateArray(int result, List<Integer> array){
for (int i = 0; i<array.size() - 1; i++){
for (int j = i + 1; j < array.size(); j ++){
int value1 = array.get(i);
int value2 = array.get(j);
if(value1 + value2 == result){
return true;
}
}
}
return false;
}
public static void main(String[] args) {
System.out.println(validateArray(8, arrayTwo));
}
}
This works fine. What I'm trying to learn is how to rewrite this code in Java 8. As in what the different options with the loops in Java 8.
If you use a bit different solution using Set<Integer> to find an addition complement into the result, then you can easily convert the solution into Stream API.
Iterative approach
Set<Integer> set = Set.copyOf(array);
for (Integer integer : array) {
if (set.contains(result - integer) && (result != 2 * integer)) {
System.out.printf("Found %s + %s = %s", integer, result - integer, result);
return true;
}
}
System.out.printf("Found found no two numbers their addition would result in in %s%n", result);
return false;
Stream API approach (with logging)
Set<Integer> set = Set.copyOf(array);
return array.stream()
.filter(integer -> set.contains(result - integer) && (result != 2 * integer))
.findFirst()
.map(integer -> {
System.out.printf("Found %s + %s = %s%n", integer, result - integer, result);
return true;
})
.orElseGet(() -> {
System.out.printf("Found found no two numbers their addition would result in in %s%n", result);
return false;
});
And if you don't need to log the results and you care only about the result, the whole stream can be simplified and shortened.
Stream API approach (result only)
Set<Integer> set = Set.copyOf(array);
return array.stream()
.anyMatch(integer -> set.contains(result - integer) && (result != 2 * integer));
Remark:
The algorithm used in all snippets above is simple. You iterate each number in the array and check whether its difference from the result would be a number found in the Set<Integer> of the array (constant look-up: O(1)). To eliminate the currently iterated number (in case the requested result would be 2 * integer), such a check is added. This solution assumes there are no duplicated numbers in the input array. In such a case, the Set<Integer> shall be used instead and there is no need of a conversion.
Regardless of the implementation (streams or loops) performing brute-force iterations over the whole list for each element of the list isn't the best way to solve this problem.
We can index the elements by generating a Map of type Map<Integer,Boolean> (credits to #Holger for this idea), where Keys would represent unique values in the given list and the corresponding boolean Values would denote whether the occurs more than once. Then we can iterate over the Keys of the Map, checking for each key if the corresponding key, which is equal to result - key is present in the Map.
There's one edge case, though, that we need to address:
if result is even and there's a single element in the list, which is equal to result / 2 checking if result - key is present in the map is not sufficient and in this case the Value would be handy to check if associated key has a pair (to construct the target sum).
If you want to use Stream API firstly to generate a Map, you can use of Collector toMap().
Then create a stream over the Keys of the Map and apply anyMatch() operation to obtain the boolean result:
static boolean validateArray(int result, List<Integer> array) {
Map<Integer, Boolean> hasPair = array.stream()
.collect(Collectors.toMap(
Function.identity(), // Key
i -> false, // Value - the element has been encountered for the first time, therefore Value is false
(left, right) -> true // mergeFunction - resolves value of a duplicated Key a true (it has a pair)
));
return hasPair.keySet().stream()
.anyMatch(key -> key * 2 == result ?
hasPair.get(key) : hasPair.containsKey(result - key)
);
}

Check if list of integers contains two groups of different repeated numbers

How to using java stream, check if list of integers contains two groups of different repeated numbers. Number must be repeated not more then two time.
Example: list of 23243.
Answer: true, because 2233
Example 2: list of 23245.
Answer: none
Example 3: list of 23232.
Answer: none, because 222 repeated three times
One more question, how can i return not anyMatch, but the biggest of repeated number?
listOfNumbers.stream().anyMatch(e -> Collections.frequency(listOfNumbers, e) == 2)
This will tell you if the list meets your requirements.
stream the list of digits.
do a frequency count.
stream the resultant counts
filter out those not equal to a count of 2.
and count how many of those there are.
Returns true if final count == 2, false otherwise.
List<Integer> list = List.of(2,2,3,3,3,4,4);
boolean result = list.stream()
.collect(Collectors.groupingBy(a -> a,
Collectors.counting()))
.values().stream().filter(count -> count == 2).limit(2)
.count() >= 2; // fixed per OP's comment
The above prints true since there are two groups of just two digits, namely 2's and 4's
EDIT
First, I made Holger's suggestion to short circuit the count check.
To address your question about returning multiple values, I broke up the process into parts. The first is the normal frequency count that I did before. The next is gathering the information requested. I used a record to return the information. A class would also work. The max count for some particular number is housed in an AbstractMap.SimpleEntry
List<Integer> list = List.of(2, 3, 3, 3, 4, 4, 3, 2, 3);
Results results = groupCheck(list);
System.out.println(results.check);
System.out.println(results.maxEntry);
Prints (getKey() and getValue() may be used to get the individual values. First is the number, second is the occurrences of that number.)
true
3=5
The method and record declaration
record Results(boolean check,
AbstractMap.SimpleEntry<Integer, Long> maxEntry) {
}
Once the frequency count is computed, simply iterate over the entries and
count the pairs and compute the maxEntry by comparing the existing maximum count to the iterated one and update as required.
public static Results groupCheck(List<Integer> list) {
Map<Integer, Long> map = list.stream().collect(
Collectors.groupingBy(a -> a, Collectors.counting()));
AbstractMap.SimpleEntry<Integer, Long> maxEntry =
new AbstractMap.SimpleEntry<>(0, 0L);
int count = 0;
for (Entry<Integer, Long> e : map.entrySet()) {
if (e.getValue() == 2) {
count++;
}
maxEntry = e.getValue() > maxEntry.getValue() ?
new AbstractMap.SimpleEntry<>(e) : maxEntry;
}
return new Results(count >= 2, maxEntry);
}
One could write a method which builds a TreeMap of the frequencies.
What happens here, is that a frequency map is built first (by groupingBy(Function.identity(), Collectors.counting()))), and then we must 'swap' the keys and values, because we want to use the frequencies as keys.
public static TreeMap<Long, List<Integer>> frequencies(List<Integer> list) {
return list.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.collect(Collectors.toMap(e -> e.getValue(), e -> List.of(e.getKey()), (a, b) -> someMergeListsFunction(a, b), TreeMap::new));
}
And then we can just use our method like this:
// We assume the input list is not empty
TreeMap<Long, List<Integer>> frequencies = frequencies(list);
var higher = frequencies.higherEntry(2L);
if (higher != null) {
System.out.printf("There is a number which occurs more than twice: %s (occurs %s times)\n", higher.getValue().get(0), higher.getKey());
}
else {
List<Integer> occurTwice = frequencies.lastEntry().getValue();
if (occurTwice.size() < 2) {
System.out.println("Only " + occurTwice.get(0) " occurs twice...");
}
else {
System.out.println(occurTwice);
}
}
A TreeMap is a Map with keys sorted by some comparator, or the natural order if none is given. The TreeMap class contains methods to search for certain keys. For example, the higherEntry method returns the first entry which is higher than the given key. With this method, you can easily check if a key higher than 2 exists, for one of the requirements is that none of the numbers may occur more than twice.
The above code checks whether there is a number occurring more than twice, that is when higherEntry(2L) returns a nonnull value. Otherwise, lastEntry() is the highest number occurring. With getValue(), you can retrieve the list of these numbers.

Create all possible combinations of elements

I need to create all possible combinations of some kind of Key, that is composed from X (in my case, 8), equally important elements. So i came up with code like this:
final LinkedList<Key> keys = new LinkedList();
firstElementCreator.getApplicableElements() // All creators return a Set of elements
.forEach( first -> secondElementCreator.getApplicableElements()
.forEach( second -> thirdElementCreator.getApplicableElements()
// ... more creators
.forEach( X -> keys.add( new Key( first, second, third, ..., X ) ) ) ) ) ) ) ) );
return keys;
and it's working, but there is X nested forEach and i have feeling that i'm missing out an easier/better/more elegant solution. Any suggestions?
Thanks in advance!
Is it Cartesian Product? Many libraries provide the API, for example: Sets and Lists in Guava:
List<ApplicableElements> elementsList = Lists.newArrayList(firstElementCreator, secondElementCreator...).stream()
.map(c -> c.getApplicableElements()).collect(toList());
List<Key> keys = Lists.cartesianProduct(elementsList).stream()
.map(l -> new Key(l.get(0), l.get(1), l.get(2), l.get(3), l.get(4), l.get(5), l.get(6), l.get(7))).collect(toList());
Since the number of input sets is fixed (it has to match the number of arguments in the Key constructor), your solution is actually not bad.
It's more efficient and easier to read without the lambdas, though, like:
for (Element first : firstElementCreator.getApplicableElements()) {
for (Element second : secondElementCreator.getApplicableElements()) {
for (Element third : thirdElementCreator.getApplicableElements()) {
keys.add(new Key(first, second, third));
}
}
}
The canonical solution is to use flatMap. However, the tricky part is to create the Key object from the multiple input levels.
The straight-forward approach is to do the evaluation in the innermost function, where every value is in scope
final List<Key> keys = firstElementCreator.getApplicableElements().stream()
.flatMap(first -> secondElementCreator.getApplicableElements().stream()
.flatMap(second -> thirdElementCreator.getApplicableElements().stream()
// ... more creators
.map( X -> new Key( first, second, third, ..., X ) ) ) )
.collect(Collectors.toList());
but this soon becomes impractical with deep nesting
A solution without deep nesting requires elements to hold intermediate compound values. E.g. if we define Key as
class Key {
String[] data;
Key(String... arg) {
data=arg;
}
public Key add(String next) {
int pos = data.length;
String[] newData=Arrays.copyOf(data, pos+1);
newData[pos]=next;
return new Key(newData);
}
#Override
public String toString() {
return "Key("+Arrays.toString(data)+')';
}
}
(assuming String as element type), we can use
final List<Key> keys =
firstElementCreator.getApplicableElements().stream().map(Key::new)
.flatMap(e -> secondElementCreator.getApplicableElements().stream().map(e::add))
.flatMap(e -> thirdElementCreator.getApplicableElements().stream().map(e::add))
// ... more creators
.collect(Collectors.toList());
Note that these flatMap steps are now on the same level, i.e. not nested anymore. Also, all these steps are identical, only differing in the actual creator, which leads to the general solution supporting an arbitrary number of Creator instances.
List<Key> keys = Stream.of(firstElementCreator, secondElementCreator, thirdElementCreator
/* , and, some, more, if you like */)
.map(creator -> (Function<Key,Stream<Key>>)
key -> creator.getApplicableElements().stream().map(key::add))
.reduce(Stream::of, (f1,f2) -> key -> f1.apply(key).flatMap(f2))
.apply(new Key())
.collect(Collectors.toList());
Here, every creator is mapping to the identical stream-producing function of the previous solution, then all are reduced to a single function combining each function with a flatMap step to the next one, and finally the resulting function is executed to get a stream, which is then collected to a List.

Multiply the occurence of each element in a list by 4

I am trying to achieve the following scenario.
I have an oldList and I am trying to multiply the occurences of each element by 4 and put them in a newList by using the Stream API. The size of the oldList is not known and each time, it may appear with a different size.
I have already solved this problem with two traditional loops as follows;
private List< Integer > mapHourlyToQuarterlyBased( final List< Integer > oldList )
{
List< Integer > newList = new ArrayList<>();
for( Integer integer : oldList )
{
for( int i = 0; i < 4; i++ )
{
newList.add( integer );
}
}
return newList;
}
but I have learnt the Stream API newly and would like to use it to consolidate my knowledge.
You could use a flatMap to produce a Stream of 4 elements from each element of the original List and then generate a single Stream of all these elements.
List<Integer> mapHourlyToQuarterlyBased =
oldList.stream()
.flatMap(i -> Collections.nCopies(4, i).stream())
.collect(Collectors.toList());
You can acheive that using flatMap:
List<Integer> result = list.stream().flatMap(i -> Stream.of(i,i,i,i)).collect(Collectors.toList());
or in a more generic way:
List<Integer> result = list.stream().flatMap(i -> Stream.generate(() -> i).limit(4)).collect(Collectors.toList());
For each element in the input list, this creates a stream consisting of this element repeated 4 times and flat maps it. All the elements are then collected into a list.

Java8 filter collect both type of value

Is there a way to collect both filtered and not filtered value in java 8 filter ?
One way is:
.filter( foo -> {
if(!foo.apply()){
// add to required collection
}
return foo.apply();
}
Is there a better alternative ?
Map<Boolean, List<Foo>> map =
collection.stream().collect(Collectors.partitioningBy(foo -> foo.isBar());
You can use a ternary operator with map, so that the function you apply is either the identity for some condition, In below example I calculating square of even numbers and keeping odd numbers as it is.
List<Integer> temp = arrays.stream()
.map(i -> i % 2 == 0 ? i*i : i)
.collect(Collectors.toList());
In your case it would be like this :
List<Integer> temp = arrays.stream()
.map(!foo.apply() -> ? doSomething: doSomethingElse)
.collect(Collectors.toList());

Categories