【问题标题】:Join list of string with commas and new lines用逗号和换行加入字符串列表
【发布时间】:2021-12-12 12:08:34
【问题描述】:

我需要用逗号加入一个字符串列表。但我也有行长的限制。 例如列表I need to join a list of strings with commas 应该变成: I, need, to, join, a, \n list, of, strings, with, \n commas.

我已经为此实现了一个方法:

private static String joinLabels(List<String> strings) {
    int rowLengthCounter = 0;
    StringBuilder result = new StringBuilder();
    for (String str : strings) {
        rowLengthCounter += str.length();
        if (rowLengthCounter > 40) {
            result.append("\n");
            rowLengthCounter = str.length();
        }
        result.append(str);
        result.append(", ");
    }
    return result.substring(0, result.lastIndexOf(", "));
}

而且它有效。但也许存在简单的方法,比如 String.join 用指定的分隔符连接字符串,或者流 API。感谢您的帮助。

【问题讨论】:

  • 现有 Java SE 类的形式没有简单的方法可以做到这一点。使用Stream 会很复杂......这违背了使用Stream 的目的。
  • 您的实现不计算分隔符的字符,此外,限制 40 对于您预期的示例输出来说太大了。这是不一致的。示例的期望可以通过String.join(", ", strings).replaceAll("\\G(?=.{25}).{1,23}, ", "$0\n") 来实现

标签: java string java-stream


【解决方案1】:

在这种情况下,有人可能会争辩说需要蒸汽。但是这里没有给出的另一个解决方案是创建一个自定义收集器。收集器是一种可变的归约操作,看起来几乎就像您当前的 for 循环代码。

public class CustomStringJoiner implements Collector<String, StringBuffer, String> {

        private int rowLength = 0;
        private final String delimiter;
        private final int maxRowSize;
        private boolean first;

        public CustomStringJoiner(String delimiter, int maxRowSize) {
            this.delimiter = delimiter;
            this.maxRowSize = maxRowSize;
            first = true;
        }

        @Override
        public Supplier<StringBuffer> supplier() {
            return StringBuffer::new;
        }

        @Override
        public BiConsumer<StringBuffer, String> accumulator() {
            return this::add;
        }

        @Override
        public BinaryOperator<StringBuffer> combiner() {
            return this::combine;
        }

        @Override
        public Function<StringBuffer, String> finisher() {
            return StringBuffer::toString;
        }

        @Override
        public Set<Characteristics> characteristics() {
            return Set.of();
        }

        private void add(StringBuffer b, String v) {
            rowLength += v.length();


            if (!first) {
                b.append(delimiter);
            } else {
                first = false;
            }

            if (rowLength > maxRowSize) {
                b.append("\n");
                rowLength = v.length();
            }
            b.append(v);
        }

        private StringBuffer combine(StringBuffer a, StringBuffer b) {
            if (a.lastIndexOf("\n") == a.length()) {
                a.append(b);
            } else {
                for (final var s : b.toString().split(delimiter)) {
                    add(a, s);
                }
            }
            return a;
        }
    }

可以看到add方法和你的for循环是一样的。

像这样使用它:

final String result = Stream.of(values).collect(new CustomStringJoiner(", ", 10));
System.out.println(result);

这将输出:

I, need, to, 
join, a, list, 
of, strings, 
with, commas

【讨论】:

    【解决方案2】:

    给定:

    private static final int maxLength = 15;
    private static final List<String> strings = 
        Arrays.asList("I", "need", "to", "join", "a", "list", "of", "strings", "with", "commas");
    

    我可以看到两种方式。它们都依赖于这样一个事实,即您可以通过这种方式计算 StringBuilder 中当前行的长度:

    int currentLineLength = sb.length() - sb.lastIndexOf("\n");

    1:Stream方式

    • 在除最后一个字符串之外的每个字符串中添加逗号
    • 尝试将每个令牌添加到StringBuilder
      • 如果在StringBuilder中给当前行添加下一个token大于max,添加换行符
      • 添加令牌
    StringBuilder sb = new StringBuilder();
    
    IntStream.range(0, strings.size())
             .mapToObj(i -> strings.get(i) + (i<strings.size()-1 ?  ", " : ""))
             .forEach(s -> addToStringBuilder(s, sb));
    
    System.out.println(sb);
    
    
    private void addToStringBuilder(String s, StringBuilder sb) {
        int currentLineLength = sb.length() - sb.lastIndexOf("\n");
        if (currentLineLength + s.length() > maxLength) {
            sb.append("\n");
        }
        sb.append(s);
    }
    

    2:或者你可以走递归的方式

    String result = concatenate(strings, new StringBuilder());
    System.out.println(result);
    
    private String concatenate(List<String> remainingTokens, StringBuilder sb) {
        if(remainingTokens.size() == 0) {
            return sb.toString();
        }
        String nextToken = remainingTokens.size() > 1
            ? remainingTokens.get(0) + ", "
            : remainingTokens.get(0);
    
        int currentLineLength = sb.length() - sb.lastIndexOf("\n");
        if (currentLineLength + nextToken.length() > maxLength) {
            sb.append("\n");
        }
        sb.append(nextToken);
        return concatenate(remainingTokens.subList(1, remainingTokens.size()), sb);
    }
    

    两个输出:

    I, need, to, 
    join, a, 
    list, of, 
    strings, 
    with, commas
    

    【讨论】:

      【解决方案3】:

      流的代码相当复杂,但你可以这样写:

          public static String joinLabels2(final List<String> strings,) {
          final Optional<String> res = strings.stream()
                  .map(s -> s + ", ")
                  .reduce((s1, s2) ->
                          s1.contains("\n") && (s1 + s2).length() - StringUtils.lastIndexOf(s1, "\n") > 40
                          ||
                          !s1.contains("\n") && (s1 + s2).length() > 40
                          ?
                          s1 + s2 + "\n" : s1 + s2)
                  .map(s -> s.substring(0, s.length() - 2));
          return res.get();
      }
      

      通过将最大长度从“40”更改为“10”进行测试:

      @Test
      public void test() {
          System.out.println(joinLabels2(Arrays.asList("I", "need", "to", "join", "a", "list", "of", "strings", "with", "commas")));
      }
      

      输出:

      I, need, to, 
      join, a, list, 
      of, strings, 
      with, commas,
      

      这样写 reduce 条件会很棒:(s1+s2).length() &gt; 40 ? ... 但是在第一个换行符之后,条件将始终为真,因为 s1 包含先前的归约。

      注意:如果您想比较不带逗号的长度,则需要微调该方法,因为我在缩减之前添加了它们,但您没有在原始代码中添加它们。

      【讨论】:

        【解决方案4】:

        你可以使用 java 8 的流和收集器 api 来做这样的事情,

        private static String joinLables(List<String> lables) {
            final AtomicInteger counter = new AtomicInteger();
            final Integer chunkSize = 3;
            final String DELIMITER = ",";
            return lables.stream()
                    .collect(Collectors.groupingBy(it -> counter.getAndIncrement() / chunkSize)).values()
                    .stream()
                    .map(s -> String.join(DELIMITER, s))
                    .reduce((a, b) -> a + "\n" + b)
                    .get();
        }
        

        【讨论】:

        • 您应该更改 couter.addAndGet(it.length()) 以使其按预期工作
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-10
        • 2016-02-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-04
        相关资源
        最近更新 更多