Java 8 - 对列表进行分组并计算数量

5

我有一个结果列表。我需要找到通过的结果计数。但是列表中的某些项之间存在关系。例如,我有以下列表:

1.0 - false
2.0 - true
3.0 - false
4.0 - true
1.1 - true
3.1 - true

那么传递的计数应该是2而不是4。因为我想基于id(1,1.2,1.3,1.xx)将列表分组,并在组中所有项目都通过时标记它为通过。我尝试使用groupingBy进行分组,得到了预期行为的映射。我可以迭代地遍历该映射并获取计数。但是我想知道是否有任何方法可以简单地使用Java 8完成此操作。

public class Main {

static class Resultx {

    double id = 1;

    Boolean passed = false;

    public void setId(double id) {
        this.id = id;
    }

    public double getId() {
        return id;
    }

    public void setAsPassed() {
        this.passed = true;
    }

    public Boolean getPassed() {
        return passed;
    }

    @Override
    public String toString() {
        return getId() + " - " + getPassed();
    }
}


public static void main(String[] args) {
    List<Resultx> results = new ArrayList<>();
    for (int i = 1; i < 5; i++) {
        Resultx result = new Resultx();
        result.setId(i);
        if (i % 2 == 0) {
            result.setAsPassed();
        }
        results.add(result);
    }
    for (int i = 1; i < 5; i += 2) {
        Resultx result = new Resultx();
        result.setId(i + .1);
        result.setAsPassed();
        results.add(result);
    }
    System.out.println(results.size());
    results.forEach(System.out::println);
    System.out.println(results.stream().filter(Resultx::getPassed).count());
    System.out.println(results.stream().filter(e -> !e.getPassed()).count());
    System.out.println(results.stream().collect(Collectors.groupingBy(r -> (int) (r.getId()))));
}
}

输出

Total count - 6
1.0 - false
2.0 - true
3.0 - false
4.0 - true
1.1 - true
3.1 - true
Total pass count  - 4
Total fail count - 2
{1=[1.0 - false, 1.1 - true], 2=[2.0 - true], 3=[3.0 - false, 3.1 - true], 4=[4.0 - true]}

我想要总的通过计数和总的失败计数,分别为2次和2次。

那么,对于大于 .5 的数字呢?例如 1.6,它会被视为 1 还是 2 - Eugene
1
使用 double 作为 ID 看起来很可疑。使用 Boolean 而不是 boolean 也是如此。您计划支持对其使用 null 值吗? - Holger
应该很容易。将值迭代为键值对,其中键为(int) floatValue;,值为布尔运算 - existingValueIfany && newBooleanValue。现在您将拥有带有数字和布尔值的K、V对。仅过滤true值并获取计数。我正在使用移动设备,因此无法输入答案 :( - Karthik R
5个回答

5

试试这个

 Map<Boolean, Long> collect = results.stream()
                    .collect(Collectors.groupingBy(r -> (int) (r.getId()))).values()
                    .stream().map(l -> l.stream().allMatch(p -> p.getPassed()))
                    .collect(Collectors.partitioningBy(k -> k, Collectors.counting()));

            System.out.println(collect);

选择哪种显示器:

{false=2, true=2}

1
缺点是这将首先将所有组元素收集到“List”中,然后才执行“passed”属性的逻辑与操作。可以在第一次分组步骤中直接进行此缩减,而无需创建中间列表。 - Holger
1
感谢 @SEY_91,虽然你的回答直截了当,正是我所期望的,但基于 Holger 的观点,我接受了他的答案。 - Madhan

3
您的代码有一些奇怪的地方,例如使用 double ID,然后在分组操作中将其转换为 int,或者使用 Boolean 作为您的 passed 属性,而不是只使用 boolean。使用引用类型 Boolean 可能会出现 null 的情况,如果有这种可能,您必须处理它。否则,请使用 boolean
同时,您想要的结果也不够清晰。这个例子不足以描述它。
如果您只想计算组数,其中 true 表示 “全部都是 true”,false 表示 “某些是 false”,那么可以简单地这样做:
Map<Boolean,Long> counts = results.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.toMap(r -> (int)r.getId(), Resultx::getPassed, Boolean::logicalAnd),
        m -> m.values().stream()
            .collect(Collectors.partitioningBy(b -> b, Collectors.counting()))
    ));

如果你想计算组中的元素数量,这会变得更加复杂。
int totalPassedCount = results.stream()
    .collect(Collectors.collectingAndThen(Collectors.groupingBy(r -> (int)r.getId(),
        Collector.of(() -> new Object() { int count = 0; boolean pass = true; },
            (o, r) -> { o.count++; o.pass &= r.getPassed(); },
            (x, y) -> { x.count += y.count; x.pass &= y.pass; return x; },
            o -> o.pass? o.count: 0
        )),
        (Map<Integer,Integer> x) -> x.values().stream().mapToInt(i -> i).sum()
    ));
