【问题标题】:How to print a list by adding a new line after every 3rd element in a list in java lambda expression?java - 如何通过在java lambda表达式列表中的每个第三个元素之后添加一个新行来打印列表?
【发布时间】:2018-02-11 05:39:21
【问题描述】:

假设我有一个如下列表

Collection<?> mainList = new ArrayList<String>();
mainList=//some method call//

目前,我将列表中的元素显示为

System.out.println(mainList.stream().map(Object::toString).collect(Collectors.joining(",")).toString());

我得到的结果是

a,b,c,d,e,f,g,h,i

如何通过在java中列表中的每个第三个元素后添加一个新行来打印此列表,以便它将打印结果如下

a,b,c
d,e,f
g,h,i

注意:这类似于 How to Add newline after every 3rd element in arraylist in java?。但是格式化文件是在读取自身时完成的。 我想在打印输出时这样做。

【问题讨论】:

  • 在我看来,您的问题比输出更基本。这里真正的问题是:如何以 X 组的形式获取流中的元素?很惊讶没有在Collectors 中找到这样做的东西。您可能可以创建自己的 Collector 来完成它,生成 Lists 作为输出。
  • @T.J.Crowder 显然有 Guava 和 Apache Commons 中的课程可以做到这一点 - baeldung.com/java-list-split
  • @T.J.Crowder Collectors.groupingBy(predicate).values() 可以将列表划分为子列表列表。我已经建立了我的答案。
  • @SzymonStepniak:啊,我应该想到把柜台放在这样的容器里。
  • 我不得不怀疑,鉴于.collect 是一个终端操作,而您显然只是将它们用于此列创建任务,为什么还要为使用 Streams 的开销而烦恼呢?

标签: java arraylist lambda java-8


【解决方案1】:

如果您想坚持使用 Java Stream API,那么您的问题可以通过将初始列表划分为大小为 3 的子列表来解决,然后将每个子列表表示为字符串并使用 \n 连接结果。

import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

final class PartitionListExample {

    public static void main(String[] args) {

        final Collection<String> mainList = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i");
        final AtomicInteger idx = new AtomicInteger(0);
        final int size = 3;

        // Partition a list into list of lists size 3
        final Collection<List<String>> rows = mainList.stream()
                .collect(Collectors.groupingBy(
                        it -> idx.getAndIncrement() / size
                ))
                .values();

        // Write each row in the new line as a string
        final String result = rows.stream()
                .map(row -> String.join(",", row))
                .collect(Collectors.joining("\n"));

        System.out.println(result);
    }
}

有 3rd 方库提供使列表分区更容易的实用程序类(例如 GuavaApache Commons Collections),但此解决方案仅基于 Java 8 SDK 构建。

它的作用是:

  • 首先,我们通过按分配的行索引分组来收集所有元素,并将值存储为列表(例如{0=[a,b,c],1=[d,e,f],2=[g,h,i]}
  • 然后我们获取所有值的列表,例如[[a,b,c],[d,e,f],[g,h,i]]
  • 最后,我们将列表列表表示为一个字符串,其中每一行由\n 分隔

输出 Demo

运行以下程序将打印到控制台以下输出:

a,b,c
d,e,f
g,h,i

从以下示例中获得更多信息

Alnitak 在下面的示例中发挥了更多作用,并通过在.groupingBy 收集器中使用Collectors.joining(",") 并在最后使用String.join("\n", rows) 而不是触发另一个流减少来提出更短的解决方案。

        final Collection<String> rows = mainList.stream()
                .collect(Collectors.groupingBy(
                        it -> idx.getAndIncrement() / size,
                        Collectors.joining(",")
                ))
                .values();

        // Write each row in the new line as a string
        final String result = String.join("\n", rows);

        System.out.println(result);
    }

最后说明

请记住,这不是以所需格式打印元素列表的最有效方式。但是,如果涉及到创建最终结果,任何元素的分区列表都为您提供了灵活性,并且非常容易阅读和理解。

