算法实战:根据Key或Value对Map进行排序及其应用
来源:互联网 发布:php生成8位唯一邀请码 编辑:程序博客网 时间:2024/06/05 13:25
摘要:
我们知道,Map是 Java Collection Framework 的重要成员,也是我们最常用的容器类之一。Map的实现多种多样,包括HashMap、LinkedHashMap等。但是,无论实际中使用哪种实现,我们在编程过程中常常会遇到诸如根据Key或Value对Map进行排序、保持Map插入顺序等问题,本文特别针对以上几个问题给出了具体解法,并分享华为一道与我们主题极为相关的笔试题。
版权声明:
本文原创作者:书呆子Rico
作者博客地址:http://blog.csdn.net/justloveyou_/
友情提示:
若读者需要本博文相关完整代码,请移步我的Github自行获取,项目名为 JTools,链接地址为:https://github.com/githubofrico/JTools。
关于 LinkedHashMap 更深入的介绍,请移步我的博文《Map 综述(二):彻头彻尾理解 LinkedHashMap》。
一. 算法概述
我们知道,Map是键值对(Key-Value 对)映射的抽象接口,是 Java Collection Framework 的重要成员,也是我们最常用的容器类之一。Map的实现多种多样,包括HashMap、LinkedHashMap等等。特别地,笔者在博文《Map 综述(一):彻头彻尾理解 HashMap》 和《Map 综述(二):彻头彻尾理解 LinkedHashMap》对HashMap和LinkedHashMap进行了深入地介绍。但是,无论实际中使用哪种实现,我们在编程过程中常常会遇到这样些问题:
如何对Map根据Value进行排序并进行输出呢?(Entry + List + Comaprator)
如何对Map根据Key进行排序并进行输出呢?(使用SortedMap可以使用轻松实现,本文将给出一种更直接的解决方案)
如何使Map保持插入顺序呢?(使用LinkedHashMap可以使用轻松实现)
笔者将在本文着重探讨这些问题的解决方案,并给出华为一道类似的笔试题《简单错误记录》,以飨读者。
二. 算法实现
书归正传,下面的代码给出了以上三个问题的实现样例,下面我们来进行逐一分析。注意,为了保证算法的实用性和鲁棒性,笔者在实现过程中使用了泛型。对于泛型的概念,一言以蔽之,其实质上就是实现了 类型的参数化。此外,如果读者想更全面、详细的了解Java的泛型机制,请见笔者的巨长博文《Java 泛型(Generics) 综述》。
/** * Title: Map的增强实现 * Description: * * 1. 根据Value对Map进行排序,并将每条Map.Entry按序输出,这种排序是不稳定的,其取决于Map的具体实现: * 若使用HashMap实现,由于HashMap是无序的,所以是不稳定的; * 若使用LinkedHashMap实现,由于LinkedHashMap是保留插入顺序的,所以是稳定的。 * 所谓排序稳定是指,相同两项在排序后仍保持最初的顺序,不会颠倒。 * * 2. 根据Key对Map进行排序,并将每条Map.Entry按序输出,这种排序是稳定的,和Map的具体实现无关。 * 因为Key不同于Value,是唯一的。 * * 3. 使Map保持插入顺序,并将每条Map.Entry按序输出,这时我们应该选用LinkedHashMap来实现Map。 * 因为LinkedHashMap本身就是保留插入顺序的。 * * @author rico * @created 2017年5月11日 上午9:01:53 */public class MapUtil { /** * @description 根据Value对Map进行排序,并将每条Map.Entry按序输出 * @author rico * @created 2017年5月11日 上午9:14:10 * @param map * 待排序的Map * @param valueComparator * Value的排序规则 */ public static <K, V> void rankMapByValue(Map<K, V> map, final Comparator<V> valueComparator) { List<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>( map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) { return valueComparator.compare(o1.getValue(), o2.getValue()); } }); for (Map.Entry<K, V> entry : list) { System.out.println("Key : " + entry.getKey() + " , Value : " + entry.getValue()); } } /** * @description 根据Key对Map进行排序,并将每条Map.Entry按序输出 * @author rico * @created 2017年5月11日 上午9:14:10 * @param map * 待排序的Map * @param valueComparator * Key的排序规则 */ public static <K, V> void rankMapByKey(Map<K, V> map, final Comparator<K> keyComparator) { List<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>( map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) { return keyComparator.compare(o1.getKey(), o2.getKey()); } }); for (Map.Entry<K, V> entry : list) { System.out.println("Key : " + entry.getKey() + " , Value : " + entry.getValue()); } } public static void main(String[] args) { // 使用HashMap实现 Map<String, Integer> hashMap = new HashMap<String, Integer>(); hashMap.put("D", 1); hashMap.put("C", 2); hashMap.put("A", 3); hashMap.put("B", 2); hashMap.put("F", 1); hashMap.put("E", 0); System.out.println("对HashMap实现的Map进行Value排序并打印:"); rankMapByValue(hashMap, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { // TODO Auto-generated method stub return Integer.compare(o1, o2); } }); System.out.println(); // 使用LinkedHashMap实现 Map<String, Integer> linkedHashMap = new LinkedHashMap<String, Integer>(); linkedHashMap.put("D", 1); linkedHashMap.put("C", 2); linkedHashMap.put("A", 3); linkedHashMap.put("B", 2); linkedHashMap.put("F", 1); linkedHashMap.put("E", 0); System.out.println("对LinkedHashMap实现的Map进行Value排序并打印:"); rankMapByValue(linkedHashMap, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { // TODO Auto-generated method stub return Integer.compare(o1, o2); } }); System.out.println("\n--------我是分割线--------\n"); System.out.println("对Map进行Key排序并打印:"); rankMapByKey(hashMap, String.CASE_INSENSITIVE_ORDER); // String的一个排序算子 System.out.println("\n--------我是分割线--------\n"); System.out.println("HashMap是不保持插入顺序的,是无序的:"); for (Map.Entry<String, Integer> entry : hashMap.entrySet()) { System.out.println("Key : " + entry.getKey() + " , Value : " + entry.getValue()); } System.out.println(); System.out.println("LinkedHashMap是保持插入顺序的:"); for (Map.Entry<String, Integer> entry : linkedHashMap.entrySet()) { System.out.println("Key : " + entry.getKey() + " , Value : " + entry.getValue()); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
1、对Map根据Key进行排序并进行输出
在上述代码中,方法rankMapByValue实现了”如何对Map根据Value进行排序并进行输出呢?” 这个问题。算法的设计思路是:首先将给定的Map转换成一个以Map.Entry为元素的List,然后我们再使用容器工具类Collections对List进行排序。在进行排序时,客户端需要指定具体的排序规则,即需要传入具体的 Comparator。最后,我们将排序后的List依次打印输出。对应的代码片段如下:
/** * @description 根据Value对Map进行排序,并将每条Map.Entry按序输出 * @author rico * @created 2017年5月11日 上午9:14:10 * @param map * 待排序的Map * @param valueComparator * Value的排序规则 */ public static <K, V> void rankMapByValue(Map<K, V> map, final Comparator<V> valueComparator) { List<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>( map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) { return valueComparator.compare(o1.getValue(), o2.getValue()); } }); for (Map.Entry<K, V> entry : list) { System.out.println("Key : " + entry.getKey() + " , Value : " + entry.getValue()); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
其输出结果如下:
关于Java中的排序算子 Comparator 的更多介绍,请读者移步我的博文《Java Comparator Vs. Comparable》。
2、对Map根据Key进行排序并进行输出
在上述代码中,方法rankMapByKey实现了”如何对Map根据Key进行排序并进行输出呢?” 这个问题。本算法的设计思路与对Map根据Value进行排序并进行输出的设计思路类似,此不赘述。对应的代码片段如下:
/** * @description 根据Key对Map进行排序,并将每条Map.Entry按序输出 * @author rico * @created 2017年5月11日 上午9:14:10 * @param map * 待排序的Map * @param valueComparator * Key的排序规则 */ public static <K, V> void rankMapByKey(Map<K, V> map, final Comparator<K> keyComparator) { List<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>( map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) { return keyComparator.compare(o1.getKey(), o2.getKey()); } }); for (Map.Entry<K, V> entry : list) { System.out.println("Key : " + entry.getKey() + " , Value : " + entry.getValue()); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
其输出结果如下:
3、使Map保持插入顺序
在实际工作学习中,如果我们需要使Map保持插入顺序,那么我们的最佳做法就是使用LinkedHashMap来实现这个Map,因为LinkedHashMap本身就具有保留插入顺序这个特性。相应的测试代码如下:
public static void main(String[] args) { // 使用HashMap实现 Map<String, Integer> hashMap = new HashMap<String, Integer>(); hashMap.put("D", 1); hashMap.put("C", 2); hashMap.put("A", 3); hashMap.put("B", 2); hashMap.put("F", 1); hashMap.put("E", 0); // 使用LinkedHashMap实现 Map<String, Integer> linkedHashMap = new LinkedHashMap<String, Integer>(); linkedHashMap.put("D", 1); linkedHashMap.put("C", 2); linkedHashMap.put("A", 3); linkedHashMap.put("B", 2); linkedHashMap.put("F", 1); linkedHashMap.put("E", 0); System.out.println("HashMap是不保持插入顺序的,是无序的:"); for (Map.Entry<String, Integer> entry : hashMap.entrySet()) { System.out.println("Key : " + entry.getKey() + " , Value : " + entry.getValue()); } System.out.println(); System.out.println("LinkedHashMap是保持插入顺序的:"); for (Map.Entry<String, Integer> entry : linkedHashMap.entrySet()) { System.out.println("Key : " + entry.getKey() + " , Value : " + entry.getValue()); } }/* Output: HashMap是不保持插入顺序的,是无序的: Key : D , Value : 1 Key : E , Value : 0 Key : F , Value : 1 Key : A , Value : 3 Key : B , Value : 2 Key : C , Value : 2 --------我是分割线-------- LinkedHashMap是保持插入顺序的: Key : D , Value : 1 Key : C , Value : 2 Key : A , Value : 3 Key : B , Value : 2 Key : F , Value : 1 Key : E , Value : 0 *///:~
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
所以,使Map保持插入顺序最简单的办法就是使用LinkedHashMap实现。与LinkedHashMap相比,HashMap则是不能保证插入顺序,是乱序的。
三. 算法实战与应用
1、题目描述
在本节,笔者分享华为一道与我们主题极为相关的笔试题《简单错误记录》。下面是这道编程题的详细描述。
题目:简单错误记录
题目描述:开发一个简单错误记录功能小模块,能够记录出错的代码所在的文件名称和行号,要求如下:
记录最多8条错误记录,对相同的错误记录(即文件名称和行号完全匹配)只记录一条,错误计数增加;(文件所在的目录不同,文件名和行号相同也要合并);
超过16个字符的文件名称,只记录文件的最后有效16个字符;(如果文件名不同,而只是文件名的后16个字符和行号相同,也不要合并)
输入的文件可能带路径,记录文件名称不能带路径
输入描述:
一行或多行字符串。每行包括带路径文件名称,行号,以空格隔开。文件路径为windows格式如:E:\V1R2\product\fpgadrive.c 1325
输出描述:
将所有的记录统计并将结果输出,格式:文件名代码行数数目,一个空格隔开,如: fpgadrive.c 1325 1 结果根据数目从多到少排序,数目相同的情况下,按照输入第一次出现顺序排序。如果超过8条记录,则只输出前8条记录.如果文件名的长度超过16个字符,则只输出后16个字符
输入例子:
E:\V1R2\product\fpgadrive.c 1325
输出例子:
fpgadrive.c 1325 1
2、题目求解
public class Main { public static void main(String[] args) { // 应该使用LinkedHashMap实现而不是Hashmap实现,因为题目输出描述中要求保证: // 结果根据数目从多到少排序,数目相同的情况下,按照输入第一次出现顺序排序。 Map<String, Integer> map = new LinkedHashMap<String, Integer>(); String key; String filename; String path; Scanner in = new Scanner(System.in); while (in.hasNext()) { path = in.next(); // 将路径转换为文件名 int id = path.lastIndexOf('\\'); // 如果找不到说明只有文件名没有路径 filename = id < 0 ? path : path.substring(id + 1); int linenum = in.nextInt(); key = filename + " " + linenum; // 统计频率 if (map.containsKey(key)) { map.put(key, map.get(key) + 1); } else { map.put(key, 1); } } in.close(); // 对记录进行排序 List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>( map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() { // 降序 @Override public int compare(Map.Entry<String, Integer> value0, Map.Entry<String, Integer> value1) { return Integer.compare(value1.getValue(), value0.getValue()); } }); // 只输出前8条 for (int i = 0; i < 8; i++) { String[] str = list.get(i).getKey().split(" "); String fname = str[0].length() > 16 ? str[0].substring(str[0] .length() - 16) : str[0]; String count = str[1]; System.out.println(fname + " " + count + " " + list.get(i).getValue()); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
如上图所示,程序被Accept。反观本题的具体实现,其本质上就是我们在上文提到过的算法。
四. 更多
关于HashMap 和 LinkedHashMap的深入介绍,请见笔者的博文《Map 综述(一):彻头彻尾理解 HashMap》 和《Map 综述(二):彻头彻尾理解 LinkedHashMap》。
关于Java中的排序算子 Comparator 的更多介绍,请读者移步我的博文《Java Comparator Vs. Comparable》。
如果读者想更全面、详细的了解Java的泛型机制,请见笔者的巨长博文《Java 泛型(Generics) 综述》。
引用
《简单错误记录》
- 算法实战:根据Key或Value对Map进行排序及其应用
- 算法实战:根据Key或Value对Map进行排序及其应用
- 算法实战:根据Key或Value对Map进行排序及其应用
- C++: 根据key或value对map排序
- map 对key 或 value排序
- 根据value字段对map进行排序
- 对map根据value进行排序
- 根据value值对map进行排序
- Java基础 —— 根据 Key 或是 Value 对 Map 进行排序
- 根据map中的key或者value值进行排序
- 分别根据key和value对HashMap进行排序
- java中根据value对key进行排序
- STL 对map的key和value进行大小排序
- 对Map的key和value进行排序
- 对Map的key和value进行排序
- Map根据value进行排序
- Map根据value进行排序
- Map根据value进行排序
- Integer,int,String相互转换
- Struts2框架自学之路——拦截器
- 解决VS2015安装后stdio.h ucrtd.lib等文件无法识别问题,即include+lib环境变量配置
- 基于DB的全局唯一id
- 表达式
- 算法实战:根据Key或Value对Map进行排序及其应用
- Activity的传值的几种方式
- java生成验证码
- 如何在地图上打点数百万条数据
- 【算法题】链表冒泡排序
- mr中的combiner
- 内存监视
- Cookie应用:显示上次访问页面时间
- bzoj 4278: [ONTAK2015]Tasowanie&bzoj 1692: [Usaco2007 Dec]队列变换 后缀数组+贪心