遍历List删除重复元素的方案探究
2012-10-24 20:31
225 查看
遍历List,然后删除其中的元素,这种操作存在风险,如果改变List的结构,就会影响到我们接下来的操作,这是危险的,甚至有些数据我们在毫不知情的情况下就被删除掉。这都是不允许的,为应对这种情况,我们可以做一个映射,将原来List映射到新的List上,比如说,可以将不需要删除的元素放到一个新的List上,而不是在原有List上直接删除得到,这样更加安全,但是很多情况下,我们根本就不知道数据是怎样的,它哪些是重复的,需要进行判断,但是在循环中如果这样写:
这可不是什么正确的代码,因为你能找出来的,只有根本就不重复的元素,但是重复的元素也是要添加的啊(只是只添加一个而已)。那么,只要找到重复的,然后删除掉就行了,是的,这就是万恶的remove()之所以会被使用的原因。“只要删除就行”,这句话是具有潜在风险的,如果你无法保证删除后的List是安全的,请千万不要这么做。
List中有两个方法是关于删除元素的,就是remove()和removeAll() 。remove()会改变List的结构,就是它会删除原来List中元素的索引,然后剩下的每个元素都向前移动一位,这样,当我们要对List的下个元素进行操作的时候,该元素的索引已经发生改变,就可能会出现错误。使用removeAll()存在限制,因为它的参数是一个容器(通常都是与操作容器一样类型),它的原理是把这个参数容器里的元素跟List中的元素进行一一比较(使用equals()),但是,如果这是一个对象容器,存储的是对象,如何比较这是一个大问题。基本上,它是无法处理存储对象的容器的,而且它是全部删除,我们这里是删除重复的,removeAll()显然无法符合我们的需要。那么,我们应该怎么做呢?(想要知道更多有关于remove()和removeAll()的东西,可以参考一下我这篇文章:/article/4889738.html)
使用迭代器的话,就使得这变成可能。迭代器的原理,是保留原有List元素的索引,这样就不会破坏List的结构。我们来看一下代码实现:
这样确实能够安全的删除我们想要删除的元素,但是,它依然不能解决我们上面的问题,因为我们根本就不知道重复的元素到底是什么。 但是这里还是要说一下,迭代器的处理功能是很强大的,它能够处理对象容器,可以完全匹配也可以不用完全匹配,就像下面这样:
要想真正解决我们上面的问题,需要使用到Map中key-value的唯一性问题。我们知道,一个key只能存储一个value,而且key是唯一的,如果我们的数据中,一个key存在多个value,那么,取最后一个value。根据这个特性,我们就能做点事情了。
我们来举个例子,就像这样的数据:2,2,2,2,3,3,4,6,5,2,4,7,6,5,我们想要一个不重复的数列,利用map,我们可以这样做:
因为每个key只能存储一个value,而且key是唯一的,将我们的目标数据变成key,然后取出来,我们就能解决这个问题。
但是我们要解决的是对象容器的问题,那么,map是否也可以呢?答案是可以的。
还是Book的例子。我们的book可能存在这样的重复:(java, 0), (java, 0),(c,1),(c++,2),
那么,我们就可以这样做:
这种做法有个局限,就是无法将自定义对象作为Key值,因为Map无法判断两个自定义对象是否相等,因为它内部使用的是equals或==,如果是(java,0),(java,1),(java,0),(c,0),(c,1)这种情况,就很难说哪个能作为key值呢。所以,我们的方案还得继续改进以满足我们一开始的要求。
上面的例子症结就是Map的equals()无法判断两个自定义对象是否相等,于是,我们的第一个想法就是为什么不自定义自己的equals()方法呢?因为编译器是这样来判断两个对象是否相等的:先是看这个对象是否有自定义的equals()方法,如果没有,再调用默认的equals(),这个equals()是Object类的,它只是比较两个引用的地址。这种比较方法一般情况下是够用的,但是在比较一些更加复杂的对象时就会有问题。但是为什么equals()能够比较String呢?语法上,即使内容相同的两个String对象,也是两个不同的引用,但是在比较的时候还是一样的,因为它们的hashCode()是一样的。所以,如果覆写了Object的equals(),也要顺便覆写hashCode(),因为在比较对象的时候,先调用hashCode() ,如果相同,再调用equals()。
我们这次选择的容器是Set。我们知道,Set里的元素都是不重复的,但这只对于String和基本类型而言,自定义对象要覆写equals()和hashCode()才能发挥作用。
覆写hashCode()有很多方法,只要确保散列码不一样就行,一般我们散列码的结果是与我们对象的数据有关,这次我们的对象中包含两个数据:name和rating,所以,hashCode()这么写:
接着就是equals():
千万不要想当然的的将参数设为RatingBook,因为equals()是不支持泛型的(虽然java后来支持泛型)。这里我先判断obj是否是RatingBook,这样也能顺便判断是否为null。
然后就是我们的测试类:
结果如下:
HashMap也是同样的道理,这里就不改了。
至此,关于这个话题,我们算是讨论完毕了,因为我们想要的效果已经达成了,但是依然是有很多问题亟待我们去研究,因为代码是永远不会有完美的一天。
List<Integer> mlist = new ArrayList<Integer>(); for(int i = 0; i < list.size() - 1; i++){ for(int j = i + 1; j < list.size(); j++){ if(list.get(i) != list.get(j)){ mlist.add(list.get(i)); } } }
这可不是什么正确的代码,因为你能找出来的,只有根本就不重复的元素,但是重复的元素也是要添加的啊(只是只添加一个而已)。那么,只要找到重复的,然后删除掉就行了,是的,这就是万恶的remove()之所以会被使用的原因。“只要删除就行”,这句话是具有潜在风险的,如果你无法保证删除后的List是安全的,请千万不要这么做。
List中有两个方法是关于删除元素的,就是remove()和removeAll() 。remove()会改变List的结构,就是它会删除原来List中元素的索引,然后剩下的每个元素都向前移动一位,这样,当我们要对List的下个元素进行操作的时候,该元素的索引已经发生改变,就可能会出现错误。使用removeAll()存在限制,因为它的参数是一个容器(通常都是与操作容器一样类型),它的原理是把这个参数容器里的元素跟List中的元素进行一一比较(使用equals()),但是,如果这是一个对象容器,存储的是对象,如何比较这是一个大问题。基本上,它是无法处理存储对象的容器的,而且它是全部删除,我们这里是删除重复的,removeAll()显然无法符合我们的需要。那么,我们应该怎么做呢?(想要知道更多有关于remove()和removeAll()的东西,可以参考一下我这篇文章:/article/4889738.html)
使用迭代器的话,就使得这变成可能。迭代器的原理,是保留原有List元素的索引,这样就不会破坏List的结构。我们来看一下代码实现:
Itreator iterator = list.iterator(); while(iterator.hasNext()){ Integer num = iterator.next(); if(num == 2){ iterator.remove(); } }
这样确实能够安全的删除我们想要删除的元素,但是,它依然不能解决我们上面的问题,因为我们根本就不知道重复的元素到底是什么。 但是这里还是要说一下,迭代器的处理功能是很强大的,它能够处理对象容器,可以完全匹配也可以不用完全匹配,就像下面这样:
List<Book> list = new ArrayList<Book>(); //Book对象包含两个数据,名字name和书号number //假设list里面两本书,(java, 1)和(java, 2) Itreator iterator = list.iterator(); while(iterator.hasNext()){ Book book = (Book)iterator.next(); if((book.getName()).equals("java")){ //删除所有名字有java的书 iterator.remove(); } if((book.getName()).equals("java")) && ((book.getNumber()) == 2)){ //删除(java, 2) iterator.remove(); } }
要想真正解决我们上面的问题,需要使用到Map中key-value的唯一性问题。我们知道,一个key只能存储一个value,而且key是唯一的,如果我们的数据中,一个key存在多个value,那么,取最后一个value。根据这个特性,我们就能做点事情了。
我们来举个例子,就像这样的数据:2,2,2,2,3,3,4,6,5,2,4,7,6,5,我们想要一个不重复的数列,利用map,我们可以这样做:
int[] input = new int[]{ 2,2,2,2,3,3,4,6,5,2,4,7,6,5 }; Map<Integer, Integer> output = new HashMap<Integer, Integer>(); for(int i = 0; i < input.length; i++) { output.put(input[i], 0); } for(int i : output.keySet()) { System.out.println(i); }
因为每个key只能存储一个value,而且key是唯一的,将我们的目标数据变成key,然后取出来,我们就能解决这个问题。
但是我们要解决的是对象容器的问题,那么,map是否也可以呢?答案是可以的。
还是Book的例子。我们的book可能存在这样的重复:(java, 0), (java, 0),(c,1),(c++,2),
那么,我们就可以这样做:
Map<String, Integer> map = new HashMap<String, Integer>(); List<Book> list = new ArrayList<Book>(); for(int i = 0; i < list.size(); i++){ map.put(list.get(i).getName(), list.get(i).getNumber()); } Set entries = map.entrySet(); if (entries != null) { Iterator iterator = entries.iterator(); while (iterator.hasNext()) { Book book = new Book(); Map.Entry entry = (Entry) iterator.next(); String key =(String) entry.getKey(); Integer value = (Integer)entry.getValue(); book.setName(key); book.setNumber(value); } }
这种做法有个局限,就是无法将自定义对象作为Key值,因为Map无法判断两个自定义对象是否相等,因为它内部使用的是equals或==,如果是(java,0),(java,1),(java,0),(c,0),(c,1)这种情况,就很难说哪个能作为key值呢。所以,我们的方案还得继续改进以满足我们一开始的要求。
上面的例子症结就是Map的equals()无法判断两个自定义对象是否相等,于是,我们的第一个想法就是为什么不自定义自己的equals()方法呢?因为编译器是这样来判断两个对象是否相等的:先是看这个对象是否有自定义的equals()方法,如果没有,再调用默认的equals(),这个equals()是Object类的,它只是比较两个引用的地址。这种比较方法一般情况下是够用的,但是在比较一些更加复杂的对象时就会有问题。但是为什么equals()能够比较String呢?语法上,即使内容相同的两个String对象,也是两个不同的引用,但是在比较的时候还是一样的,因为它们的hashCode()是一样的。所以,如果覆写了Object的equals(),也要顺便覆写hashCode(),因为在比较对象的时候,先调用hashCode() ,如果相同,再调用equals()。
我们这次选择的容器是Set。我们知道,Set里的元素都是不重复的,但这只对于String和基本类型而言,自定义对象要覆写equals()和hashCode()才能发挥作用。
覆写hashCode()有很多方法,只要确保散列码不一样就行,一般我们散列码的结果是与我们对象的数据有关,这次我们的对象中包含两个数据:name和rating,所以,hashCode()这么写:
public int hashCode() { return this.name.hashCode() + this.rating; }
接着就是equals():
public boolean equals(Object obj) { RatingBook book = new RatingBook(); if (obj instanceof RatingBook) { book = (RatingBook) obj; } return ((book.getRating() == this.rating) &(book.getName().equals(this.name))); }
千万不要想当然的的将参数设为RatingBook,因为equals()是不支持泛型的(虽然java后来支持泛型)。这里我先判断obj是否是RatingBook,这样也能顺便判断是否为null。
然后就是我们的测试类:
Set<RatingBook> set = new HashSet<RatingBook>(); set.add(new RatingBook("java", 1)); set.add(new RatingBook("java", 1)); set.add(new RatingBook("c", 0)); set.add(new RatingBook("c", 0)); set.add(new RatingBook("c", 1)); set.add(new RatingBook("java", 0)); set.add(new RatingBook("java", 0)); set.add(new RatingBook("java", 0)); Iterator<RatingBook> iterator = set.iterator(); while (iterator.hasNext()) { RatingBook book = iterator.next(); System.out.println(book); }
结果如下:
c评分:1 c评分:0 java评分:0 java评分:1
HashMap也是同样的道理,这里就不改了。
至此,关于这个话题,我们算是讨论完毕了,因为我们想要的效果已经达成了,但是依然是有很多问题亟待我们去研究,因为代码是永远不会有完美的一天。
相关文章推荐
- 如何在遍历list集合中删除list集合中的元素
- Python处理list中的重复元素(重命名,统计,删除等)
- [LeetCode] Remove Duplicates from Sorted List 删除排序链表中的重复元素
- java中删除list重复元素
- 删除有序链表的重复元素 Remove Duplicates from Sorted List
- 如何正确遍历删除List中的元素,你会吗?
- 正确遍历删除List中的元素
- foreach遍历list删除元素一定会报错?
- 正确在遍历中删除List元素
- Python 删除list里面的重复元素
- leetcode83---Remove Duplicates from Sorted List(删除重复元素)
- list删除重复元素
- LintCode Remove Duplicates from Sorted List 删除链表中的重复元素
- python list遍历时删除元素的推荐做法
- 遍历删除List中的元素,会报错? 用iterator.remove() 完美解决
- 【数据结构_顺序表_List_1038】顺序表中重复元素的删除
- python中list用法及遍历删除元素
- java 删除ArrayList LinkedList集合中的重复元素
- 正确遍历删除List中的元素(着重看iterator方式)
- 如何正确遍历删除List中的元素,你会吗?