Сортировать и группировать коллекцию java

У меня есть объект, который имеет имя и оценку. Я хотел бы отсортировать коллекцию таких объектов, чтобы они были сгруппированы по имени и отсортированы по максимальному балу в каждой группе (и внутри группы по убыванию).

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

(a, 3)
(a, 9)
(b, 7)
(b, 10)
(c, 8)
(c, 3)

то я бы хотел, чтобы они были отсортированы следующим образом:

(b, 10)
(b, 7)
(a, 9)
(a, 3)
(c, 8)
(c, 3)

это возможно с помощью компаратора? Я не могу понять это, поэтому любые намеки будут оценены.

Ответы

Ответ 1

Нет, вы не можете сделать это с помощью одного вида с одним Comparator.

Вы должны группировать по имени и сортировать группы по наивысшей оценке в группе.

Затем вам нужно свернуть группы обратно в список.

С Java 8

Изменить: поскольку я написал этот ответ, вышла Java 8, что значительно упростило проблему:

import java.util.*;
import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;

List<Record> result = records.stream()
    .sorted(comparingInt(Record::getScore).reversed())
    .collect(groupingBy(Record::getName, LinkedHashMap::new, toList()))
    .values().stream()
    .flatMap(Collection::stream)
    .collect(toList());

Сначала мы сортируем по результату в обратном порядке, а затем группируем с помощью LinkedHashMap, который сохранит порядок вставки для ключей, поэтому сначала появятся клавиши с более высоким счетом.

Сначала сортировка - ОК, если группы малы, поэтому избыточные сравнения между объектами в разных группах не так сильно вредят.

Кроме того, с помощью этого метода дубликаты сохраняются.


В качестве альтернативы, если вы не заботитесь о сохранении дубликатов, вы можете:

Comparator<Record> highestScoreFirst = comparingInt(Record::getScore).reversed();

List<Record> result = records.stream()
        .collect(groupingBy(Record::getName,
                toCollection(() -> new TreeSet<>(highestScoreFirst))))
        .values().stream()
        .sorted(comparing(SortedSet::first, highestScoreFirst))
        .flatMap(Collection::stream)
        .collect(toList());

Если записи группируются в отсортированные TreeSet s, вместо сортировки значений в качестве первой операции потока, а затем сортировки сортируются по их первому, наивысшему значению.

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


Реализация Comparable:

И вы можете сделать это короче, выполнив свой инструмент записи Comparable

public class Record implements Comparable<Record> {
    @Override
    public int compareTo(Record other) {
        // Highest first
        return -Integer.compare(getScore(), other.getScore());

        /* Or equivalently:
           return Integer.compare(other.getScore(), getScore());
        */
    }
    ...
}

List<Record> result = records.stream()
    .collect(groupingBy(Record::getName, toCollection(TreeSet::new)))
    .values().stream()
    .sorted(comparing(SortedSet::first))
    .flatMap(Collection::stream)
    .collect(toList());

Перед Java 8

Изменить: Вот действительно грубый unit test, который демонстрирует один из способов сделать это. Я не очистил его так сильно, как хотелось бы.

В Java это как-то больно, и я обычно использовал Google Guava для этого.

import org.junit.Test;

import java.util.*;

import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;

public class GroupSortTest {

    @Test
    public void testGroupSort() {
        List<Record> records = asList(
                new Record("a", 3),
                new Record("a", 9),
                new Record("b", 7),
                new Record("b", 10),
                new Record("c", 8),
                new Record("c", 3));

        List<SortedMap<Integer, Record>> recordsGroupedByName = groupRecordsByNameAndSortedByScoreDescending(records);
        Collections.sort(recordsGroupedByName, byHighestScoreInGroupDescending());
        List<Record> result = flattenGroups(recordsGroupedByName);

        List<Record> expected = asList(
                new Record("b", 10),
                new Record("b", 7),
                new Record("a", 9),
                new Record("a", 3),
                new Record("c", 8),
                new Record("c", 3));

        assertEquals(expected, result);
    }

    private List<Record> flattenGroups(List<SortedMap<Integer, Record>> recordGroups) {
        List<Record> result = new ArrayList<Record>();
        for (SortedMap<Integer, Record> group : recordGroups) {
            result.addAll(group.values());
        }
        return result;
    }

    private List<SortedMap<Integer, Record>> groupRecordsByNameAndSortedByScoreDescending(List<Record> records) {
        Map<String, SortedMap<Integer, Record>> groupsByName = new HashMap<String, SortedMap<Integer, Record>>();
        for (Record record : records) {
            SortedMap<Integer, Record> group = groupsByName.get(record.getName());
            if (null == group) {
                group = new TreeMap<Integer, Record>(descending());
                groupsByName.put(record.getName(), group);
            }
            group.put(record.getScore(), record);
        }
        return new ArrayList<SortedMap<Integer, Record>>(groupsByName.values());
    }

    private DescendingSortComparator descending() {
        return new DescendingSortComparator();
    }

    private ByFirstKeyDescending byHighestScoreInGroupDescending() {
        return new ByFirstKeyDescending();
    }

    private static class ByFirstKeyDescending implements Comparator<SortedMap<Integer, Record>> {
        public int compare(SortedMap<Integer, Record> o1, SortedMap<Integer, Record> o2) {
            return o2.firstKey().compareTo(o1.firstKey());
        }
    }