System.out.println(totalPassedCount);

这里使用了自定义收集器作为groupingBydownstream收集器。该自定义收集器将元素计数和所有元素是否通过收集到一个对象中,然后在完成步骤中,如果组中所有元素均通过,则用计数替换这些对象,否则替换为零。接下来,对groupingBy收集器添加了一步完成步骤,用于汇总所有这些值。
以上解决方案是为了获得所请求的已通过计数。由于您在问题开头提出了两者的请求,因此可以使用该解决方案。
Map<Boolean,Integer> counts = results.stream()
    .collect(Collectors.collectingAndThen(Collectors.groupingBy(r -> (int)r.getId(),
        Collector.of(() -> new Object() { int count = 0; boolean pass = true; },
            (o, r) -> { o.count++; o.pass &= r.getPassed(); },
            (x, y) -> { x.count += y.count; x.pass &= y.pass; return x; }
        )),
        m -> m.values().stream().collect(
            Collectors.partitioningBy(o -> o.pass, Collectors.summingInt(o -> o.count)))
    ));

同时获取两个。

或者,从单个示例中获得预期规则很棘手,

Map<Boolean,Integer> counts = results.stream()
    .collect(Collectors.collectingAndThen(Collectors.groupingBy(r -> (int)r.getId(),
        Collector.of(() -> new Object() { int passed, failed; },
            (o, r) -> { if(r.getPassed()) o.passed++; else o.failed++; },
            (x, y) -> { x.passed += y.passed; x.failed += y.failed; return x; }
        )),
        m -> m.values().stream()
            .filter(o -> o.passed == 0 || o.failed == 0)
            .collect(Collectors.partitioningBy(o -> o.failed==0,
                Collectors.summingInt(o -> o.failed==0? o.passed: o.failed)))
    ));

这将仅计算组中所有元素是否全部通过或失败的情况下的passedfailed。但是,由于您希望得到22的结果,您可以删除.filter(o -> o.passed == 0 || o.failed == 0)行。然后,如果组中所有元素都通过,则只计算passed,但是即使组中某些元素已经通过了,也会计算所有的failed。然后,你会得到22的结果。


双重部分的原因是,假设有具有子任务的任务,那么它们将是1.1、1.2...1.12。结果对象将保存任务的结果。我需要按顺序打印它们,所以选择了这种方式。 - Madhan
是的,我本来会选择布尔类型,我不想要空值,我会进行更改。谢谢你的建议。感谢你详细的解释。是的,我想要“true”表示“所有都为真”,而“false”表示“某些为假”。 - Madhan

0

我不完全确定这是最简单的方法,但是:

Map<Double, Boolean> map = new HashMap<>();
    map.put(1.0D, false);
    map.put(2.0D, true);
    map.put(2.1D, true);
    map.put(1.2D, true);

    Map<Boolean, Long> result = map.entrySet()
        .stream()
        .collect(Collectors.groupingBy(
                x -> Arrays.asList(Math.round(x.getKey()), x.getValue()),
                Collectors.counting()))
        .entrySet()
        .stream()
        .collect(Collectors.partitioningBy(
                e -> (Boolean) e.getKey().get(1),
                Collectors.counting()));


  System.out.println(result); // {false=1, true=2}

你只是在计算分组数量,完全忽略了第一个分组操作中元素的计数。也就是说,你生成了分组 [1, false][2, true][1, true],导致有一个 false 值和两个 true 值。 - Holger
@Holger 这个结果不是 OP 所期望的吗?但我同意在无用计数方面的观点。 - Eugene
我不知道。楼主的描述真的很模糊。有许多东西恰好是“2”...但如果楼主只想要计算组数,这可以做得更简单。我在我的回答开头添加了一个解决方案。 - Holger

0

这很简单,只需基于测试编号对结果进行排序,然后逐个计算每个组。只要你的测试数量不超过百万级别,排序不应该会造成太大的计算量。

如果一个“组”中的单个“测试”失败,则整个组都失败,并且可以使用结构来存储每个分组的结果。


如何给出falsetrue的总结果? - Eugene
根据您的具体意思,您可以在不同的结构中进行计数,一个是针对“总体”(两个整数:overall_true、overall_false),另一个是“每个组内的总数”。为了简单起见,我建议使用哈希映射表(虽然不是最理想的,但大多数人学习的第一个集合)来存储每个组的计数值(事先检查我没有覆盖值)。 - mawalker

0

不确定是否使用Java 8集合API是最简单的方法,但以下是一种实现方式,可以计算正数和负数结果的数量:

System.out.println(
              results.stream()
                    .collect(Collectors.groupingBy(result -> (int) (result.getId())))
                    .values()
                    .stream()
                    .map(v -> v.stream().allMatch(Resultx::getPassed))
                    .collect(Collectors.groupingBy(b -> b, Collectors.counting()))
        );

2
基本上与此答案相同。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接