Найти количество элементов в диапазоне от объекта карты - PullRequest
0 голосов
/ 02 мая 2018

Структура карты и данные приведены ниже

Map<String, BigDecimal>
  • А, 12
  • B, 23
  • С, 67
  • D, 99

Теперь я хочу сгруппировать значения в диапазоне, у выхода есть диапазон в качестве ключа и количество элементов там в качестве значения. Как ниже:

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

Как мы можем сделать это, используя потоки Java?

Ответы [ 8 ]

0 голосов
/ 04 мая 2018

Предполагая, что ваш диапазон имеет значение BigDecimal.valueOf(26), вы можете сделать следующее, чтобы получить Map<BigDecimal, Long>, где каждый ключ представляет идентификатор группы (0 для [0-25], 1 для [26, 51], .. .), и каждое соответствующее значение представляет количество элементов группы.

content.values()
    .stream()
    .collect(Collectors.groupingBy(n -> n.divide(range, BigDecimal.ROUND_FLOOR), Collectors.counting()))
0 голосов
/ 02 мая 2018

Если бы только RangeMap из гуавы имел такие методы, как replace из computeIfPresent/computeIfAbsent, такие как добавления в java-8 Map do, это было бы очень просто. В противном случае это немного громоздко:

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);
0 голосов
/ 02 мая 2018

Вы можете использовать решение для регулярных диапазонов, например,

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 (исключая)» и т. Д.

0 голосов
/ 02 мая 2018

Вы также можете использовать 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
0 голосов
/ 02 мая 2018

Это даст вам аналогичный результат.

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
0 голосов
/ 02 мая 2018

Вот код, который вы можете использовать:

    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;
    }

}
0 голосов
/ 02 мая 2018

Если у вас есть 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, но оно было в два раза больше и не могло справиться с перекрывающимися диапазонами или значениями, которых не было ни в одном диапазоне, поэтому я думаю, что подобное решение будет лучше.

0 голосов
/ 02 мая 2018

Вы можете сделать это так:

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, 76-100 = 1, 51-75 = 1}

Я не смог найти лучшего способа сделать эту проверку для больших десятичных дробей, но вы можете подумать о том, как ее улучшить :) Возможно, создайте внешний метод, который сделает этот трюк

...