Java 8 Stream, как получить количество Top N?

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

public class PlayerStatistics {
    int id
    String name;
    int idMatchWon; // key from Match

    // getter , setter
}

public static void main(String[] args) throws Exception {

    List<PlayerStatistics> _players = new ArrayList<PlayerStatistics>();

    _players.add(initialize(1,'John',4));
    _players.add(initialize(2,'Teddy',2));
    _players.add(initialize(3,'Kevin',3));

    // How to get Top 2
    List<PlayerStatistics> _top2Players = extractTop2Players(_players);
}

private List<PlayerStatistics> extractTop2Players (List<PlayerStatistics> _list) {

    List<PlayerStatistics> _topPlayers = new ArrayList<PlayerStatistics>();

    // 1. Group and count 
    Map<String, Long> _players = _list
            .stream()
            .filter(x -> (!"".equals(x.getName()) && x.getName()!= null) )
            .collect(
                    Collectors.groupingBy(
                            PlayerStatistics::getName, Collectors.counting()
                    )
            );
    ;

    // 2 Best Palyers
    Set<String> _sortedPlayers = _players.entrySet().stream()
            .sorted(Map.Entry.comparingByValue(Collections.reverseOrder()))
            .limit(2)
            .map(Entry::getKey)
            .collect(Collectors.toSet())
    ;

    // 3. Rebuild list 
    _topPlayers = _list
            .stream()
            .filter(x -> _sortedPlayers.contains(x.getName()))
            .collect(Collectors.toList())
    ;

    return _topPlayers;
}


private PlayerStatistics initialize (int id, String name, int year, int month, int won, int lost) {
    return 
        new PlayerStatistics()
            .withId(id)
            .withName(name)
            .withIdMatchWon(won)
        );
}

Я бы использовал .sorted() и .limit(2), как и вы. Однако я не изучил весь ваш код.

Ole V.V. 15.09.2018 12:40

Я бы так и поступил.

Peter Lawrey 15.09.2018 15:32
'John' и т. д. Недопустимый синтаксис Java. Далее непонятна ваша задача. Вы говорите, что хотите получить «плееры», но на самом деле вы собираете PlayerStatistics, которых может быть больше, чем по одному на игрока. Кажется, вы предполагаете, что у каждого игрока есть уникальное имя, но вы должны четко указать это. Тем более, что имя - единственное актуальное понятие «игрок». Если это все, что у вас есть о плеере, ваш набор _sortedPlayers уже является окончательным результатом, двумя лучшими «плеерами», поэтому непонятно, почему вы выполняете этот третий шаг, собирая все связанные PlayerStatistics.
Holger 17.09.2018 11:30
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
3
4 606
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

  1. Временная сложность: вы сортируете весь набор данных, который имеет временную сложность O(mlogm), где m является размером вашего первоначального списка проигрывателей. Сразу же вы берете верхние элементы N в своем списке с помощью N << m.

    Ниже я показываю способ улучшить временную сложность алгоритма для O(mlogN), что означает, что в вашем конкретном случае он станет O(m) (это потому, что N=2, поэтому logN=log2=1).

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

    Это можно улучшить, чтобы выполнить только 2 прохода по набору данных: первый для создания карты подсчетов (аналогично тому, что вы уже сделали), а другой для создания структуры, которая будет содержать только верхние элементы N, отсортированные. по убыванию счетчика, при этом результат будет готов к возврату после завершения обхода.

Важный: Решение, приведенное ниже, требует, чтобы ваш класс PlayerStatistics последовательно реализовал методы hashCode и equals.

Сначала у нас есть общий метод topN, который (что неудивительно) извлекает верхние элементы N из любой заданной карты. Он делает это, сравнивая свои записи по значению в порядке убывания (в этой версии значения V должны быть Comparable<V>, но этот алгоритм можно легко расширить для поддержки значений, которые не реализуют Comparable<V>, путем предоставления пользовательского Comparator<V>):

public static 
<K, V extends Comparable<? super V>, T extends Comparable<? super T>>
Collection<K> 
topN(
        Map<K, V> map, 
        int N,
        Function<? super K, ? extends T> tieBreaker) {

    TreeMap<Map.Entry<K, V>, K> topN = new TreeMap<>(
        Map.Entry.<K, V>comparingByValue()      // by value descending, then by key
            .reversed()                         // to allow entries with duplicate values
            .thenComparing(e -> tieBreaker.apply(e.getKey())));

    map.entrySet().forEach(e -> {
      topN.put(e, e.getKey());
      if (topN.size() > N) topN.pollLastEntry();
    });

    return topN.values();
}

Здесь topNTreeMap ведет себя как приоритетная очередь размера N (хотя мы складываем до элементов N+1). Сначала мы помещаем запись в карту topN, затем, если на карте больше записей N, мы немедленно вызываем для нее метод pollLastEntry, который удаляет запись с самым низким приоритетом (согласно порядку ключей TreeMap) . Это гарантирует, что при обходе карта topN будет содержать только отсортированные верхние записи N.

Обратите внимание, что я использую компаратор, который сначала сортирует TreeMap<Map.Entry<K, V>, K> по значениям V в порядке убывания, а затем по клавишам K. Это достигается с помощью функции Function<? super K, ? extends T> tieBreaker, которая преобразует каждый ключ K в значение T, которое должно быть Comparable<T>. Все это позволяет карте содержать записи с повторяющимися значениями V, не требуя, чтобы ключи K также были Comparable<K>.

Наконец, вы должны использовать описанный выше метод следующим образом:

Map<PlayerStatistics, Long> counts = yourInitialListOfPlayers.stream()
    .filter(x -> !"".equals(x.getName()) && x.getName() != null)
    .collect(Collectors.groupingBy(x -> x, Collectors.counting()));

Collection<PlayerStatistics> top2 = topN(counts, 2, PlayerStatistics::getName);

Большое спасибо, Федерико. Прежде всего, чтобы не останавливаться на проблемах с цитатой и не торопиться, чтобы проанализировать мою проблему. И прежде всего иметь возможность абстрагироваться для решения проблемы :). Я просто интегрирую ваши функции в свой код, и все работает отлично.

anthony44 18.09.2018 18:53

@ anthony44 Я рад узнать, что мой ответ помог вам, было весело писать его. Что касается комментариев и наблюдений, то они актуальны, по крайней мере, для новых читателей. Честно говоря, я немного догадался, отвечая на ваш вопрос ... Я предполагал, что PlayerStatistics был просто примером, и что реальный код, который у вас есть, подсчитывает элементы и группы по некоторым свойствам. Комментарии Хольгера помогают посетителям лучше понять суть проблемы и лучше понять ответы.

fps 18.09.2018 18:59

Хороший ответ. Будет ли <T extends Comparable<? super T>> здесь предпочтительнее?

user7 23.10.2018 05:40

@ user7 Я не уверен, но, кажется, лучше

fps 23.10.2018 15:21

Ваше решение вставляет запись карты каждый в ваш TreeSet, удаляя наименьший элемент после каждой вставки. Но все эти вставки log (n) не нужны - вы можете проверить, больше ли входящая запись, чем самая маленькая запись в TreeSet, прежде чем вставлять ее.

Brian Gordon 02.02.2020 04:38

Другие вопросы по теме