背景
客户有一个这样的需求:
在一个房间列表中有一堆不同名字的房间名,但是总会有名字相同的几个房间名。那么就在一个Adapter中显示当前房间列表中前十位名称一致的数据。
那么就意味着需要构建一个长度为10的数据结构,该结构中至少包含该房间名称和 相同房间名称的数目。
那么就得遍历整个的原始房间名称数据。简化来说:就是一个列表,列表包括各种各样的名字。
客户想要的就是这里面名字出现次数排在前十的房间名称。
思路
算法实现:
- 需要建立一个kv对即散列表,存储当前的名称和其对应的数目;
这个实现就比较简单,就是遍历整个的房间名,如果有增加,就put进去;反之就忽略。
// 遍历获取数据
for (RoomInfo roomInfo : roomList) {
String roomName = roomInfo.getSubject();
// 添加 前缀展示
String roomNameWithPremix = RoomNameListAdapter.ROOM_PREMIX + roomName;
if (roomNameCountMap.containsKey(roomNameWithPremix)) {
int count = roomNameCountMap.get(roomNameWithPremix);
count++;
roomNameCountMap.put(roomNameWithPremix, count);
} else {
roomNameCountMap.put(roomNameWithPremix, 1);
}
}
- 通过排列读取前十个的大小的数据,可以考虑使用PriorityQueue优先级队列;
这也是我第一次使用这个对象,还有很多的不懂。
详细可以查阅官方文档
重点是 比较器的类型:
PriorityQueue<RoomNameCountPair> queue = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
queue = new PriorityQueue<>(Comparator.comparingInt(RoomNameCountPair::getCount));
}
其中::
的使用可以参考。
- 最终代码
HashMap<String, Integer> roomNameCountMap = new HashMap<>();
List<String> subjectWithTopTen = new ArrayList<>();
private List<String> getTopRoomNameList(List<RoomInfo> roomList, int limit) {
// 重新刷
roomNameCountMap.clear();
subjectWithTopTen.clear();
if (roomList.isEmpty()) {
return subjectWithTopTen;
}
// 遍历获取数据
for (RoomInfo roomInfo : roomList) {
String roomName = roomInfo.getSubject();
// 添加 前缀展示
String roomNameWithPremix = RoomNameListAdapter.ROOM_PREMIX + roomName;
if (roomNameCountMap.containsKey(roomNameWithPremix)) {
int count = roomNameCountMap.get(roomNameWithPremix);
count++;
roomNameCountMap.put(roomNameWithPremix, count);
} else {
roomNameCountMap.put(roomNameWithPremix, 1);
}
}
// 优先队列 queue 来对房间名称出现次数进行排序
PriorityQueue<RoomNameCountPair> queue = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
queue = new PriorityQueue<>(Comparator.comparingInt(RoomNameCountPair::getCount));
}
for (Map.Entry<String, Integer> entry : roomNameCountMap.entrySet()) {
queue.offer(new RoomNameCountPair(entry.getKey(), entry.getValue()));
// 是否大于限制的10个,如果是就弹出最高优先级的
if (queue.size() > limit) {
queue.poll();
}
}
while (!queue.isEmpty()) {
subjectWithTopTen.add(0, queue.poll().getRoomName());
}
return subjectWithTopTen;
}
如何理解PriorityQueue
假设有以下的 mock 数据:
roomNameCountMap = {
"room1": 5,
"room2": 3,
"room3": 2,
"room4": 7,
"room5": 1,
...
// 共有20个键值对
}
在代码执行过程中,queue
内的实际数据的内容会如下变化:
- 初始状态:
queue
为空。 - 创建
PriorityQueue
对象时,指定了一个比较器Comparator.comparingInt(RoomNameCountPair::getCount)
,它会根据RoomNameCountPair
对象的count
属性进行排序。 - 第一次循环迭代:
- 将 “room1” 和对应的计数值 5 封装为
RoomNameCountPair
对象,加入queue
。 queue
中的内容为[(room1, 5)]
。
- 将 “room1” 和对应的计数值 5 封装为
- 第二次循环迭代:
- 将 “room2” 和对应的计数值 3 封装为
RoomNameCountPair
对象,加入queue
。 queue
中的内容为[(room2, 3), (room1, 5)]
。
- 将 “room2” 和对应的计数值 3 封装为
- 第三次循环迭代:
- 将 “room3” 和对应的计数值 2 封装为
RoomNameCountPair
对象,加入queue
。 queue
中的内容为[(room3, 2), (room1, 5), (room2, 3)]
。
- 将 “room3” 和对应的计数值 2 封装为
- 第四次循环迭代:
- 将 “room4” 和对应的计数值 7 封装为
RoomNameCountPair
对象,加入queue
。 queue
中的内容为[(room3, 2), (room2, 3), (room1, 5), (room4, 7)]
。
- 将 “room4” 和对应的计数值 7 封装为
- 第五次循环迭代:
- 将 “room5” 和对应的计数值 1 封装为
RoomNameCountPair
对象,加入queue
。 queue
中的内容为[(room5, 1), (room3, 2), (room2, 3), (room1, 5), (room4, 7)]
。
- 将 “room5” 和对应的计数值 1 封装为
- 接下来的循环迭代会继续按照计数值的大小插入元素,并在
queue
的大小超过限制时,弹出计数值最小的元素。最终,queue
中会保留计数值最大的前limit
个元素。
由于我们在创建 PriorityQueue
对象时指定了比较器,因此 queue
中的元素会根据计数值从小到大进行排序。