    private static class DescendingSortComparator implements Comparator<Comparable> {
        public int compare(Comparable o1, Comparable o2) {
            return o2.compareTo(o1);
        }
    }
}

Ответ 2

Предваряйте сборку и поместите объекты в Map<String, SortedSet<YourObject>> с ключом по имени, где SortedSet - это TreeSet с пользовательским компаратором, который сравнивается по результату.

Затем заберите по набору map values ​​() и поместите группы в SortedSet<SortedSet<YourObject>> со вторым пользовательским компаратором, который сравнивает SortedSets в соответствии с их наибольшим элементом. На самом деле, вместо того, чтобы использовать foreaching, вы можете просто использовать addAll().

Здесь код:

public class SortThings {

    static class Thing {
        public final String name;
        public final int score;
        public Thing(String name, int score) {
            this.name = name;
            this.score = score;
        }
        @Override
        public String toString() {
            return "(" + name + ", " + score + ")";
        }
    }

    public static void main(String[] args) {
        Collection<Thing> things = Arrays.asList(
            new Thing("a", 3),
            new Thing("a", 9),
            new Thing("b", 7),
            new Thing("b", 10),
            new Thing("c", 8),
            new Thing("c", 3)
        );

        SortedSet<SortedSet<Thing>> sortedGroups = sortThings(things);

        System.out.println(sortedGroups);
    }

    private static SortedSet<SortedSet<Thing>> sortThings(Collection<Thing> things) {
        final Comparator<Thing> compareThings = new Comparator<Thing>() {
            public int compare(Thing a, Thing b) {
                Integer aScore = a.score;
                Integer bScore = b.score;
                return aScore.compareTo(bScore);
            }
        };

        // first pass
        Map<String, SortedSet<Thing>> groups = new HashMap<String, SortedSet<Thing>>();
        for (Thing obj: things) {
            SortedSet<Thing> group = groups.get(obj.name);
            if (group == null) {
                group = new TreeSet<Thing>(compareThings);
                groups.put(obj.name, group);
            }
            group.add(obj);
        }

        // second pass
        SortedSet<SortedSet<Thing>> sortedGroups = new TreeSet<SortedSet<Thing>>(new Comparator<SortedSet<Thing>>() {
            public int compare(SortedSet<Thing> a, SortedSet<Thing> b) {
                return compareThings.compare(a.last(), b.last());
            }
        });
        sortedGroups.addAll(groups.values());
        return sortedGroups;
    }

}

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

Ответ 3

public class ScoreComparator implements Comparator<Item>
{

  public int compare(Item a, Item b){

    if (a.name.equals(b.name){
      return a.score.compareTo(b.score);
    }

    return a.name.compareTo(b.Name);    

  }

}

Ответ 4

Да Перейти Comparator

Сначала отдавайте предпочтение name, а затем забивайте. он будет сгруппирован с отсортированным счетом также

    List<Score> scores = new ArrayList<Score>();
    scores.add(new Score("a", 58));
    scores.add(new Score("a", 10));
    scores.add(new Score("b", 165));
    scores.add(new Score("a", 1));
    scores.add(new Score("b", 1658));
    scores.add(new Score("c", 1));
    scores.add(new Score("c", 10));
    scores.add(new Score("c", 0));

    Collections.sort(scores, new Comparator<Score>() {

        public int compare(Score o1, Score o2) {
            if (o1.getName().compareTo(o2.getName()) == 0) {
                return o2.getScore() - o1.getScore();
            } else {
                return o1.getName().compareTo(o2.getName());
            }
        }
    });
    System.out.println(scores);

Update

Как заметил Крис.

import java.util.*;

/**
 *
 * @author Jigar
 */
class Score {

    private String name;
    private List<Integer> scores;

    public Score() {
    }

    public Score(String name, List<Integer> scores) {
        this.name = name;
        this.scores = scores;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Integer> getScores() {
        return scores;
    }

    public void setScores(List<Integer> scores) {
        this.scores = scores;
    }

    @Override
    public String toString() {
        return name + " , " + scores + "\n";
    }
}

public class ScoreDemo { 

    public static void main(String[] args) {
        List<Score> scores = new ArrayList<Score>();


        List<Integer> lstA = new ArrayList<Integer>();
        lstA.add(3);
        lstA.add(9);
        lstA.add(7);
        Collections.sort(lstA);
        Collections.reverse(lstA);

        List<Integer> lstB = new ArrayList<Integer>();
        lstB.add(10);
        lstB.add(8);
        lstB.add(3);
        Collections.sort(lstB);
        Collections.reverse(lstB);

        List<Integer> lstC = new ArrayList<Integer>();
        lstC.add(8);
        lstC.add(3);
        Collections.sort(lstC);
        Collections.reverse(lstC);


        scores.add(new Score("a", lstA));
        scores.add(new Score("b", lstB));
        scores.add(new Score("c", lstC));





        Collections.sort(scores, new Comparator<Score>() {

            public int compare(Score o1, Score o2) {
                return o2.getScores().get(0).compareTo(o1.getScores().get(0));
            }
        });
        System.out.println(scores);

    }
}

Ответ 5

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

    class Item{
      String name;
      int score;
    }

   new Comparator<Item>(){

       @Override
       public int compare(Item o1, Item o2) {
            if (o1.name.equals(o2.name)) {
                return o1.score > o2.score ? 1 : -1; // might have to flip this. I didn't test
            }else {
                return o1.name.compareTo(o2.name);
            }
       }
    };