按多个字段对列表中的对象进行分组

4

我有一个简单的对象,就像这样:

public class Person {
 private int id;
 private int age;
 private String hobby;

 //getters, setters
}

我想按照属性对一个Person列表进行分组。

输出应该像这样

Person count/Age/Hobby
2/18/Basket
5/20/football

为了更好地理解,附上一张图表

chart

X 轴:兴趣爱好分布 Y 轴:人数分布

颜色代表年龄

我已经使用 map 按一个属性进行了分组,但我不知道如何按多个属性进行分组

//group only by age . I want to group by hobby too
 personMapGroupped = new LinkedHashMap<String, List<Person>>();
 for (Person person : listPerson) {
            String key = person.getAge();
            if (personMapGroupped.get(key) == null) {
                personMapGroupped.put(key, new ArrayList<Person>());
            }
            personMapGroupped.get(key).add(person);
        }

然后我像这样检索可分组的对象:

  for (Map.Entry<String, List<Person>> entry : personMapGroupped .entrySet()) {

            String key = entry.getKey();// group by age
            String value = entry.getValue(); // person count
            // I want to retrieve the group by hobby here too... 
        }

任何建议都将不胜感激。
非常感谢。

实现Comparable接口,然后对列表进行排序。 - Andrew Williamson
分组应该怎么做?应该按单个属性(即每个属性一个映射),按属性组合(即合并的映射键)还是按属性层次结构(例如,先按年龄,再按爱好)对人员进行分组? - Thomas
@Thomas 非常感谢您。组合很不错(比如:PersonCount/Age/Hobby,因为后面我需要在 for 循环中使用它们)。我已经编辑了我的答案。 - ulquiorra
1
很遗憾,您的编辑并没有使问题更加清晰。您是想按年龄分组获取人数,然后再按兴趣爱好分组,例如“所有喜欢打篮球的18岁人”吗?还是您也想独立于他们的年龄,按兴趣爱好获取所有人的信息? - Thomas
你需要用Java做这件事的原因是什么?这可能是你在数据库中会发现更容易的事情。 - ipsi
3个回答

2

实现按照不同字段对人员进行比较的方法。例如,如果您想按年龄分组,请将此方法添加到Person中:

public static Comparator<Person> getAgeComparator(){
    return new Comparator<Person>() {

        @Override
        public int compare(Person o1, Person o2) {
            return o1.age-o2.age;
        }
    };
}

然后您可以简单地调用:Arrays.sort(people,Person.getAgeComparator())或使用以下代码对Collection进行排序:

List<Person> people = new ArrayList<>();
people.sort(Person.getAgeComparator());

要同时使用多个Comparator进行排序,首先为每个字段定义一个Comparator(例如年龄和姓名各一个)。然后可以使用ComparatorChain将它们组合起来。您可以按照以下方式使用ComparatorChain

ComparatorChain chain = new ComparatorChain();
chain.addComparator(Person.getNameComparator());
chain.addComparator(Person.getAgeComparator());

2
您可以将属性简单地组合成一个键。
for (Person person : listPerson) {
    String key = person.getAge() + ";" + person.getHobby();
    if (!personMapGrouped.contains(key)) {
       personMapGrouped.put(key, new ArrayList<Person>());
    }
    personMapGrouped.get(key).add(person);
}

计算条目数量可以通过使用personMapGrouped.get("18;Football").getSize()轻松确定。

2

我不确定您的要求,但我可能会使用多个地图(顺便说一下,Google Guava的Multimap可以使这更容易),以及集合,例如:

//I'm using a HashMultimap since order of persons doesn't seem to be relevant and I want to prevent duplicates   
Multimap<Integer, Person> personsByAge = HashMultimap.create();

//I'm using the hobby name here for simplicity, it's probably better to use some enum or Hobby object
Multimap<String, Person> personsByHobby = HashMultimap.create();

//fill the maps here by looping over the persons and adding them (no need to create the value sets manually

由于我使用值集,Person 需要合理实现 equals()hashCode() 方法,这可能会利用 id 字段。这也有助于查询。

构建子集将非常容易:

Set<Person> age18 = personsByAge.get(18);
Set<Person> basketballers = personsByHobby.get( "basketball" );

//making use of Guava again
Set<Person> basketballersAged18 = Sets.intersection( age18, basketballers );

请注意,我在这里使用了Google Guava,但您可以通过一些额外的手动代码(例如,使用Map<String,Set<Person>>并手动创建值集合以及使用Set.retainAll()方法)来实现相同的效果。

谢谢,我不知道这个Google Guava库,它可以方便很多事情。但是我仍然有困难来使用它来解决我的问题。我更新了一个图表,代表所需的输出,请看一下。 - ulquiorra
@ulquiorra 这应该不难实现。只需按需要将数据分为单独的子集(例如爱好和年龄),并获取这些子集交集的大小作为栏的数据。毕竟,你仍然需要6个单独的值。 - Thomas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接