地图结构和数据如下:
Function
现在我想要在范围内对值进行分组,输出的范围为键,元素的数量为值。如下所示:
我们如何使用java流做到这一点?
答案 0 :(得分:6)
你可以这样做:
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);
}
}
结果是:结果= {0-25 = 2,761-100 = 1,51-75 = 1}
我无法找到一个更好的方法来检查大小数,但你可以考虑如何改进它:)也许做一个外部方法来做那个技巧
答案 1 :(得分:4)
您可以使用常规范围的解决方案,例如
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);
});
将打印:
1 - 25: 2
51 - 75: 1
76 - 100: 1
(不使用不规则范围0 - 25
)
或具有明确范围的解决方案:
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
也可以扩展以打印缺席范围:
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
但请注意,将数值放入范围内,例如: “0 - 25”和“26 - 50”只有在谈论整数时才有意义,排除25到26之间的值,提出了为什么你使用BigDecimal
代替BigInteger
的问题。对于十进制数字,通常使用“0(含) - 25(不包括)”和“25(含) - 50(独占)”等范围。
答案 2 :(得分:2)
如果你有Range
这样的话:
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;
}
}
你可以这样做:
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}
我也有一个groupingBy
收集器的解决方案,但是它只有两倍大,无法处理任何范围内的重叠范围或值,所以我认为是一个解决方案这样会更好。
答案 3 :(得分:1)
您还可以使用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
答案 4 :(得分:1)
如果来自guava的RangeMap
只有像replace
computeIfPresent/computeIfAbsent
这样的方法,就像java-8 Map
中的添加一样,那么这将是一件轻而易举的事。否则它有点麻烦:
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);
答案 5 :(得分:0)
以下是您可以使用的代码:
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;
}
}
答案 6 :(得分:0)
这会给你一个类似的结果。
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()));
}
虽然与您提出的问题存在一些差异
输出(您可能希望更好地迭代地图键以按排序顺序获取输出)
75-99 = 1
0-24 = 2
50-74 = 1
答案 7 :(得分:0)
假设您的范围具有值@RunWith(SpringRunner.class)
class UltraServiceTest {
}
,您可以执行以下操作以获得BigDecimal.valueOf(26)
,其中每个键代表组ID(0表示[0-25],1表示[26,51] ],...),每个对应的值代表元素的组数。
Map<BigDecimal, Long>