【问题标题】:Java functional approach to join identical elements into a single oneJava 函数式方法将相同的元素连接成一个单一的元素
【发布时间】:2020-02-17 20:08:22
【问题描述】:

我想使用 Streams API 来处理通话记录并计算同一电话号码的总账单金额。这是使用混合方法实现它的代码,但我想使用全功能方法:

List<CallLog> callLogs = Arrays.stream(S.split("\n"))
                    .map(CallLog::new)
                    .sorted(Comparator.comparingInt(callLog -> callLog.phoneNumber))
                    .collect(Collectors.toList());

            for (int i = 0; i< callLogs.size() -1 ;i++) {
                if (callLogs.get(i).phoneNumber == callLogs.get(i+1).phoneNumber) {
                    callLogs.get(i).billing += callLogs.get(i+1).billing;
                    callLogs.remove(i+1);
                }
            }

【问题讨论】:

  • 你应该先分组,然后合并所有组
  • 请注意 - 我强烈建议您不要将电话号码视为整数类型。这是一条经验法则 - 除非您要对数据执行数学运算,否则不要将其视为数字。即使它仅由数字成员组成。可以是电话号码、帐号或其他任何内容。如果“数字”以零开头,则在转换为实际数字类型时它们会丢失,因此帐号0001234 不仅仅是1234,这是一回事。此外,您很容易受到整数溢出的影响——尤其是对于大数字的电话号码。

标签: java functional-programming java-stream


【解决方案1】:

您可以使用Collectors.groupingByCallLog 对象按电话号码与Collectors.summingInt 组合在一起,以汇总分组元素的计费

Map<Integer, Integer> likesPerType = Arrays.stream(S.split("\n"))
                                           .map(CallLog::new)
                 .collect(Collectors.groupingBy(CallLog::getPhoneNumber, Collectors.summingInt(CallLog::getBilling)));

【讨论】:

    【解决方案2】:
    Map<Integer, Integer> result = Arrays.stream(S.split("\n"))
                        .map(CallLog::new)
                        .sorted(Comparator.comparingInt(callLog -> callLog.phoneNumber))
                        .collect(Collectors.toMap(
                            c -> c.phoneNumber(),
                            c -> c.billing(),
                            (a, b) -> a+b
                         ));
    

    如果你想要一个“列出 callLogs”作为结果:

    List<CallLog> callLogs = Arrays.stream(S.split("\n"))
                            .map(CallLog::new)
                            .collect(Collectors.toMap(
                                c -> c.phoneNumber(),
                                c -> c.billing(),
                                (a, b) -> a+b
                             ))
                            .entrySet()
                            .stream()
                            .map(entry -> toCallLog(entry.getKey(), entry.getValue()))
                            .sorted(Comparator.comparingInt(callLog -> callLog.phoneNumber))
                            .collect(Collectors.toList())
    

    【讨论】:

      【解决方案3】:

      如果您改为执行以下操作,您可以保存自己的排序 -> 集合到列表 -> 迭代列表以获取彼此相邻的值

      1. 创建所有CallLog 对象。
      2. 通过phoneNumber 字段合并它们
        • 每次合并billing 字段
      3. 返回已经合并的项目

      这可以使用Collectors.toMap(Function, Function, BinaryOperator) 来完成,其中第三个参数是合并函数,它定义了如何组合具有相同键的项目:

      Collection<CallLog> callLogs = Arrays.stream(S.split("\n"))
        .map(CallLog::new)
        .collect(Collectors.toMap( //a collector that will produce a map
          CallLog::phoneNumber,    //using phoneNumber as the key to group
          x -> x,                  //the item itself as the value
          (a, b) -> {              //and a merge function that returns an object with combined billing
            a.billing += b.billing;
            return a;
          }))
        .values(); //just return the values from that map
      

      最后,您将拥有具有唯一phoneNumber 字段的CallLog 项目,其billing 字段等于先前重复的phoneNumbers 的所有billings 的组合。

      【讨论】:

      • 这是我未提及的后续处理所绝对需要的。感谢您的回答和其他建议/cmets @vlaz
      【解决方案4】:

      您要做的是删除重复的电话号码,同时添加他们的帐单。流不兼容的一件事是删除操作。那么我们如何在不移除的情况下完成您需要的操作呢?

      我不会排序,而是使用groupingBy 电话号码,然后我会将呼叫日志组列表映射到已经累积计费的呼叫日志。

      【讨论】:

        【解决方案5】:

        您可以按电话号码对帐单金额进行分组,就像 VLAZ 所说的那样。示例实现可能如下所示:

        import java.util.Arrays;
        import java.util.Map;
        import java.util.stream.Collectors;
        
        public class Demo {
        
            public static void main(String[] args) {
        
                final String s = "555123456;12.00\n"
                        + "555123456;3.00\n"
                        + "555123457;1.00\n"
                        + "555123457;2.00\n"
                        + "555123457;5.00";
        
                final Map<Integer, Double> map = Arrays.stream(s.split("\n"))
                        .map(CallLog::new)
                        .collect(Collectors.groupingBy(CallLog::getPhoneNumber, Collectors.summingDouble(CallLog::getBilling)));
        
                map.forEach((key, value) -> System.out.printf("%d: %.2f\n", key, value));
            }
        
            private static class CallLog {
        
                private final int phoneNumber;
                private final double billing;
        
                public CallLog(int phoneNumber, double billing) {
                    this.phoneNumber = phoneNumber;
                    this.billing = billing;
                }
        
                public CallLog(String s) {
                    final String[] strings = s.split(";");
                    this.phoneNumber = Integer.parseInt(strings[0]);
                    this.billing = Double.parseDouble(strings[1]);
                }
        
                public int getPhoneNumber() {
                    return phoneNumber;
                }
        
                public double getBilling() {
                    return billing;
                }
            }
        }
        

        产生以下输出:

        555123456: 15.00
        555123457: 8.00
        

        【讨论】:

        • 是的。使用 groupingBy 后,排序后的调用就无关紧要了。
        • 你是对的。我将从我的示例中删除它。谢谢!
        猜你喜欢
        • 2018-05-08
        • 2021-08-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-26
        • 2017-03-23
        • 2017-08-06
        相关资源
        最近更新 更多