Как преобразовать для итерации с условиями в поток Java 8 - PullRequest
0 голосов
/ 04 сентября 2018

В настоящее время у меня есть этот метод, который я хочу преобразовать в стиль потока Java 8 (кстати, у меня мало практики с этим API, цель этого небольшого упражнения):

private static Map<Integer, List<String>> splitByWords(List<String> list) {
   for (int i = 0; i < list.size(); i++) { 
        if(list.get(i).length() > 30 && list.get(i).contains("-")) {
            mapOfElements.put(i, Arrays.stream(list.get(i).split("-")).collect(Collectors.toList()));
        } else if(list.get(i).length() > 30) {
            mapOfElements.put(i, Arrays.asList(new String[]{list.get(i)}));
        } else {
            mapOfElements.put(i, Arrays.asList(new String[]{list.get(i) + "|"}));
        }
   }

   return mapOfElements;
}

Это то, что я получил до сих пор:

private static Map<Integer, List<String>> splitByWords(List<String> list) {
   Map<Integer, List<String>> mapOfElements = new HashMap<>();
   IntStream.range(0, list.size())
        .filter(i-> list.get(i).length() > 30 && list.get(i).contains("-"))
        .boxed()
        .map(i-> mapOfElements.put(i, Arrays.stream(list.get(i).split("-")).collect(Collectors.toList())));

//Copy/paste the above code twice, just changing the filter() and map() functions?

В «старомодном» смысле мне просто нужна одна итерация for, чтобы сделать все, что мне нужно в отношении моих условий. Есть ли способ добиться этого с помощью Stream API или, если я хочу придерживаться его, я должен повторить приведенный выше код, просто изменив условия filter () и map (), следовательно, имея три for итерации?

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

Текущее решение с циклом for выглядит хорошо. Поскольку нужно различать только три случая, нет необходимости обобщать обработку.

Если будет больше случаев для различения, тогда может иметь смысл рефакторинг кода. Мой подход заключается в явном определении различных условий и соответствующей им обработки строк. Позвольте мне объяснить это, используя код из вопроса.

Прежде всего я определяю различные условия, используя перечисление.

  public enum StringClassification {
    CONTAINS_HYPHEN, LENGTH_GT_30, DEFAULT;

    public static StringClassification classify(String s) {
      if (s.length() > 30 && s.contains("-")) {
        return StringClassification.CONTAINS_HYPHEN;
      } else if (s.length() > 30) {
        return StringClassification.LENGTH_GT_30;
      } else {
        return StringClassification.DEFAULT;
      }
    }
  }

Используя это перечисление, я определяю соответствующие строковые процессоры:

  private static final Map<StringClassification, Function<String, List<String>>> PROCESSORS;
  static {
    PROCESSORS = new EnumMap<>(StringClassification.class);
    PROCESSORS.put(StringClassification.CONTAINS_HYPHEN, l -> Arrays.stream(l.split("-")).collect(Collectors.toList()));
    PROCESSORS.put(StringClassification.LENGTH_GT_30, l -> Arrays.asList(new String[] { l }));
    PROCESSORS.put(StringClassification.DEFAULT, l -> Arrays.asList(new String[] { l + "|" }));
  }

Исходя из этого, я могу выполнить всю обработку, используя запрошенную IntStream:

  private static Map<Integer, List<String>> splitByWords(List<String> list) {
    return IntStream.range(0, list.size()).boxed()
      .collect(Collectors.toMap(Function.identity(), i -> PROCESSORS.get(StringClassification.classify(list.get(i))).apply(list.get(i))));
  }

Подход заключается в том, чтобы получить для строки соответствующий StringClassification, а затем, в свою очередь, соответствующий строковый процессор. Строковые процессоры реализуют шаблон стратегии , предоставляя Function<String, List<String>>, который отображает String в List<String> в соответствии с StringClassification.

Быстрый пример:

  public static void main(String[] args) {
    List<String> list = Arrays.asList("123",
      "1-2",
      "0987654321098765432109876543211",
      "098765432109876543210987654321a-b-c");
    System.out.println(splitByWords(list));
  }

Вывод:

{0=[123|], 1=[1-2|], 2=[0987654321098765432109876543211], 3=[098765432109876543210987654321a, b, c]}

Это позволяет легко добавлять или удалять условия и строковые процессоры.

0 голосов
/ 04 сентября 2018

Прежде всего я не вижу смысла использовать тип Map<Integer, List<String>>, когда ключ является индексом. Почему бы не использовать List<List<String>> вместо этого? Если вы не используете фильтр, элементы должны быть в том же индексе, что и входные данные.

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

private static List<List<String>> splitByWords(List<String> list)
{
    return list.stream()
        .map(
            string -> string.length() > 30
                ? Arrays.asList(string.split("-")) 
                : Arrays.asList(string + "|")
        )
        .collect(Collectors.toList());
}

Вы можете добавить более сложную логику, сделав лямбду-мультилинию (в этом случае не требуется). например.

.map(string -> {
    // your complex logic

    // don't forget, when using curly braces you'll
    // need to return explicitly
    return result;
})

Более функциональный подход заключается в группировке строк по размеру с последующим применением определенного обработчика для разных групп. Индекс довольно сложно сохранить прежним, поэтому я изменяю возвращаемое значение на Map<String, List<String>>, чтобы результат можно было получить, указав исходную строку:

private static Map<String, List<String>> splitByWords(List<String> list)
{
    Map<String, List<String>> result = new HashMap<>();
    Map<Boolean, List<String>> greaterThan30;

    // group elements
    greaterThan30 = list.stream().collect(Collectors.groupingBy(
        string -> string.length() > 30
    ));

    // handle strings longer than 30 chars
    result.putAll(
        greaterThan30.get(true).stream().collect(Collectors.toMap(
            Function.identity(), // the same as: string -> string
            string -> Arrays.asList(string.split("-"))
        ))
    );

    // handle strings not longer than 30 chars
    result.putAll(
        greaterThan30.get(false).stream().collect(Collectors.toMap(
            Function.identity(), // the same as: string -> string
            string -> Arrays.asList(string + "|")
        ))
    );

    return result;
}

Вышесказанное кажется большой проблемой, но, на мой взгляд, более понятным. Вы также можете отправить логику для обработки больших и маленьких строк другим методам, зная, что предоставленная строка всегда соответствует критериям.

Это медленнее, чем первое решение. Для списка размером n он должен циклически проходить элементы n для группировки по критериям. Затем выполните цикл по элементам x (0 <= x <= n), которые соответствуют критериям, затем выполните цикл по элементам n - x, которые не соответствуют критериям. (Всего 2 раза весь список.)

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

...