【问题标题】:Sum up List to a limit with streams使用流将列表汇总到极限
【发布时间】:2020-10-21 19:25:56
【问题描述】:

如何获取记录,计数总和应在限制范围内。在下面的示例中有Records 对象包含recordId 和count,我想根据count 的总和应该小于或等于我的限制条件来获取记录数据。

public class Records {
    private int recordID;
    private int count;

    public Records(int recordID, int count) {
        this.recordID = recordID;
        this.count = count;
    }

    public int getRecordID() {
        return recordID;
    }

    public void setRecordID(int recordID) {
        this.recordID = recordID;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}


public static void main(String[] args) {
    
    final List<Records> recordList = new ArrayList<>();
    
    recordList.add(new Records(100, 10));
    recordList.add(new Records(501, 20));
    recordList.add(new Records(302, 5));
    recordList.add(new Records(405, 2));
    recordList.add(new Records(918, 8));
    int limit = 35;
}

预期结果

recordList 应该有记录对象:[100,10]、[500,20]、[302,5] 记录

【问题讨论】:

  • 我认为流操作不太适合这种情况。老式的 while 循环就可以了。
  • 函数式编程中的函数(由流使用)应该是无状态的。你的任务要求你记住一个状态。只需使用常规循环,这里没有流的好处。
  • @f1sh 在正确的函数式编程中,这将是一个前缀扫描操作,然后是一个 zip 和一个“take while”。问题是流是一次性的抽象。 这是这里的问题,而不是函数式编程。也就是说,它仍然可以作为归约或收集器来完成。
  • 我从这次讨论中学到了很多!以下帖子也可能有所帮助(我认为它不是重复的,因为它更通用)-stackoverflow.com/questions/33058670/…

标签: java java-8 java-stream


【解决方案1】:

使用 Stream API 解决这个问题的问题是,您必须在处理上下文之外保留一些信息,同时读取/更新(依赖)它。这些任务不适合 Stream API。

使用适合且非常适合的 for 循环:

int index = 0;                              // highest index possible
int sum = 0;                                // sum as a temporary variable
for (int i=0; i<recordList.size(); i++) {   // for each Record
    sum += recordList.get(i).getCount();    // ... add the 'count' to the 'sum'
    if (sum <= limit) {                     // ... until the sum is below the limit
        index = i;                          // ... move the pivot
    } else break;                           // ... or else stop processing
}

// here you need to get the list from 0 to index+1 
// as long as the 2nd parameter of subList(int, int) is exlcusive
List<Record> filteredRecords = recordList.subList(0, index + 1);

【讨论】:

    【解决方案2】:

    这是我唯一能想到的,但它不如常规循环高效,因为它针对它拥有的每个列表条目运行。这也导致它进一步向下添加其他值。例如,如果限制为 46,则将跳过计数为 5 的第三个条目,但仍会添加计数为 2 的下一个条目。不知道这是否是你想要的行为

        AtomicInteger count = new AtomicInteger();
    
        recordList = recordList.stream().filter(r -> {
            if(count.get() + r.count <= limit){
                count.addAndGet(r.count);
                return true;
            }
            return false;
        }).collect(Collectors.toList());
    

    【讨论】:

    【解决方案3】:

    将以下toString 添加到您的班级以进行打印,您可以按如下方式进行:

    public String toString() {
        return String.format("[%s, %s]", recordID, count);
    }
    
    • 分配一个列表来存储结果
    • 初始化总和
    • 遍历列表,将计数相加,直到 已达到阈值。
    List<Records> results = new ArrayList<>();
    int sum = 0;
    for (Records rec : recordList) {
         // sum the counts
         sum += rec.getCount();
         if (sum > limit) {
            // stop when limit exceeded
            break;
         }
         results.add(rec);
    }
            
    results.forEach(System.out::println);       
    
    

    打印

    [100, 10]
    [501, 20]
    [302, 5]
    

    【讨论】:

      【解决方案4】:

      使用 java 8,您可以执行以下操作:

      public static void main(String[] args) {
              int limit = 35;
              List<Records> recordList = new ArrayList<>();
              recordList.add(new Records(100, 10));
              recordList.add(new Records(501, 20));
              recordList.add(new Records(302, 5));
              recordList.add(new Records(405, 2));
              recordList.add(new Records(918, 8));
      
              List<Records> limitedResult = recordList.stream().filter(new Predicate<Records>() {
                  int sum = 0;
                  @Override
                  public boolean test(Records records) {
                      sum=sum+records.getCount();
                      return sum <= limit;
                  }
              }).collect(Collectors.toList());
              //do what do you want with limitedResult
              System.out.println(limitedResult);
          }
      

      编辑:

      或者你可以制作返回 Predicate 的函数,它可以被重用为:

          //Reusable predicate
          public static Predicate<Records> limitRecordPredicate(int limit){
              return new Predicate<Records>() {
                  int sum = 0;
                  @Override
                  public boolean test (Records records){
                      sum = sum + records.getCount();
                      return sum <= limit;
                  }
              };
          }
      

      然后像这样使用它:

      List<Records> limitedResult = recordList.stream().filter(limitRecordPredicate(limit)).collect(Collectors.toList());
              //do what do you want with limitedResult
              System.out.println(limitedResult);
      

      输出:

      [Records[recordID=100, count=10], Records[recordID=501, count=20], Records[recordID=302, count=5]]
      

      【讨论】:

      • 这不能保证有效,因为根据合同,谓词等是无状态的。流可能被乱序消费等。只有收集器被允许是有状态的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-04
      • 2020-08-31
      相关资源
      最近更新 更多