Считайте уникальные символы и проверяйте строку в некоторых случаях, используя Java Stream - PullRequest
5 голосов
/ 17 апреля 2020

Я пытаюсь написать метод, который будет проверять строку. Если строка имеет одинаковое количество каждого символа, например "aabb", "abcabc", "abc", она действительна или содержит один дополнительный символ, такой как "ababa" или "aab", также допустима и в других случаях - недействительно. Обновление: извините, я забыл упомянуть такие случаи, как abcabcab -> a-3, b-3, c -2 -> 2 дополнительных символа (a, b) -> недопустимые. И мой код не охватывает такие случаи. Пробел - это символ, заглавные буквы отличаются от строчных. Теперь у меня есть это, но это выглядит неоднозначно (особенно последние два метода):

public boolean validate(String line) {
    List<Long> keys = countMatches(countChars(line));
    int matchNum = keys.size();
    if (matchNum < 2) return true;
    return matchNum == 2 && Math.abs(keys.get(0) - keys.get(1)) == 1;
}

Подсчет ввода уникальных символов, я бы sh, чтобы получить List<long>, но я не знаю, как:

private Map<Character, Long> countChars(String line) { 
    return line.chars()
               .mapToObj(c -> (char) c)
               .collect(groupingBy(Function.identity(), HashMap::new, counting()));
}


private List<Long> countMatches(Map<Character, Long> countedEntries) {
    return new ArrayList<>(countedEntries.values()
            .stream()
            .collect(groupingBy(Function.identity(), HashMap::new, counting()))
            .keySet());
}

Как я могу оптимизировать метод выше? Мне нужно просто List<Long>, но мне нужно создать карту.

Ответы [ 4 ]

4 голосов
/ 17 апреля 2020

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

private List<Long> distinctFrequencies(String line) {
    return line.chars().mapToObj(c -> (char) c)
            .collect(Collectors.groupingBy(Function.identity(),
                    Collectors.counting()))
            .values().stream()
            .distinct()
            .collect(Collectors.toList());
}

Конечно, все, что вам нужно изменить в вашем методе проверки, это присвоение

List<Long> keys = distinctFrequencies(line);

Если подумать об этом, если вы будете sh повторно использовать API Map<Character, Long> countChars где-то еще, вы могли бы изменить API различных частот, чтобы использовать его как

private List<Long> distinctFrequencies(String line) {
    return countChars(line).values()
            .stream()
            .distinct()
            .collect(Collectors.toList());
}
2 голосов
/ 17 апреля 2020

Вы можете выполнить оценку, если каждый символ в строке имеет одинаковое количество вхождений, используя потоковый API, например:

boolean valid = "aabbccded".chars()
      .boxed()  
      .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))                      
      .values().stream()
      .reduce((a, b) -> a == b ? a : -1L)
      .map(v -> v > 0)
      .get();

РЕДАКТИРОВАТЬ:

после прочтения комментариев, я сейчас полагаем, что поняли требование.

  1. строка считается действительной, если все символы в ней имеют одинаковое количество вхождений, например aabb
  2. или если есть один дополнительный символ, например abb
  3. строка abcabcab недопустима, так как имеет 3a 3b и 2 c и, таким образом, имеет 1 дополнительный a и 1 дополнительный b, что слишком много. следовательно, вы не можете выполнить проверку с помощью списка частот, вам нужна дополнительная информация о том, как часто отличаются длины символов -> Карта

вот новое испытание:

TreeMap<Long, Long> map = "abcabcab".chars()
                .boxed()
                .collect(groupingBy(Function.identity(), counting()))
                .values().stream()
                .collect(groupingBy(Function.identity(), TreeMap::new, counting()));

boolean valid = map.size() == 1 ||        // there is only a single char length
        ( map.size() == 2 &&              // there are two and there is only 1 extra char
        ((map.lastKey() - map.firstKey()) * map.lastEntry().getValue() <= 1));

вся проверка может быть выполнена в одном выражении с помощью метода Collectors.collectingAndThen, который @Nikolas использовал в своем ответе, или вы также можете использовать сокращение:

boolean valid = "aabcc".chars()
    .boxed()
    .collect(groupingBy(Function.identity(), counting()))
    .values().stream()
    .collect(groupingBy(Function.identity(), TreeMap::new, counting()))
    .entrySet().stream()
    .reduce((min, high) -> {
         min.setValue((min.getKey() - high.getKey()) * high.getValue()); // min.getKey is the min char length
         return min;                                                     // high.getKey is a higher char length
                                                                         // high.getValue is occurrence count of higher char length
        })                                                               // this is always negative
    .map(min -> min.getValue() >= -1)
    .get();
1 голос
/ 17 апреля 2020

Вы можете сделать это следующим образом:

  1. сначала посчитайте каждое вхождение символа.
  2. затем найдите минимальное значение для вхождения.
  3. и, наконец, пошаговое суммирование всех значений, чтобы разница с наименьшим значением (minValue) была меньше или равна единице.

    public static boolean validate(String line) {
        Map<Character, Long> map = line.chars()
                     .mapToObj(c -> (char) c)
                     .collect(groupingBy(Function.identity(), Collectors.counting()));
        long minValue = map.values().stream().min(Long::compareTo).orElse(0l);
        return map.values().stream().mapToLong(a -> Math.abs(a - minValue)).sum() <= 1;
    }
    
1 голос
/ 17 апреля 2020

Используйте Collector.collectingAndThen, то есть коллектор, который использует нисходящий Collector и финишер Function, который отображает результат.

  • Используйте Collectors.groupingBy и Collectors.counting, чтобы получить частоту каждого символа в строке.

    // Results in Map<Integer, Long>
    .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())
    
  • Используйте map -> new HashSet<>(map.values()).size() == 1, который проверяет, равны ли все частоты - если так, есть одно отдельное значение.

Wrapping эти два в Collector.collectingAndThen выглядят так:

String line = "aabbccdeed";
boolean isValid = line.chars()                          // IntStream of characters    
    .boxed()                                            // boxed as Stream<Integer>
    .collect(Collectors.collectingAndThen(              // finisher's result type
        Collectors.groupingBy(                          // grouped Map<Integer, Integer>
                Function.identity(),                    // ... of each character
                Collectors.counting()),                 // ... frequency
        map -> new HashSet<>(map.values()).size() == 1  // checks the frequencies
    ));

// aabbccded  -> false
// aabbccdeed -> true
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...