分组和双重排序列表<人>

时间:2018-11-19 12:49:15

标签: java sorting

我仔细阅读了所有手册和所有SO问题,但仍然无法弄清楚……

我有一个列表(整数代表年龄):

List<Person> people = Arrays.asList
    (
            new Person("bob", 10),
            new Person("sue", 4),
            new Person("tom", 37),
            new Person("jim", 10),
            new Person("boo", 4),
            new Person("ekk", 53),
            new Person("joe", 10)
    );

我需要:

  • 按年龄分组列表,
  • 按组大小排序(降序),
  • 按年龄排序(降序)

因此,使用上面的示例,结果必须像这样:

{10=[bob, jim, joe],4=[sue, boo], 53=[ekk], 37=[tom]}

我尝试过的事情:

我尝试有无流。我都失败了。

注意:我倾向于无流解决方案,因为从下面的代码测试来看,流似乎慢得多(我使用了System.nanotime() )。这3次操作将每次执行数千次,因此可能会有所不同。

我在这里使用流是

List<List<Person>> grpd = new ArrayList<>
    (
            people.stream()
                    .collect
                            (
                                    groupingBy(Person::getAge, toList())
                            )
                    .values()
    );
grpd = grpd.stream().sorted((a, b) -> Integer.compare(b.size(), a.size())).collect(toList());

没有流方法:

    Map<Integer, List<Person>> grouped = new HashMap<>();
    for (Person person : people)
    {
        if (grouped.containsKey(person._age))
        {
            grouped.get(person._age).add(person);
        } else
        {
            List<Person> p = new ArrayList<>();
            p.add(person);
            grouped.put(person._age, p);
        }
    }


    List<Map.Entry<Integer, List<Person>>> entries = new ArrayList<>(grouped.entrySet());
    Collections.sort(entries, new Comparator<Map.Entry<Integer, List<Person>>>()
    {
        @Override
        public int compare(Map.Entry<Integer, List<Person>> o1, Map.Entry<Integer, List<Person>> o2)
        {
            return Integer.compare(o2.getValue().size(), o1.getValue().size());
        }
    });
    Map<Integer, List<Person>> sortedBySize = new LinkedHashMap<>();
    for (Map.Entry<Integer, List<Person>> entry : entries)
    {
        sortedBySize.put(entry.getKey(), entry.getValue());
    }

问题: 我不知道如何在两种情况下都添加最终排序。

public class Person
{
    public String _name;
    public int _age;
    public int getAge() { return _age; }

    public Person(String name, int age)
    {
        _name = name;
        _age = age;
    }

    @Override
    public String toString()
    {
        return _name;
    }
}

4 个答案:

答案 0 :(得分:3)

使用流。

首先,按年龄分组:

Map<Integer, List<Person>> groupedByAge =
    people.stream().collect(groupingBy(Person::getAge));

然后排序此地图的条目:

Comparator<Map.Entry<Integer, List<Person>>> byCount = comparingInt(e -> e.getValue().size());
Comparator<Map.Entry<Integer, List<Person>>> byAge = comparingInt(Map.Entry::getKey);
Stream<Map.Entry<Integer, List<Person>>> sorted =
    groupedByAge.entrySet().stream().sorted(byCount.reversed().thenComparing(byAge.reversed()));

然后从列表中取出列表:

List<List<Person>> result = sorted.map(Map.Entry::getValue).collect(toList());

(您可以将所有内容放到一个表达式中,但我认为这样更容易理解)

答案 1 :(得分:1)

您也曾问过非流解决方案,它是:

Map<Integer, List<Person>> grouped = new HashMap<>();
people.forEach(person -> grouped.computeIfAbsent(
        person.getAge(), 
        k -> new ArrayList<>())
    .add(person));

按年龄分组。现在让我们对条目进行排序,首先按组大小降序,然后按年龄降序:

List<Map.Entry<Integer, List<Person>>> toSort = new ArrayList<>(grouped.entrySet());
toSort.sort(
    Comparator.comparingInt((Map.Entry<Integer, List<Person>> e) -> e.getValue().size())
              .reversed()
              .thenComparingInt(Map.Entry.comparingByKey().reversed()));

现在,toSort是条目的排序列表。您需要将这些条目放入新的地图中:

Map<Integer, List<Person>> sorted = new LinkedHashMap<>();
toSort.forEach(e -> sorted.put(e.getKey(), e.getValue()));

然后sorted保留您想要的结果。

答案 2 :(得分:1)

由于您还在寻找非流式解决方案:

public static Map<Integer, List<Person>> group(List<Person> people) {
  Map<Integer, List<Person>> intermediateGrouping = new HashMap<>();
  for (Person person : people) {
    intermediateGrouping.computeIfAbsent(person.getAge(), k -> new ArrayList<>()).add(person);
  }

  Comparator<Entry<Integer, List<Person>>> byGroupSize = Entry.comparingByValue(Comparator.comparingInt(List::size));
  Comparator<Entry<Integer, List<Person>>> byAge = Entry.comparingByKey();

  List<Entry<Integer, List<Person>>> entries = new ArrayList<>(intermediateGrouping.entrySet());
  entries.sort(byGroupSize.reversed().thenComparing(byAge.reversed()));

  Map<Integer, List<Person>> result = new LinkedHashMap<>(entries.size());
  for (Entry<Integer, List<Person>> entry : entries) {
    result.put(entry.getKey(), entry.getValue());
  }

  return result;
}

或者,如果您希望结果为List<List<Person>>

public static List<List<Person>> group(List<Person> people) {
  Map<Integer, List<Person>> intermediateGrouping = new HashMap<>();
  for (Person person : people) {
    intermediateGrouping.computeIfAbsent(person.getAge(), k -> new ArrayList<>()).add(person);
  }

  Comparator<Entry<Integer, List<Person>>> byGroupSize = Entry.comparingByValue(Comparator.comparingInt(List::size));
  Comparator<Entry<Integer, List<Person>>> byAge = Entry.comparingByKey();

  List<Entry<Integer, List<Person>>> entries = new ArrayList<>(intermediateGrouping.entrySet());
  entries.sort(byGroupSize.reversed().thenComparing(byAge.reversed()));

  List<List<Person>> result = new ArrayList<>(entries.size());
  for (Entry<Integer, List<Person>> entry : entries) {
    result.add(entry.getValue());
  }

  return result;
}

答案 3 :(得分:0)

当无流方式的大小相等时,尝试使用以下实现修改您的排序比较器

 Collections.sort(entries, new Comparator<Map.Entry<Integer, List<Person>>>()
        {
            @Override
            public int compare(Map.Entry<Integer, List<Person>> o1, Map.Entry<Integer, List<Person>> o2)
            {
                if(o1.getValue().size()<o2.getValue().size())
                    return 1;
                else if(o1.getValue().size()>o2.getValue().size())
                    return -1;
                else {
                   if( o1.getKey()< o2.getKey())
                            return 1;
                   else if(o1.getKey()>o2.getKey())
                       return -1;
                   else
                       return 0;
                }


            }
        });

让我知道它是否适用于您所有的测试用例