从列表中查找最常见的对象

时间:2011-02-22 22:29:47

标签: java list data-structures performance counting

假设我有ListEmployee个对象。 Employee对象具有getDepartment方法,该方法返回Department对象。我想遍历该列表以找到Employee s最多的部门(即Department最常返回的getDepartment对象)。最快的方法是什么?

public class Employee{

   static allEmployees = new ArrayList<Employee>();       

   int id;
   Department department;

   public Employee(int id, Department department){
     this.id = id;
     this.department = department;
     allEmployees.add(this);
   }

   public Department getDepartment(){
     return department;
   }

   public static List<Employee> getAllEmployees(){
      return allEmployees;
   }
}

public class Department{
   int id;
   String name;

   public Department(int id){
     this.id = id;
   }

   public String getName(){
     return name;
   }
}

如果有两个部门的员工人数相同,则返回哪个部门无关紧要。

谢谢!

5 个答案:

答案 0 :(得分:3)

创建部门ID地图 - &gt;计数。

通过id,您可以获得所有计数的集合。您还可以维护一个最大项目,该项目是对具有最高计数的地图条目的引用。

算法类似于:

1)初始化Map和currentMax
2)循环员工
3)为每位员工,获得其部门编号 4)做一些像map.get(currentId)的事情    a)如果当前计数为空,则将其初始化为
5)递增计数
6)如果增加的计数是> currentMax,更新currentMax

该算法将在O(n)中运行;我认为你不能比这更好。它的空间复杂度也是O(n),因为计数的数量与输入的大小成正比。

如果需要,可以创建一个使用合成的类(即包含一个Map和一个List),并管理保持指向具有最高计数的Entry的指针。这样就可以正确封装这部分功能。这种方法的更大好处是它允许您在将项目输入列表时维护计数(您将代理将员工添加到列表中的方法,以便更新地图计数器)。可能会有点矫枉过正。

答案 1 :(得分:2)

这是一个vanilla Java 8解决方案:

Employee.getAllEmployees()
        .stream()
        .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.counting()))
        .entrySet()
        .stream()
        .max(Comparator.comparing(Entry::getValue))
        .ifPresent(System.out::println);

最多两次通过员工名单。如果您愿意添加第三方依赖项,则使用jOOλ的等效解决方案是:

Seq.seq(Employee.getAllEmployees())
   .grouped(Employee::getDepartment, Agg.count())
   .maxBy(Tuple2::v2)
   .ifPresent(System.out::println);

(免责声明:我为jOOλ背后的公司工作)

答案 2 :(得分:1)

我会使用Guava执行类似的操作:

Multiset<Department> departments = HashMultiset.create();
for (Employee employee : employees) {
  departments.add(employee.getDepartment());
}

Multiset.Entry<Department> max = null;
for (Multiset.Entry<Department> department : departments.entrySet()) {
  if (max == null || department.getCount() > max.getCount()) {
    max = department;
  }
}

您需要在equals上正确实施hashCodeDepartment才能实现此目的。

还有一个问题here提到了将来创建“排行榜”类型Multiset的可能性,它会根据其中包含的每个条目的数量来维护订单。

答案 3 :(得分:0)

由于您只想计算员工,因此制作地图相对容易。

HashMap<Department, Integer> departmentCounter;

将部门映射到员工数量(您为每个员工增加计数)。或者,您可以使用列表将整个Employee存储在地图中:

HashMap<Department, List<Employee>> departmentCounter;

然后查看列表的大小。

如果您不知道如何使用该类,您可以查看HashMap文档: http://download.oracle.com/javase/1.4.2/docs/api/java/util/HashMap.html

提示:您需要使用HashMap.keySet()来查看已输入的部门。

答案 4 :(得分:0)

我会这样做,modulo == null和isEmpty检查:

public static <C> Multimap<Integer, C> getFrequencyMultimap(final Collection<C> collection,
    final Ordering<Integer> ordering) {
    @SuppressWarnings("unchecked")
    Multimap<Integer, C> result = TreeMultimap.create(ordering, (Comparator<C>) Ordering.natural());
    for (C element : collection) {
        result.put(Collections.frequency(collection, element), element);
    }
    return result;
}

public static <C> Collection<C> getMostFrequentElements(final Collection<C> collection)       {
    Ordering<Integer> reverseIntegerOrdering = Ordering.natural().reverse();
    Multimap<Integer, C> frequencyMap = getFrequencyMultimap(collection, reverseIntegerOrdering);
    return frequencyMap.get(Iterables.getFirst(frequencyMap.keySet(), null));
}

还有CollectionUtils.getCardinalityMap()将完成第一种方法的工作,但这更灵活,也更加狡猾。

请记住,C类应该很好地实现,即具有equals(),hashCode()并实现Comparable。

您可以使用它:

Collection<Dummy> result = LambdaUtils.getMostFrequentElements(list);

作为奖励,你也可以用类似的方法得到频率较低的元素,只需用Ordering.natural()提供第一个方法,不要反转它。