【问题标题】:Find most common Object from a list从列表中查找最常见的对象
【发布时间】:2011-02-22 22:29:47
【问题描述】:

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

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;
   }
}

如果有两个部门的员工人数相等,则返回哪个并不重要。

谢谢!

【问题讨论】:

    标签: java list data-structures performance counting


    【解决方案1】:

    创建部门 ID 地图 -> 计数。

    这样您就可以通过 id 获得所有计数的集合。您还可以维护一个 max item,它是对具有最高计数的映射条目的引用。

    算法类似于:

    1) 初始化一个 Map 和一个 currentMax
    2) 循环访问员工
    3) 对于每个员工,获取其部门 ID
    4) 做类似 map.get(currentId)
    a) 如果当前计数为空,则初始化它
    5) 增加计数
    6)如果增加的计数>currentMax,更新currentMax

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

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

    【讨论】:

    • 我想了想,我想知道Java中是否有一个地图数据结构可以跟踪最高的Entry值。你知道吗?
    • @adam,我不知道有什么,尽管您可以维护对具有最高计数的 Map.Entry 的单独引用。或者您可以编写自己的类来封装该功能。
    • 我不知道,但如果你真的想这样做,你可以扩展 HashMap 类......然而,这可能远远超出你的程序范围。
    • @Adam - 没有这样的地图数据结构。从计算的角度来看,这样做是没有意义的……除非您需要知道每次更新后的最大值。
    • 您能发一个WORKING EXAMPLE 以便进一步参考
    【解决方案2】:

    这是一个普通的 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λ 背后的公司工作)

    【讨论】:

      【解决方案3】:

      我会使用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;
        }
      }
      

      您需要在 Department 上正确实现 equalshashCode 才能使其正常工作。

      还有一个问题here 提到了将来创建“排行榜”类型Multiset 的可能性,该类型将根据其中包含的每个条目的计数来维持顺序。

      【讨论】:

      【解决方案4】:

      由于只想统计员工人数,因此制作地图相对容易。

      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() 来查看已输入的部门。

      【讨论】:

      • 嗯...我认为这没有帮助。问题在于找到员工最多的部门。从部门 id 到部门的映射不是问题的一部分,因为每个员工都已经拥有对其部门的引用。
      • 这是一种改进,但您不需要保留员工名单。这个问题只需要计数。
      【解决方案5】:

      我会这样做,模 == 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() 提供第一个方法并且不要反转它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-09-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-03
        相关资源
        最近更新 更多