查找地图对象范围内的元素数量

时间:2018-05-02 12:07:06

标签: java java-8 java-stream

地图结构和数据如下:

Function
  • A,12
  • B,23
  • C,67
  • D,99

现在我想要在范围内对值进行分组,输出的范围为键,元素的数量为值。如下所示:

  • 0-25,2
  • 26-50,0
  • 51-75,1
  • 76-100,1

我们如何使用java流做到这一点?

8 个答案:

答案 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()));
}

虽然与您提出的问题存在一些差异

  • 范围包括在内; 0-24而不是0-25,因此25包括在25-50
  • 您的范围0-25包含26个可能的值,而所有其他范围包含25个值。此实现输出的大小为25(可通过范围变量配置)
  • 您可以决定范围

输出(您可能希望更好地迭代地图键以按排序顺序获取输出)

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>