Reputation: 5223
Map structure and data is given below
Map<String, BigDecimal>
Now i want to group values in range, output has range as key and number of elements there as value. Like below:
How can we do this using java streams ?
Upvotes: 5
Views: 1901
Reputation: 1147
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()))
Upvotes: 0
Reputation: 298143
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.
Upvotes: 5
Reputation: 120848
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);
Upvotes: 1
Reputation: 5552
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
Upvotes: 1
Reputation: 2541
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
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
Upvotes: 0
Reputation: 33865
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.
Upvotes: 2
Reputation: 13835
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;
}
}
Upvotes: 0
Reputation: 7071
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
Upvotes: 6