public static int construction(String myString) {
Set<Character> set = new HashSet<>();
int count = myString.chars() // returns IntStream
.mapToObj(c -> (char)c) // Stream<Character> why is this required?
.mapToInt(c -> (set.add(c) == true ? 1 : 0)) // IntStream
.sum();
return count;
}
The above code will not compile without:
.mapObj(c -> (char)c)
// <Character> Stream<Character> java.util.stream.IntStream.mapToObj(IntFunction<? extends Character> mapper)
If i remove it, I get the following error
The method mapToInt((<no type> c) -> {}) is undefined for the type IntStream
Can someone explain this? It seems like I am starting with and IntStream, converting to a Stream of Characters and then back to IntStream.
The method CharSequence::chars returns the IntStream, which of course doesn't provide any method converting to int, such as mapToInt, but mapToObj instead. Therefore the method IntStream::map(IntUnaryOperator mapper) which both takes returns int as well shall be used since IntUnaryOperator does the same like Function<Integer, Integer> or UnaryOperator<Integer>:
int count = myString.chars() // IntStream
.map(c -> (set.add((char) c) ? 1 : 0)) // IntStream
.sum();
long count = myString.chars() // IntStream
.filter(c -> set.add((char) c)) // IntStream
.count();
Also, using Set<Integer> helps you to avoid conversion to a Character:
Set<Integer> set = new HashSet<>();
int count = myString.chars() // IntStream
.map(c -> (set.add(c) ? 1 : 0)) // IntStream
.sum();
long count = myString.chars() // IntStream
.filter(set::add) // IntStream
.count();
However, regardless of what you try to achieve, your code is wrong by principle. See the Stateless behaviors. Consider using the following snippet which lambda expressions' results are not dependent on the result of a non-deterministic operation, such as Set::add.
Stream pipeline results may be nondeterministic or incorrect if the behavioral parameters to the stream operations are stateful.
long count = myString.chars() // IntStream
.distinct() // IntStream
.count();
You can also collect to a set and then take the size without using an explicit map.
It does not require using external state to contain the characters.
long count = str.chars().boxed().collect(Collectors.toSet()).size();
But imho, the more direct approach which was already mentioned is cleaner in appearance and the one I would prefer to use.
long count = str.chars().distinct().count();
Because String.chars() is already returning an IntStream and IntStream does not have mapToInt function
You could use a filter instead then count:
int count = myString.chars()
.filter(c -> set.add(c) == true)
.count();
I admit that I made this so slubby last midnight!
As mentioned by the comments, here is the required fixes.
Thank you for mentioning.
long count = myString.chars()
.filter(c -> set.add((char)c))
.count();
Related
I'm trying to write a method that will validate String. If string has same amount of every char like "aabb", "abcabc", "abc" it is valid or if contains one extra symbol like "ababa" or "aab" it is also valid other cases - invalid.
Update: sorry, I forget to mention such cases like abcabcab -> a-3, b-3, c-2 -> 2 extra symbols (a, b) -> invalid. And my code doesn't cover such cases.
Space is a symbol, caps letters are different from small letters. Now I have this, but it looks ambiguous (especially last two methods):
public boolean validate(String line) {
List<Long> keys = countMatches(countChars(line));
int matchNum = keys.size();
if (matchNum < 2) return true;
return matchNum == 2 && Math.abs(keys.get(0) - keys.get(1)) == 1;
}
Counting unique symbols entry I'd wish to get List<long>, but I don't know how:
private Map<Character, Long> countChars(String line) {
return line.chars()
.mapToObj(c -> (char) c)
.collect(groupingBy(Function.identity(), HashMap::new, counting()));
}
private List<Long> countMatches(Map<Character, Long> countedEntries) {
return new ArrayList<>(countedEntries.values()
.stream()
.collect(groupingBy(Function.identity(), HashMap::new, counting()))
.keySet());
}
How can I optimize a method above? I need just List<Long>, but have to create a map.
As I could observe, you are looking for distinct frequencies using those two methods. You can merge that into one method to use a single stream pipeline as below :
private List<Long> distinctFrequencies(String line) {
return line.chars().mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(Function.identity(),
Collectors.counting()))
.values().stream()
.distinct()
.collect(Collectors.toList());
}
Of course, all you need to change in your validate method now is the assignment
List<Long> keys = distinctFrequencies(line);
With some more thought around it, if you wish to re-use the API Map<Character, Long> countChars somewhere else as well, you could have modified the distinct frequencies API to use it as
private List<Long> distinctFrequencies(String line) {
return countChars(line).values()
.stream()
.distinct()
.collect(Collectors.toList());
}
you could perform an evaluation if every char in a string has the same occurence count using the stream api like this:
boolean valid = "aabbccded".chars()
.boxed()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.values().stream()
.reduce((a, b) -> a == b ? a : -1L)
.map(v -> v > 0)
.get();
EDIT:
after reading the comments, I now believe to have understood the requirement.
a string is considered valid if all chars in it have the same occurrence count like aabb
or if there is a single extra character like abb
the string abcabcab is invalid as it has 3a 3b and 2c and thus, it has 1
extra a and 1 extra b, that is too much. hence, you can't perform the validation with a frequency list, you need additional information about how often the char lengths differ -> Map
here is a new trial:
TreeMap<Long, Long> map = "abcabcab".chars()
.boxed()
.collect(groupingBy(Function.identity(), counting()))
.values().stream()
.collect(groupingBy(Function.identity(), TreeMap::new, counting()));
boolean valid = map.size() == 1 || // there is only a single char length
( map.size() == 2 && // there are two and there is only 1 extra char
((map.lastKey() - map.firstKey()) * map.lastEntry().getValue() <= 1));
the whole validation could be executed in a single statement by using the Collectors.collectingAndThen method that #Nikolas used in his answer or you could use a reduction as well:
boolean valid = "aabcc".chars()
.boxed()
.collect(groupingBy(Function.identity(), counting()))
.values().stream()
.collect(groupingBy(Function.identity(), TreeMap::new, counting()))
.entrySet().stream()
.reduce((min, high) -> {
min.setValue((min.getKey() - high.getKey()) * high.getValue()); // min.getKey is the min char length
return min; // high.getKey is a higher char length
// high.getValue is occurrence count of higher char length
}) // this is always negative
.map(min -> min.getValue() >= -1)
.get();
Use Collector.collectingAndThen that is a collector that uses a downstream Collector and finisher Function that maps the result.
Use the Collectors.groupingBy and Collectors.counting to get the frequency of each character in the String.
// Results in Map<Integer, Long>
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting())
Use the map -> new HashSet<>(map.values()).size() == 1 that checks whether all frequencies are equal - if so, there is one distinct value.
Wrapping these two in Collector.collectingAndThen looks like:
String line = "aabbccdeed";
boolean isValid = line.chars() // IntStream of characters
.boxed() // boxed as Stream<Integer>
.collect(Collectors.collectingAndThen( // finisher's result type
Collectors.groupingBy( // grouped Map<Integer, Integer>
Function.identity(), // ... of each character
Collectors.counting()), // ... frequency
map -> new HashSet<>(map.values()).size() == 1 // checks the frequencies
));
// aabbccded -> false
// aabbccdeed -> true
You can do like this:
first count every character occurrence.
then find min value for occurrence.
and at the last step sum all values that the difference with the smallest value(minValue) is less than or equal to one.
public static boolean validate(String line) {
Map<Character, Long> map = line.chars()
.mapToObj(c -> (char) c)
.collect(groupingBy(Function.identity(), Collectors.counting()));
long minValue = map.values().stream().min(Long::compareTo).orElse(0l);
return map.values().stream().mapToLong(a -> Math.abs(a - minValue)).sum() <= 1;
}
I want to convert a List<String> to an IntStream. Suppose that my list is like ["abc", "de", "fghi"]. Then the IntStream that I want is like 1,1,1,2,2,3,3,3,3. (The number of occurrences of a number i in the IntStream depends on the length of ith string in the given list)
I wrote the following method for that (it won't compile):
private static IntStream getSingleIntStream(List<String> list) {
final AtomicInteger atomicInteger = new AtomicInteger();
return list.stream()
.map(str -> {
atomicInteger.incrementAndGet();
return IntStream.range(0, str.length())
.map(i -> atomicInteger.get());
}); // Now I don't understand how can I convert this Stream<IntStream> to a single IntStream
}
But I don't understand how can I convert can I convert a Stream<IntStream> to a single IntStream. (My guess is that we can use flatMap somehow, but I don't exactly get how to use it.)
I'm using IntStream instead of Stream<Integer> to avoid auto-boxing and make my whole system more efficient.
The other solution would be like this:
IntStream result = IntStream.range(0,list.size())
.flatMap(i-> IntStream.range(0, list.get(i)
.length())
.map(j->i+1));
Expanding over the comment of #JB_Nizet
The method that I need to use is flatMapToInt.
private static IntStream getSingleIntStream(List<String> list) {
final AtomicInteger atomicInteger = new AtomicInteger();
return list.stream()
.flatMapToInt(str -> {
atomicInteger.incrementAndGet();
return IntStream.range(0, str.length())
.map(i -> atomicInteger.get());
});
}
The solution would be to use flatMapToInt:
private static IntStream getSingleIntStream(List<String> list) {
final AtomicInteger atomicInteger = new AtomicInteger();
return list.stream()
.flatMapToInt(str -> {
atomicInteger.incrementAndGet();
return IntStream.range(0, str.length())
.map(i -> atomicInteger.get());
});
}
but I'd rethink what you'd like to achieve here. Now, each String in the initial list will be replaced with a number taken from AtomicInteger (and this will be repeated String.length() times for each String):
getSingleIntStream(Arrays.asList("a", "bc")).forEach(System.out::println); // 1 2 2
I assume you wanted to number each char from every String:
private static IntStream getSingleIntStream(List<String> list) {
AtomicInteger atomicInteger = new AtomicInteger();
return list.stream()
.flatMapToInt(str -> IntStream.range(0, str.length())
.map(i -> atomicInteger.incrementAndGet()));
}
// 1 2 3
You are absolutely correct about flatMap, as Hadi J already shows in a good answer. I just wanted to offer my variant of the same:
private static IntStream getSingleIntStream(List<String> list) {
return IntStream.rangeClosed(1, list.size())
.flatMap(i -> list.get(i - 1).chars().map(ch -> i));
}
You may find this version more natural or concise. In any case both versions have the advantage of avoiding the AtomicInteger and the side effect of the stream pipeline on it. A stream pipeline should be free from side effects.
Let’s also see it in action:
List<String> list = List.of("abc", "de", "fghi");
int[] nums = getSingleIntStream(list).toArray();
System.out.println(Arrays.toString(nums));
[1, 1, 1, 2, 2, 3, 3, 3, 3]
When you need the index of an element inside a stream, the general trick is to start out from an IntStream of the indices and inside your stream pipeline make the lookup of the elements from the indices (list.get(i - 1)). In this case I am (unconventionally) using 1-based indices because you wanted your resulting numbers to start from 1. So we need to subtract 1 in the list lookup.
I have an attribute this.sudoku which is a int[9][9] array.
I need to get a column of this into a set.
Set<Integer> sudoku_column = IntStream.range(0, 9)
.map(i -> this.sudoku[i][column])
.collect(Collectors.toSet());
I expect a columns values in this set. but it says that Collectors.toSet() cannot be applied to this collect function in the chain. Can someone explain why?
IntStream#map consumes an IntUnaryOperator which represents an operation on a single int-valued operand that produces an int-valued result thus the result is an IntStream, however IntStream does not have the collect overload you're attempt to use, which means you have a couple of options; i.e. either use IntStream#collect:
IntStream.range(0, 9)
.collect(HashSet::new, (c, i) -> c.add(sudoku[i][column]), HashSet::addAll);
or use mapToObj to transform from IntStream to Stream<Integer> which you can then call .collect(Collectors.toSet()) upon.
IntStream.range(0, 9)
.mapToObj(i -> this.sudoku[i][column])
.collect(Collectors.toSet());
IntStream#map takes an IntUnaryOperator which is a function to transform an int to another int.
It's fine if you want to continue with an IntStream. But if you need to collect the stream into a Set<Integer>, you need to turn your IntStream into a stream of boxed ints, a Stream<Integer>, by IntStream#boxed.
.map(i -> this.sudoku[i][column])
.boxed()
.collect(Collectors.toSet());
Collectors.toSet() cannot be applied to this collect function in the chain
Collectors.toSet() return a Collector which doesn't fit the signature of the single collect(Supplier, ObjIntConsumer, BiConsumer) method in IntStream. Though, it's suitable for Stream.collect(Collector).
I am trying to process a stream of Integers and collect the integers that match a predicate (via the compare() function) into a list. Here's a rough outline of the code I've written.
private List<Integer> process() {
Z z = f(-1);
return IntStream.range(0, 10)
.filter(i -> compare(z, f(i)))
.collect(Collectors.toCollection(ArrayList::new)); // Error on this line
}
private boolean compare(Z z1, Z z2) { ... }
private Z f(int i) { ... }
Unfortunately my solution does not compile and I cannot make sense of the compiler error for the line highlighted:
The method collect(Supplier<R>, ObjIntConsumer<R>, BiConsumer<R,R>) in the type IntStream is not applicable for the arguments (Collector<Object,capture#1-of ?,Collection<Object>>)
Any suggestions?
IntStream doesn't contain a collect method that accepts a single argument of type Collector. Stream does. Therefore you have to convert your IntStream to a Stream of objects.
You can either box the IntStream into a Stream<Integer> or use mapToObj to achieve the same.
For example:
return IntStream.range(0, 10)
.filter(i -> compare(z, f(i)))
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
boxed() will return a Stream consisting of the elements of this stream, each boxed to an Integer.
or
return IntStream.range(0, 10)
.filter(i -> compare(z, f(i)))
.mapToObj(Integer::valueOf)
.collect(Collectors.toCollection(ArrayList::new));
Or you can specify the Supplier, Accumulator and Combiner yourself:
IntStream.range(0, 10)
.filter(i -> compare(z, f(i)))
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
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());