Related
Is there a Java 8 stream operation that limits a (potentially infinite) Stream until the first element fails to match a predicate?
In Java 9 we can use takeWhile as in the example below to print all the numbers less than 10.
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
As there is no such operation in Java 8, what's the best way of implementing it in a general way?
Operations takeWhile and dropWhile have been added to JDK 9. Your example code
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
will behave exactly as you expect it to when compiled and run under JDK 9.
JDK 9 has been released. It is available for download here: JDK 9 Releases.
Such an operation ought to be possible with a Java 8 Stream, but it can't necessarily be done efficiently -- for example, you can't necessarily parallelize such an operation, as you have to look at elements in order.
The API doesn't provide an easy way to do it, but what's probably the simplest way is to take Stream.iterator(), wrap the Iterator to have a "take-while" implementation, and then go back to a Spliterator and then a Stream. Or -- maybe -- wrap the Spliterator, though it can't really be split anymore in this implementation.
Here's an untested implementation of takeWhile on a Spliterator:
static <T> Spliterator<T> takeWhile(
Spliterator<T> splitr, Predicate<? super T> predicate) {
return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
boolean stillGoing = true;
#Override public boolean tryAdvance(Consumer<? super T> consumer) {
if (stillGoing) {
boolean hadNext = splitr.tryAdvance(elem -> {
if (predicate.test(elem)) {
consumer.accept(elem);
} else {
stillGoing = false;
}
});
return hadNext && stillGoing;
}
return false;
}
};
}
static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
}
allMatch() is a short-circuiting function, so you can use it to stop processing. The main disadvantage is that you have to do your test twice: once to see if you should process it, and again to see whether to keep going.
IntStream
.iterate(1, n -> n + 1)
.peek(n->{if (n<10) System.out.println(n);})
.allMatch(n->n < 10);
As a follow-up to #StuartMarks answer. My StreamEx library has the takeWhile operation which is compatible with current JDK-9 implementation. When running under JDK-9 it will just delegate to the JDK implementation (via MethodHandle.invokeExact which is really fast). When running under JDK-8, the "polyfill" implementation will be used. So using my library the problem can be solved like this:
IntStreamEx.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
takeWhile is one of the functions provided by the protonpack library.
Stream<Integer> infiniteInts = Stream.iterate(0, i -> i + 1);
Stream<Integer> finiteInts = StreamUtils.takeWhile(infiniteInts, i -> i < 10);
assertThat(finiteInts.collect(Collectors.toList()),
hasSize(10));
Update: Java 9 Stream now comes with a takeWhile method.
No needs for hacks or other solutions. Just use that!
I am sure this can be greatly improved upon:
(someone could make it thread-safe maybe)
Stream<Integer> stream = Stream.iterate(0, n -> n + 1);
TakeWhile.stream(stream, n -> n < 10000)
.forEach(n -> System.out.print((n == 0 ? "" + n : "," + n)));
A hack for sure... Not elegant - but it works ~:D
class TakeWhile<T> implements Iterator<T> {
private final Iterator<T> iterator;
private final Predicate<T> predicate;
private volatile T next;
private volatile boolean keepGoing = true;
public TakeWhile(Stream<T> s, Predicate<T> p) {
this.iterator = s.iterator();
this.predicate = p;
}
#Override
public boolean hasNext() {
if (!keepGoing) {
return false;
}
if (next != null) {
return true;
}
if (iterator.hasNext()) {
next = iterator.next();
keepGoing = predicate.test(next);
if (!keepGoing) {
next = null;
}
}
return next != null;
}
#Override
public T next() {
if (next == null) {
if (!hasNext()) {
throw new NoSuchElementException("Sorry. Nothing for you.");
}
}
T temp = next;
next = null;
return temp;
}
public static <T> Stream<T> stream(Stream<T> s, Predicate<T> p) {
TakeWhile tw = new TakeWhile(s, p);
Spliterator split = Spliterators.spliterator(tw, Integer.MAX_VALUE, Spliterator.ORDERED);
return StreamSupport.stream(split, false);
}
}
You can use java8 + rxjava.
import java.util.stream.IntStream;
import rx.Observable;
// Example 1)
IntStream intStream = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
.takeWhile(n ->
{
System.out.println(n);
return n < 10;
}
).subscribe() ;
// Example 2
IntStream intStream = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
.takeWhile(n -> n < 10)
.forEach( n -> System.out.println(n));
Actually there are 2 ways to do it in Java 8 without any extra libraries or using Java 9.
If you want to print numbers from 2 to 20 on the console you can do this:
IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).allMatch(i -> i < 20);
or
IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).anyMatch(i -> i >= 20);
The output is in both cases:
2
4
6
8
10
12
14
16
18
20
No one mentioned anyMatch yet. This is the reason for this post.
This is the source copied from JDK 9 java.util.stream.Stream.takeWhile(Predicate). A little difference in order to work with JDK 8.
static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> p) {
class Taking extends Spliterators.AbstractSpliterator<T> implements Consumer<T> {
private static final int CANCEL_CHECK_COUNT = 63;
private final Spliterator<T> s;
private int count;
private T t;
private final AtomicBoolean cancel = new AtomicBoolean();
private boolean takeOrDrop = true;
Taking(Spliterator<T> s) {
super(s.estimateSize(), s.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED));
this.s = s;
}
#Override
public boolean tryAdvance(Consumer<? super T> action) {
boolean test = true;
if (takeOrDrop && // If can take
(count != 0 || !cancel.get()) && // and if not cancelled
s.tryAdvance(this) && // and if advanced one element
(test = p.test(t))) { // and test on element passes
action.accept(t); // then accept element
return true;
} else {
// Taking is finished
takeOrDrop = false;
// Cancel all further traversal and splitting operations
// only if test of element failed (short-circuited)
if (!test)
cancel.set(true);
return false;
}
}
#Override
public Comparator<? super T> getComparator() {
return s.getComparator();
}
#Override
public void accept(T t) {
count = (count + 1) & CANCEL_CHECK_COUNT;
this.t = t;
}
#Override
public Spliterator<T> trySplit() {
return null;
}
}
return StreamSupport.stream(new Taking(stream.spliterator()), stream.isParallel()).onClose(stream::close);
}
Here is a version done on ints - as asked in the question.
Usage:
StreamUtil.takeWhile(IntStream.iterate(1, n -> n + 1), n -> n < 10);
Here's code for StreamUtil:
import java.util.PrimitiveIterator;
import java.util.Spliterators;
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
public class StreamUtil
{
public static IntStream takeWhile(IntStream stream, IntPredicate predicate)
{
return StreamSupport.intStream(new PredicateIntSpliterator(stream, predicate), false);
}
private static class PredicateIntSpliterator extends Spliterators.AbstractIntSpliterator
{
private final PrimitiveIterator.OfInt iterator;
private final IntPredicate predicate;
public PredicateIntSpliterator(IntStream stream, IntPredicate predicate)
{
super(Long.MAX_VALUE, IMMUTABLE);
this.iterator = stream.iterator();
this.predicate = predicate;
}
#Override
public boolean tryAdvance(IntConsumer action)
{
if (iterator.hasNext()) {
int value = iterator.nextInt();
if (predicate.test(value)) {
action.accept(value);
return true;
}
}
return false;
}
}
}
Go to get library abacus-common. It provides the exact API you want and more:
IntStream.iterate(1, n -> n + 1).takeWhile(n -> n < 10).forEach(System.out::println);
Declaration: I'm the developer of AbacusUtil.
If you know the exact amount of repititions that will be performed, you can do
IntStream
.iterate(1, n -> n + 1)
.limit(10)
.forEach(System.out::println);
IntStream.iterate(1, n -> n + 1)
.peek(System.out::println) //it will be executed 9 times
.filter(n->n>=9)
.findAny();
instead of peak you can use mapToObj to return final object or message
IntStream.iterate(1, n -> n + 1)
.mapToObj(n->{ //it will be executed 9 times
if(n<9)
return "";
return "Loop repeats " + n + " times";});
.filter(message->!message.isEmpty())
.findAny()
.ifPresent(System.out::println);
You can't abort a stream except by a short-circuiting terminal operation, which would leave some stream values unprocessed regardless of their value. But if you just want to avoid operations on a stream you can add a transform and filter to the stream:
import java.util.Objects;
class ThingProcessor
{
static Thing returnNullOnCondition(Thing thing)
{ return( (*** is condition met ***)? null : thing); }
void processThings(Collection<Thing> thingsCollection)
{
thingsCollection.stream()
*** regular stream processing ***
.map(ThingProcessor::returnNullOnCondition)
.filter(Objects::nonNull)
*** continue stream processing ***
}
} // class ThingProcessor
That transforms the stream of things to nulls when the things meet some condition, then filters out nulls. If you're willing to indulge in side effects, you could set the condition value to true once some thing is encountered, so all subsequent things are filtered out regardless of their value. But even if not you can save a lot of (if not quite all) processing by filtering values out of the stream that you don't want to process.
Even I was having a similar requirement -- invoke the web-service, if it fails, retry it 3 times. If it fails even after these many trials, send an email notification. After googling a lot, anyMatch() came as a saviour. My sample code as follows. In the following example, if webServiceCall method returns true in the first iteration itself, stream does not iterate further as we have called anyMatch(). I believe, this is what you are looking for.
import java.util.stream.IntStream;
import io.netty.util.internal.ThreadLocalRandom;
class TrialStreamMatch {
public static void main(String[] args) {
if(!IntStream.range(1,3).anyMatch(integ -> webServiceCall(integ))){
//Code for sending email notifications
}
}
public static boolean webServiceCall(int i){
//For time being, I have written a code for generating boolean randomly
//This whole piece needs to be replaced by actual web-service client code
boolean bool = ThreadLocalRandom.current().nextBoolean();
System.out.println("Iteration index :: "+i+" bool :: "+bool);
//Return success status -- true or false
return bool;
}
If you have different problem, different solution may be needed but for your current problem, I would simply go with:
IntStream
.iterate(1, n -> n + 1)
.limit(10)
.forEach(System.out::println);
Might be a bit off topic but this is what we have for List<T> rather than Stream<T>.
First you need to have a take util method. This methods takes first n elements:
static <T> List<T> take(List<T> l, int n) {
if (n <= 0) {
return newArrayList();
} else {
int takeTo = Math.min(Math.max(n, 0), l.size());
return l.subList(0, takeTo);
}
}
it just works like scala.List.take
assertEquals(newArrayList(1, 2, 3), take(newArrayList(1, 2, 3, 4, 5), 3));
assertEquals(newArrayList(1, 2, 3), take(newArrayList(1, 2, 3), 5));
assertEquals(newArrayList(), take(newArrayList(1, 2, 3), -1));
assertEquals(newArrayList(), take(newArrayList(1, 2, 3), 0));
now it will be fairly simple to write a takeWhile method based on take
static <T> List<T> takeWhile(List<T> l, Predicate<T> p) {
return l.stream().
filter(p.negate()).findFirst(). // find first element when p is false
map(l::indexOf). // find the index of that element
map(i -> take(l, i)). // take up to the index
orElse(l); // return full list if p is true for all elements
}
it works like this:
assertEquals(newArrayList(1, 2, 3), takeWhile(newArrayList(1, 2, 3, 4, 3, 2, 1), i -> i < 4));
this implementation iterate the list partially for a few times but it won't add add O(n^2) operations. Hope that's acceptable.
I have another quick solution by implementing this (which is rly unclean in fact, but you get the idea):
public static void main(String[] args) {
System.out.println(StreamUtil.iterate(1, o -> o + 1).terminateOn(15)
.map(o -> o.toString()).collect(Collectors.joining(", ")));
}
static interface TerminatedStream<T> {
Stream<T> terminateOn(T e);
}
static class StreamUtil {
static <T> TerminatedStream<T> iterate(T seed, UnaryOperator<T> op) {
return new TerminatedStream<T>() {
public Stream<T> terminateOn(T e) {
Builder<T> builder = Stream.<T> builder().add(seed);
T current = seed;
while (!current.equals(e)) {
current = op.apply(current);
builder.add(current);
}
return builder.build();
}
};
}
}
Here is my attempt using just Java Stream library.
IntStream.iterate(0, i -> i + 1)
.filter(n -> {
if (n < 10) {
System.out.println(n);
return false;
} else {
return true;
}
})
.findAny();
Im using java 8. I have a class Operator which has 3 fields.
class Operator{
private String type;
private boolean updateRequested;
private boolean deleteRequested;
}
I have list of Operator. I just want to count the updatedRequested and deleteRequested based on type whose value is true and add into the Map which is Map<String,Result>
class Result{
private int deleteReqCount;
private int updateReqCount;
}
Expected result
{
"Cricket":{ deleteReqCount:10, updateReqCount:0}, // count only the value == `true`
"Football":{ deleteReqCount:2, updateReqCount:10}, // count only the value == `true`
}
This question is bit simple and I did using for loops and if condition. But I'm impressed with Stream apis and Collectors framework. I'm a beginner, so tried list.stream().collect(Collectors.groupingBy(g -> g.getType())); but couldn't go further.
Thanks in advance
Here is an implementation using Collectors.toMap
class Operator {
public String type;
public boolean updateRequested;
public boolean deleteRequested;
Operator(String type, boolean updateRequested, boolean deleteRequested) {
this.type = type;
this.updateRequested = updateRequested;
this.deleteRequested = deleteRequested;
}
}
class Result {
public int deleteReqCount;
public int updateReqCount;
Result(int deleteReqCount, int updateReqCount) {
this.deleteReqCount = deleteReqCount;
this.updateReqCount = updateReqCount;
}
#Override
public String toString() {
return "Result{" +
"deleteReqCount=" + deleteReqCount +
", updateReqCount=" + updateReqCount +
'}';
}
}
Map<String, Result> solve(List<Operator> operatorList) {
return operatorList.stream()
.collect(Collectors.toMap(
v -> v.type,
v -> new Result(v.deleteRequested ? 1 : 0, v.updateRequested ? 1 : 0),
(result, result2) -> {
int deleteReqCount = result.deleteReqCount + result2.deleteReqCount;
int updateReqCount = result.updateReqCount + result2.updateReqCount;
return new Result(deleteReqCount, updateReqCount);
}
));
}
List<Operator> operatorList = Arrays.asList(
new Operator("cricket", true, true),
new Operator("cricket", true, false),
new Operator("cricket", true, true),
new Operator("soccer", false, true),
new Operator("soccer", true, true)
);
System.out.println(solve(operatorList));
Output:
{soccer=Result{deleteReqCount=2, updateReqCount=1}, cricket=Result{deleteReqCount=2, updateReqCount=3}}
An update to Sai Kiran's excellent answer:
Map<String, Result> solve(List<Operator> operatorList) {
return operatorList.stream()
.collect(Collectors.toMap(
v -> v.type,
v -> new Result(v.deleteRequested ? 1 : 0, v.updateRequested ? 1 : 0),
(result, result2) -> {
result.updateReqCount += result2.updateReqCount;
result.deleteReqCount += result2.deleteReqCount;
return result;
}
));
}
As per comment by #Andreas,
operators.stream().collect(Collectors.toMap(
Operator::getType,
v -> new Result(v.isDeleteRequested() ? 1 : 0, v.isUpdateRequested() ? 1 : 0),
Result::merge
));
Merge method,
public Result merge(Result result) {
this.updateReqCount+=result.updateReqCount;
this.deleteReqCount+=result.deleteReqCount;
return this;
}
For the time when you update to Java 12 or higher, the task can be achieved using Collectors.teeing :
List<Operator> ops = //your operators list
Map<String, Result> myMap =
ops.stream()
.collect(
Collectors.groupingBy(Operator::getType,
Collectors.teeing(
Collectors.filtering(Operator::isUpdateRequested,Collectors.counting()),
Collectors.filtering(Operator::isDeleteRequested,Collectors.counting()),
(updateCount, deleteCount) -> {
return new Result(updateCount.intValue(),deleteCount.intValue());
}
)));
Map structure and data is given below
Map<String, BigDecimal>
A, 12
B, 23
C, 67
D, 99
Now i want to group values in range, output has range as key and number of elements there as value. Like below:
0-25, 2
26-50, 0
51-75, 1
76-100, 1
How can we do this using java streams ?
You can do it like that:
public class MainClass {
public static void main(String[] args) {
Map<String, BigDecimal> aMap=new HashMap<>();
aMap.put("A",new BigDecimal(12));
aMap.put("B",new BigDecimal(23));
aMap.put("C",new BigDecimal(67));
aMap.put("D",new BigDecimal(99));
Map<String, Long> o = aMap.entrySet().stream().collect(Collectors.groupingBy( a ->{
//Do the logic here to return the group by function
if(a.getValue().compareTo(new BigDecimal(0))>0 &&
a.getValue().compareTo(new BigDecimal(25))<0)
return "0-25";
if(a.getValue().compareTo(new BigDecimal(26))>0 &&
a.getValue().compareTo(new BigDecimal(50))<0)
return "26-50";
if(a.getValue().compareTo(new BigDecimal(51))>0 &&
a.getValue().compareTo(new BigDecimal(75))<0)
return "51-75";
if(a.getValue().compareTo(new BigDecimal(76))>0 &&
a.getValue().compareTo(new BigDecimal(100))<0)
return "76-100";
return "not-found";
}, Collectors.counting()));
System.out.print("Result="+o);
}
}
Result is : Result={0-25=2, 76-100=1, 51-75=1}
I couldn't find a better way to do that check for big decimals but you can think about how to improve it :) Maybe make an external method that does that trick
You may use a solution for regular ranges, e.g.
BigDecimal range = BigDecimal.valueOf(25);
inputMap.values().stream()
.collect(Collectors.groupingBy(
bd -> bd.subtract(BigDecimal.ONE).divide(range, 0, RoundingMode.DOWN),
TreeMap::new, Collectors.counting()))
.forEach((group,count) -> {
group = group.multiply(range);
System.out.printf("%3.0f - %3.0f: %s%n",
group.add(BigDecimal.ONE), group.add(range), count);
});
which will print:
1 - 25: 2
51 - 75: 1
76 - 100: 1
(not using the irregular range 0 - 25)
or a solution with explicit ranges:
TreeMap<BigDecimal,String> ranges = new TreeMap<>();
ranges.put(BigDecimal.ZERO, " 0 - 25");
ranges.put(BigDecimal.valueOf(26), "26 - 50");
ranges.put(BigDecimal.valueOf(51), "51 - 75");
ranges.put(BigDecimal.valueOf(76), "76 - 99");
ranges.put(BigDecimal.valueOf(100),">= 100 ");
inputMap.values().stream()
.collect(Collectors.groupingBy(
bd -> ranges.floorEntry(bd).getValue(), TreeMap::new, Collectors.counting()))
.forEach((group,count) -> System.out.printf("%s: %s%n", group, count));
0 - 25: 2
51 - 75: 1
76 - 99: 1
which can also get extended to print the absent ranges:
Map<BigDecimal, Long> groupToCount = inputMap.values().stream()
.collect(Collectors.groupingBy(bd -> ranges.floorKey(bd), Collectors.counting()));
ranges.forEach((k, g) -> System.out.println(g+": "+groupToCount.getOrDefault(k, 0L)));
0 - 25: 2
26 - 50: 0
51 - 75: 1
76 - 99: 1
>= 100 : 0
But note that putting numeric values into ranges like, e.g. “0 - 25” and “26 - 50” only makes sense if we’re talking about whole numbers, precluding values between 25 and 26, raising the question why you’re using BigDecimal instead of BigInteger. For decimal numbers, you would normally use ranges like “0 (inclusive) - 25 (exclusive)” and “25 (inclusive) - 50 (exclusive)”, etc.
If you have a Range like this:
class Range {
private final BigDecimal start;
private final BigDecimal end;
public Range(BigDecimal start, BigDecimal end) {
this.start = start;
this.end = end;
}
public boolean inRange(BigDecimal val) {
return val.compareTo(start) >= 0 && val.compareTo(end) <= 0;
}
#Override
public String toString() {
return start + "-" + end;
}
}
You can do this:
Map<String, BigDecimal> input = new HashMap<>();
input.put("A", BigDecimal.valueOf(12));
input.put("B", BigDecimal.valueOf(23));
input.put("C", BigDecimal.valueOf(67));
input.put("D", BigDecimal.valueOf(99));
List<Range> ranges = new ArrayList<>();
ranges.add(new Range(BigDecimal.valueOf(0), BigDecimal.valueOf(25)));
ranges.add(new Range(BigDecimal.valueOf(26), BigDecimal.valueOf(50)));
ranges.add(new Range(BigDecimal.valueOf(51), BigDecimal.valueOf(75)));
ranges.add(new Range(BigDecimal.valueOf(76), BigDecimal.valueOf(100)));
Map<Range, Long> result = new HashMap<>();
ranges.forEach(r -> result.put(r, 0L)); // Add all ranges with a count of 0
input.values().forEach( // For each value in the map
bd -> ranges.stream()
.filter(r -> r.inRange(bd)) // Find ranges it is in (can be in multiple)
.forEach(r -> result.put(r, result.get(r) + 1)) // And increment their count
);
System.out.println(result); // {51-75=1, 76-100=1, 26-50=0, 0-25=2}
I also had a solution with the groupingBy collector, but it was twice as big and couldn't deal with overlapping ranges or values that weren't in any range, so I think a solution like this will be better.
You can also use a NavigableMap:
Map<String, BigDecimal> dataSet = new HashMap<>();
dataSet.put("A", new BigDecimal(12));
dataSet.put("B", new BigDecimal(23));
dataSet.put("C", new BigDecimal(67));
dataSet.put("D", new BigDecimal(99));
// Map(k=MinValue, v=Count)
NavigableMap<BigDecimal, Integer> partitions = new TreeMap<>();
partitions.put(new BigDecimal(0), 0);
partitions.put(new BigDecimal(25), 0);
partitions.put(new BigDecimal(50), 0);
partitions.put(new BigDecimal(75), 0);
partitions.put(new BigDecimal(100), 0);
for (BigDecimal d : dataSet.values()) {
Entry<BigDecimal, Integer> e = partitions.floorEntry(d);
partitions.put(e.getKey(), e.getValue() + 1);
}
partitions.forEach((k, count) -> System.out.println(k + ": " + count));
// 0: 2
// 25: 0
// 50: 1
// 75: 1
// 100: 0
If only RangeMap from guava had methods like replace of computeIfPresent/computeIfAbsent like the additions in java-8 Map do, this would have been a breeze to do. Otherwise it's a bit cumbersome:
Map<String, BigDecimal> left = new HashMap<>();
left.put("A", new BigDecimal(12));
left.put("B", new BigDecimal(23));
left.put("C", new BigDecimal(67));
left.put("D", new BigDecimal(99));
RangeMap<BigDecimal, Long> ranges = TreeRangeMap.create();
ranges.put(Range.closedOpen(new BigDecimal(0), new BigDecimal(25)), 0L);
ranges.put(Range.closedOpen(new BigDecimal(25), new BigDecimal(50)), 0L);
ranges.put(Range.closedOpen(new BigDecimal(50), new BigDecimal(75)), 0L);
ranges.put(Range.closedOpen(new BigDecimal(75), new BigDecimal(100)), 0L);
left.values()
.stream()
.forEachOrdered(x -> {
Entry<Range<BigDecimal>, Long> e = ranges.getEntry(x);
ranges.put(e.getKey(), e.getValue() + 1);
});
System.out.println(ranges);
Here is the code which you can use:
public static void groupByRange() {
List<MyBigDecimal> bigDecimals = new ArrayList<MyBigDecimal>();
for(int i =0; i<= 10; i++) {
MyBigDecimal md = new MyBigDecimal();
if(i>0 && i<= 2)
md.setRange(1);
else if(i>2 && i<= 5)
md.setRange(2);
else if(i>5 && i<= 7)
md.setRange(3);
else
md.setRange(4);
md.setValue(i);
bigDecimals.add(md);
}
Map<Integer, List<MyBigDecimal>> result = bigDecimals.stream()
.collect(Collectors.groupingBy(e -> e.getRange(),
Collector.of(
ArrayList :: new,
(list, elem) -> {
if (list.size() < 2)
list.add(elem);
},
(list1, list2) -> {
list1.addAll(list2);
return list1;
}
)));
for(Entry<Integer, List<MyBigDecimal>> en : result.entrySet()) {
int in = en.getKey();
List<MyBigDecimal> cours = en.getValue();
System.out.println("Key Range = "+in + " , List Size : "+cours.size());
}
}
class MyBigDecimal{
private int range;
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public int getRange() {
return range;
}
public void setRange(int range) {
this.range = range;
}
}
This will give you a similar result.
public static void main(String[] args) {
Map<String, Integer> resMap = new HashMap<>();
int range = 25;
Map<String, BigDecimal> aMap=new HashMap<>();
aMap.put("A",new BigDecimal(12));
aMap.put("B",new BigDecimal(23));
aMap.put("C",new BigDecimal(67));
aMap.put("D",new BigDecimal(99));
aMap.values().forEach(v -> {
int lower = v.divide(new BigDecimal(range)).intValue();
// get the lower & add the range to get higher
String key = lower*range + "-" + (lower*range+range-1);
resMap.put(key, resMap.getOrDefault(key, 0) + 1);
});
resMap.entrySet().forEach(e -> System.out.println(e.getKey() + " = " + e.getValue()));
}
Though there are some differences from what you have asked
Ranges are inclusive in this; 0-24 instead of 0-25, so that 25 is included in 25-50
Your range 0-25 contains 26 possible values in between, while all other ranges contain 25 values. This implementations output has ranges of size 25 (configurable via range variable)
You can decide on the range
Output (you may want to iterate the map's key better to get the output in a sorted order)
75-99 = 1
0-24 = 2
50-74 = 1
Assuming your range has the value BigDecimal.valueOf(26), you can do the following to get a Map<BigDecimal, Long> where each key represents the group id (0 for [0-25], 1 for [26, 51], ...), and each corresponding value represents the group count of elements.
content.values()
.stream()
.collect(Collectors.groupingBy(n -> n.divide(range, BigDecimal.ROUND_FLOOR), Collectors.counting()))
I was looking through some code and came across this method that takes an HTML Header value (i.e. Content-Disposition=inline;filename=foo.bar) and parses it into a map separated by the semi-colon's into key=value pairs. At first it looked like a good candidate for optimization using a stream, but after I implemented it, the fact that I can't reuse the computed String.indexOf('=') value means the string must be scanned 3 times, which is actually less optimal than the original. I'm perfectly aware that there are many instances where Streams aren't the right tool for the job, but I was wondering if I had just missed some technique that could allow the Stream to be as performant/more performant than the initial code.
/**
* Convert a Header Value String into a Map
*
* #param value The Header Value
* #return The data Map
*/
private static Map<String,String> headerMap (String value) {
int eq;
Map<String,String> map = new HashMap<>();
for(String entry : value.split(";")) {
if((eq = entry.indexOf('=')) != -1) {
map.put(entry.substring(0,eq),entry.substring(eq + 1));
}
}
return map;
return Stream.of(value.split(";")).filter(entry -> entry.indexOf('=') != -1).collect(Collectors.));
} //headerMap
My attempt at Streaming it:
/**
* Convert a Header Value String into a Map
*
* #param value The Header Value
* #return The data Map
*/
private static Map<String,String> headerMap (String value) {
return Stream.of(value.split(";")).filter(entry -> entry.indexOf('=') != -1).collect(Collectors.toMap(entry -> entry.substring(0,entry.indexOf('=')),entry -> entry.substring(entry.substring(entry.indexOf('=') + 1))));
} //headerMap
This solution looks for '=' only once:
private static Map<String, String> headerMap(String value) {
return Stream.of(value.split(";"))
.map(s -> s.split("=", 2))
.filter(arr -> arr.length == 2)
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
}
Note that here the fast-path for String.split is used, thus regular expression is not actually created.
Note that using Guava you can do this in quite clean way even prior to Java-8:
private static Map<String, String> headerMap(String value) {
return Splitter.on( ';' ).withKeyValueSeparator( '=' ).split( value );
}
In general I would advise you against manual parsing of HTTP headers. There are many caveats there. See, for example, how it's implemented in Apache HTTP library. Use libraries.
I came up with the following code:
private static Map<String, String> headerMap(String value) {
return Stream.of(value.split(";"))
.filter(entry -> entry.indexOf('=') != -1)
.map(entry -> {
int i = entry.indexOf('=');
return new String[] { entry.substring(0, i), entry.substring(i + 1) };
})
.collect(Collectors.toMap(array -> array[0], array -> array[1]));
}
It only scans for the entry two times, by storing the key and value inside an array of size 2. I'm not sure it will be as performant as the for loop since we are creating another Object to serve just as a holder.
Another solution that scans the entry only one time is this, although I'm not very found of it:
private static Map<String, String> headerMap(String value) {
return Stream.of(value.split(";"))
.map(entry -> {
int i = entry.indexOf('=');
if (i == -1) {
return null;
}
return new String[] { entry.substring(0, i), entry.substring(i + 1) };
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(array -> array[0], array -> array[1]));
}
I realized a JMH benchmark to test this. Following is the benchmark code:
#Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
#Measurement(iterations = 10, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
#BenchmarkMode(Mode.AverageTime)
#OutputTimeUnit(TimeUnit.MICROSECONDS)
#Fork(3)
#State(Scope.Benchmark)
public class StreamTest {
private static final String VALUE = "Accept=text/plain;"
+ "Accept-Charset=utf-8;"
+ "Accept-Encoding=gzip, deflate;"
+ "Accept-Language=en-US;"
+ "Accept-Datetime=Thu, 31 May 2007 20:35:00 GMT;"
+ "Cache-Control=no-cache;"
+ "Connection=keep-alive;"
+ "Content-Length=348;"
+ "Content-Type=application/x-www-form-urlencoded;"
+ "Date=Tue, 15 Nov 1994 08:12:31 GMT;"
+ "Expect=100-continue;"
+ "Max-Forwards=10;"
+ "Pragma=no-cache";
#Benchmark
public void loop() {
int eq;
Map<String, String> map = new HashMap<>();
for (String entry : VALUE.split(";")) {
if ((eq = entry.indexOf('=')) != -1) {
map.put(entry.substring(0, eq), entry.substring(eq + 1));
}
}
}
#Benchmark
public void stream1() {
Stream.of(VALUE.split(";"))
.filter(entry -> entry.indexOf('=') != -1)
.map(entry -> {
int i = entry.indexOf('=');
return new String[] { entry.substring(0, i), entry.substring(i + 1) };
})
.collect(Collectors.toMap(array -> array[0], array -> array[1]));
}
#Benchmark
public void stream2() {
Stream.of(VALUE.split(";"))
.map(entry -> {
int i = entry.indexOf('=');
if (i == -1) {
return null;
}
return new String[] { entry.substring(0, i), entry.substring(i + 1) };
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(array -> array[0], array -> array[1]));
}
public static void main(String[] args) throws Exception {
Main.main(args);
}
}
and this is the result (Code i5 3230M CPU # 2.60 GHz, Windows 10, Oracle JDK 1.8.0_25):
Benchmark Mode Cnt Score Error Units
StreamTest.loop avgt 30 1,541 ± 0,038 us/op
StreamTest.stream1 avgt 30 1,633 ± 0,042 us/op
StreamTest.stream2 avgt 30 1,604 ± 0,058 us/op
What this demonstrates is that both the streams solution and the for loop are actually equivalent in terms of performance.
I just begin to learn java (today),
To exercise me, I would like to translate the following "8 Queens" algorithm written in python into Java :
BOARD_SIZE = 8
def under_attack(col, queens):
left = right = col
for r, c in reversed(queens):
left, right = left-1, right+1
if c in (left, col, right):
return True
return False
def solve(n):
if n == 0: return [[]]
print n
smaller_solutions = solve(n-1)
return [solution+[(n,i+1)] for i in range(BOARD_SIZE) for solution in smaller_solutions if not under_attack(i+1, solution)]
sols = solve(BOARD_SIZE)
for answer in sols:
print answer
For translation, I want to use exactly the same algorithm : recursive and use of "lists of lists of tuple" like in python (I know I should think "java", but for now, it's just for fun)
I wrote this :
import java.util.ArrayList;
class Queens {
public static boolean under_attack(int col, ArrayList<Integer[]> queens) {
int left = col, right = col;
for(int i=queens.size()-1;i>=0;i--) {
int r = queens.get(i)[0];
int c = queens.get(i)[1];
left--;
right++;
if ( c==left || c==col || c==right) {
return true;
}
}
return false;
}
public static ArrayList<ArrayList<Integer[]>> solve(int n){
ArrayList<ArrayList<Integer[]>> new_solutions = new ArrayList<ArrayList<Integer[]>>();
if ( n==0 ) {
return new_solutions;
}
ArrayList<ArrayList<Integer[]>> smaller_solutions = solve(n-1);
for (int i=0;i<8;i++) {
for (ArrayList<Integer[]> solution : smaller_solutions) {
if ( ! under_attack(i+1,solution) ) {
ArrayList<Integer[]> bigger_solution = (ArrayList<Integer[]>) solution.clone();
Integer [] tuple = new Integer [2];
tuple[0] = n;
tuple[1] = i+1;
bigger_solution.add(tuple);
new_solutions.add(bigger_solution);
}
}
}
return new_solutions;
}
public static void main(String[] args) {
System.out.println("Résolution du problème des 8 reines");
ArrayList<ArrayList<Integer[]>> solutions;
solutions = solve(8);
System.out.format("Nb solutions : %d%n",solutions.size());
for (ArrayList<Integer[]> solution : solutions) {
System.out.print("(");
for(Integer[] i:solution) {
System.out.format("[%d,%d],",i[0],i[1]);
}
System.out.println(")");
System.out.println("==============================");
}
}
}
But this does not work : no answers is found
Do you have an idea why ?
The correction needed to your code to run identicaly to the python program is the following. At the begining of the solve() function instead of:
if ( n==0 ) {
return new_solutions;
}
you should write:
if ( n==0 ) {
ArrayList<Integer[]> empty = new ArrayList<Integer[]>();
new_solutions.add(empty);
return new_solutions;
}
The reason is that the artifact [[]] in python is not an empty list, right? It is a list containing as it's only element the empty list. This is exactly the correction in the java code! Otherwise the recursion doesn't work and the under_attack() function is never called (as you can verify adding a diagnostic message at its first line)
Happy coding...and java learning