【讨论】:

  • JOOI(我不是流专家)为什么不使用rows = list.stream().collect(Collectors.groupingBy(s -&gt; idx.getAndIncrement() / 3)) - 我看不到AbstractMapSimpleEntry 操作添加了什么。我已经尝试过了,它显然对我有用,但可能会有副作用或我不知道的问题。
  • @Alnitak 很好,谢谢!它没有任何副作用,但由于创建了中间不可变条目,因此更加冗长。正如您所观察到的,可以通过直接调用.groupingBy(s -&gt; idx.getAndIncrement() / size) 来简化它。感谢您的意见!
  • 不客气——还有什么新的电话给Collectors.toList()?在我的实验中我没有发现它是必要的 - .groupingBy 已经返回一个 `Map``
  • @Alnitak 也可以避免,因为.groupingBy() 在未指定downstream 时使用它。再次感谢! :)
  • 为了更有趣,在.groupingBy 中使用Collectors.joining(",") 作为downstream 函数,并从最后一行中删除.map(row -&gt; ...) lambda。
【解决方案2】:

附注:在您的实际代码中,map(Object::toString) 如果您替换,则可能会被删除

Collection&lt;?&gt; mainList = new ArrayList&lt;String&gt;(); by

Collection&lt;String&gt; mainList = new ArrayList&lt;String&gt;();.

如果您操纵Strings,请创建StringCollection,而不是?Collection


但是格式化文件是在读取自身时完成的。我想 在打印输出时执行此操作。

得到加入的字符串后,使用replaceAll("(\\w*,\\w*,\\w*,)", "$1" + System.lineSeparator()) 应该可以完成这项工作。
Iit 将搜索并替换由, 字符分隔的所有 3 个或更多字符系列,并替换为相同的内容($1-> 组捕获),但通过将其与行分隔符连接起来。

除此之外:

String collect = mainList.stream().collect(Collectors.joining(","));    

可以简化为:

String collect = String.join(",", mainList);    

示例代码:

public static void main(String[] args) {
  Collection<String> mainList = Arrays.asList("a","b","c","d","e","f","g","h","i", "j");
  String formattedValues = String.join(",", mainList).replaceAll("(\\w*,\\w*,\\w*,)", "$1" + System.lineSeparator());
  System.out.println(formattedValues);
}

输出:

a,b,c,

d,e,f,

g,h,i,

j

【讨论】:

  • 在输出上使用字符串处理对我来说似乎是一个讨厌的黑客攻击
  • replaceAll 似乎不干净。如果您至少可以详细说明用法会很好。
  • 详细说明我的评论 - 正则表达式是内容敏感的,如果单个元素不再匹配 \w,则需要修改,从而使此代码变得脆弱。虽然可能需要更多代码,但应首先使用结构化数据将元素分组为三个一组,而不是作为后处理步骤。
  • @Alnitak,我的回答是基于操作要求。我们也可以用\w* 替换\w。根据设计的原因,代码很脆弱。如果不需要,为什么要增加复杂性?
  • @Downvoter 我可能理解这种解决问题的方法可能不是每个人都选择的,但它是正确的并且有效。请阅读它:“每当你遇到一个极其草率、不费吹灰之力的帖子,或者一个明显不正确且可能危险地不正确的答案时,请使用你的反对票。” stackoverflow.com/help/privileges/vote-down
【解决方案3】:

这里没有回答的另一种方法是创建自定义Collector

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class PartitionListInPlace {

    static class MyCollector implements Collector<String, List<List<String>>, String> {

        private final List<List<String>> buckets;
        private final int bucketSize;

        public MyCollector(int numberOfBuckets, int bucketSize) {
            this.bucketSize = bucketSize;
            this.buckets = new ArrayList<>(numberOfBuckets);
            for (int i = 0; i < numberOfBuckets; i++) {
                buckets.add(new ArrayList<>(bucketSize));
            }
        }

        @Override
        public Supplier<List<List<String>>> supplier() {
            return () -> this.buckets;
        }

        @Override
        public BiConsumer<List<List<String>>, String> accumulator() {
            return (buckets, element) -> buckets
              .stream()
              .filter(x -> x.size() < bucketSize)
              .findFirst()
              .orElseGet(() -> {
                  ArrayList<String> nextBucket = new ArrayList<>(bucketSize);
                  buckets.add(nextBucket);
                  return nextBucket;
              })
              .add(element);
        }

        @Override
        public BinaryOperator<List<List<String>>> combiner() {
            return (b1, b2) -> {
                throw new UnsupportedOperationException();
            };
        }

        @Override
        public Function<List<List<String>>, String> finisher() {
            return buckets -> buckets.stream()
              .map(x -> x.stream()
                .collect(Collectors.joining(", ")))
              .collect(Collectors.joining(System.lineSeparator()));
        }

        @Override
        public Set<Characteristics> characteristics() {
            return new HashSet<>();
        }
    }


    public static void main(String[] args) {
        Collection<String> mainList = Arrays.asList("a","b","c","d","e","f","g","h","i", "j");

        String formattedValues = mainList
          .stream()
          .collect(new MyCollector(mainList.size() / 3, 3));
        System.out.println(formattedValues);
    }
}

说明

这是一个不应该并行使用的可变收集器。如果您需要并行处理流,则必须将此收集器转换为线程安全的,如果您不关心元素的顺序,这很容易。

combiner 抛出异常,因为它从未被调用,因为它是按顺序运行的流。

Characteristics 的集合没有我们感兴趣的,您可以通过阅读javadoc 来验证这一点

supplier 方法将获取我们要插入元素的桶。该元素将被插入到下一个有空间的桶中,否则我们将创建一个新桶并将其添加到那里。

finisher 很简单:通过, 加入每个桶的内容,用System.lineSeparator() 加入桶本身

记住

不要使用这个收集器来处理

输出

a, b, c
d, e, f
g, h, i
j

【讨论】:

    猜你喜欢
    • 2021-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多