Присоединиться к списку строк с запятыми и новыми строками

#java #string #java-stream

Вопрос:

Мне нужно соединить список строк запятыми. Но также у меня есть ограничение на длину строки. Например, список 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(Listlt;Stringgt; strings) {  int rowLengthCounter = 0;  StringBuilder result = new StringBuilder();  for (String str : strings) {  rowLengthCounter  = str.length();  if (rowLengthCounter gt; 40) {  result.append("n");  rowLengthCounter = str.length();  }  result.append(str);  result.append(", ");  }  return result.substring(0, result.lastIndexOf(", ")); }  

И это работает. Но, может быть, существует простой метод, такой как String.соединение для соединения строк с указанным разделителем или потоковым API. Спасибо за помощь.

Комментарии:

1. Не существует простого метода в виде существующего класса Java SE, который бы это сделал. Делать это с Stream помощью было бы сложно … что противоречит цели использования Stream .

2. Ваша реализация не учитывает символы разделителя, кроме того, ограничение 40 слишком велико для ожидаемого вывода примера. Это непоследовательно. Ожидание примера может быть выполнено с помощью String.join(", ", strings).replaceAll("\G(?=.{25}).{1,23}, ", "$0n")

Ответ №1:

Можно было бы поспорить о необходимости пара в этом случае. Но другим решением, которое здесь не было приведено, будет создание пользовательского сборщика. Коллектор — это изменяемая операция сокращения и будет выглядеть почти как ваш текущий код цикла for.

 public class CustomStringJoiner implements Collectorlt;String, StringBuffer, Stringgt; {   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 Supplierlt;StringBuffergt; supplier() {  return StringBuffer::new;  }   @Override  public BiConsumerlt;StringBuffer, Stringgt; accumulator() {  return this::add;  }   @Override  public BinaryOperatorlt;StringBuffergt; combiner() {  return this::combine;  }   @Override  public Functionlt;StringBuffer, Stringgt; finisher() {  return StringBuffer::toString;  }   @Override  public Setlt;Characteristicsgt; characteristics() {  return Set.of();  }   private void add(StringBuffer b, String v) {  rowLength  = v.length();    if (!first) {  b.append(delimiter);  } else {  first = false;  }   if (rowLength gt; 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:

вы можете использовать API потоков и коллекторов java 8 для этого,

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

Комментарии:

1. вы должны изменить couter.addAndGet(it.length ()), чтобы это работало должным образом

Ответ №3:

Код довольно сложен с потоками, но вы можете написать его так :

 public static String joinLabels2(final Listlt;Stringgt; strings,) {  final Optionallt;Stringgt; res = strings.stream()  .map(s -gt; s   ", ")  .reduce((s1, s2) -gt;  s1.contains("n") amp;amp; (s1   s2).length() - StringUtils.lastIndexOf(s1, "n") gt; 40  ||  !s1.contains("n") amp;amp; (s1   s2).length() gt; 40  ?  s1   s2   "n" : s1   s2)  .map(s -gt; 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,  

Было бы здорово написать условие сокращения следующим образом : (s1 s2).length() gt; 40 ? ... но после первого разрыва строки условие всегда будет истинным, потому что s1 содержит предыдущее сокращение.

NB: вам нужно точно настроить этот метод, если вы хотите сравнить длину без запятых, потому что я добавил их перед сокращением, но вы этого не сделали в своем исходном коде.

Ответ №4:

Дано:

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

Я вижу 2 способа. Они оба полагаются на тот факт, что вы можете рассчитать длину текущей строки в StringBuilder таким образом:

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

1: Путь потока

  • Добавьте запятые в каждую строку, кроме последней
  • попробуйте добавить каждый токен в StringBuilder
    • Если добавление следующего токена в текущую строку в StringBuilder превышает максимальное значение, добавьте разрыв строки
    • Добавить токен
 StringBuilder sb = new StringBuilder();  IntStream.range(0, strings.size())  .mapToObj(i -gt; strings.get(i)   (ilt;strings.size()-1 ? ", " : ""))  .forEach(s -gt; 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() gt; maxLength) {  sb.append("n");  }  sb.append(s); }  

2: Или вы можете пойти рекурсивным путем

 String result = concatenate(strings, new StringBuilder()); System.out.println(result);  private String concatenate(Listlt;Stringgt; remainingTokens, StringBuilder sb) {  if(remainingTokens.size() == 0) {  return sb.toString();  }  String nextToken = remainingTokens.size() gt; 1  ? remainingTokens.get(0)   ", "  : remainingTokens.get(0);   int currentLineLength = sb.length() - sb.lastIndexOf("n");  if (currentLineLength   nextToken.length() gt; 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