Stream API - сортировка map

157
22 декабря 2021, 13:40

Случился затык. На вход подается текст, нужно подсчитать количество повторяющихся слов и вывести их в порядке от наиболее повторяющихся к наименее повторяющимся, затык в следующем. Как их можно отсортировать не только по количеству повторений, но и в лексикографическом порядке. (т.е. если есть два слова повторяющихся одинаковое число раз, отсортировать их в лексикографическом порядке).. Уже всю голову сломал .. Пока у меня получилось отсортировать только по количеству повторений..

List<String> list = new ArrayList<>();
BufferedReader inputLines = new BufferedReader(new InputStreamReader(inputStream, charset));
String wordsInText = inputLines.readLine();
Stream.of(wordsInText.split("[^A-Za-zА-Яа-я]+"))
        .map(String::toLowerCase).forEach(p -> list.add(p));
Map<String, Long> countWords = list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
countWords.entrySet().stream().sorted(Map.Entry.<String, Long>comparingByValue().reversed()).forEach(System.out::println);
Answer 1

Можно так:

public static List<String> getRepeatedWordsSorted(String str) {
    Enumeration<String> st = (Enumeration<String>)(Enumeration) new StringTokenizer(str);
    Map<String, Integer> countMap = StreamSupport.stream(Spliterators.spliteratorUnknownSize(st.asIterator(), 0), false)
            .collect(Collectors.toMap(v -> v, v -> 1, Integer::sum));
    Map<Integer, Stream<String>> reverseCountMap = countMap.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getValue, e -> Stream.of(e.getKey()), Stream::concat));
    return reverseCountMap.entrySet().stream().sorted(Map.Entry.comparingByKey())
            .map(e -> e.getValue().sorted()).flatMap(v -> v).collect(Collectors.toList());
}

Хотя соседний вариант с компаратором всё же лучше и с ним этот метод был бы таким:

public static List<String> getRepeatedWordsSorted(String str) {
    Enumeration<String> st = (Enumeration<String>)(Enumeration) new StringTokenizer(str);
    Map<String, Integer> countMap = StreamSupport.stream(Spliterators.spliteratorUnknownSize(st.asIterator(), 0), false)
            .collect(Collectors.toMap(v -> v, v -> 1, Integer::sum));
    return countMap.entrySet().stream().sorted((es1, es2) ->
            es1.getValue().equals(es2.getValue()) ?
                    es1.getKey().compareTo(es2.getKey()) :
                    es1.getValue().compareTo(es2.getValue()))
            .map(Map.Entry::getKey).collect(Collectors.toList());
}
Answer 2

Передаем свою лямбду как компаратор (меняется только последняя строка вашего кода):

countWords.entrySet().stream().sorted((item1, item2) ->
        !item1.getValue().equals(item2.getValue()) ?  // Если значения не одинаковы...
                -item1.getValue().compareTo(item2.getValue()) : // то сравниваем по значениям (минус для обратного порядка)
                item1.getKey().compareTo(item2.getKey()))  // если одинаковы - то сравниваем ключи
        .forEach(System.out::println);

Возможно будет понятнее, если расписать через if:

countWords.entrySet().stream().sorted((item1, item2) -> {
    if (!item1.getValue().equals(item2.getValue())) { // если значения не одинаковы...
        return -item1.getValue().compareTo(item2.getValue()); // то сравниваем по значениям
    } else {
        return item1.getKey().compareTo(item2.getKey());  // если одинаковы - то по ключам
    }
}).forEach(System.out::println);
Answer 3

запускаю 2 стрима, что не есть хорошо, но рефакторинг уже на вашей совести. на быструю руку примерно так:

BufferedReader inputLines = new BufferedReader(new InputStreamReader(inputStream, charset));
String wordsInText = inputLines.readLine();
Map<String, Long> result = Stream.of(wordsInText.split("[^A-Za-zА-Яа-я]+"))
        .map(String::toLowerCase)
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
        .entrySet().stream()
        .sorted((e1, e2) -> {
            final int compareResult = e2.getValue().compareTo(e1.getValue());
            return compareResult != 0 ? compareResult : e1.getKey().compareTo(e2.getKey());
        })
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (x, y) -> y, LinkedHashMap::new));
Answer 4
List<String> sortedList = Stream.of("13","13","23", "12", "12", "23")
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))//считаем элементы
                .entrySet()//достаем пары слово-количество
                .stream()
                .sorted(Map.Entry.<String, Long>comparingByValue().thenComparing(Map.Entry.comparingByKey()))//сначала сортируем по количеству, если равны, то по значению (тут .reversed())
                .map(Map.Entry::getKey)//достаем значения
                .collect(Collectors.toList());//собираем

Реезультат [12, 13, 23]

Если есть желание отсортировать в порядке убывания, то можно использовать .reversed()

READ ALSO
Не переходит по ссылке на сайте selenium Web Driver Java

Не переходит по ссылке на сайте selenium Web Driver Java

Нужно что бы открывало ссылку на видео на сайте youtubecom перепробовал уже и xpath и Linktext и selector ничего не срабатывает вот код

165
Не синхронизируется Gradle в Android Studio

Не синхронизируется Gradle в Android Studio

В школе стоит программа-фильтрВ связи с этим после установки Android Studio (установилась вроде бы нормально) и создании первого проекта, не синхронизируется...

231
Доступ к удаленному компьютеру через IP

Доступ к удаленному компьютеру через IP

Здрасьте! Учу java, не могу решить одну проблему (пока что только одну)Пишу мелкий проект, для публикации арендовал удаленный комп на amazon, развернул...

173
вывод значения общей переменной двух потоков

вывод значения общей переменной двух потоков

Пытаюсь разобраться с многопоточностью в javaВот простенький код:

196