我尝试使用HashSet
删除ArrayList<StringBuilder>
的重复项。
E.g。这是一个ArrayList
,每一行都是StringBuilder
个对象。
"u12e5 u13a1 u1423"
"u145d"
"u12e5 u13a1 u1423"
"u3ab4 u1489"
我想得到以下内容:
"u12e5 u13a1 u1423"
"u145d"
"u3ab4 u1489"
我目前的实施是:
static void removeDuplication(ArrayList<StringBuilder> directCallList) {
HashSet<StringBuilder> set = new HashSet<StringBuilder>();
for(int i=0; i<directCallList.size()-1; i++) {
if(set.contains(directCallList.get(i)) == false)
set.add(directCallList.get(i));
}
StringBuilder lastString = directCallList.get(directCallList.size()-1);
directCallList.clear();
directCallList.addAll(set);
directCallList.add(lastString);
}
但随着ArrayList
大小的增长,性能变得越来越差。这个实现有什么问题吗?或者你在表现方面有更好的表现吗?
答案 0 :(得分:9)
StringBuilder没有实现equals()或hashcode()。如果它们是完全相同的对象,则两个StringBuilder仅相等,因此将它们添加到HashSet将不会排除具有相同内容的两个不同的StringBuilder对象。
您应该将StringBuilders转换为String对象。
此外,您应该在构造函数中使用“初始容量”初始化HashSet。如果处理大量对象,这将有助于提高速度。
最后,在添加对象之前,没有必要在hashset上调用contains()。只需将你的字符串添加到集合中,集合将拒绝重复(并将返回false)。
答案 1 :(得分:2)
让我们分析一下你的方法,找出我们可以改进的地方:
static void removeDuplication(ArrayList<StringBuilder> directCallList) {
HashSet<StringBuilder> set = new HashSet<StringBuilder>();
for(int i=0; i<directCallList.size()-1; i++) {
if(set.contains(directCallList.get(i)) == false)
set.add(directCallList.get(i));
}
对于ArrayList
中的每个元素,此for循环重复一次。对于手头的任务来说,这似乎是不可避免的。但是,由于HashSet
只能包含每个项目中的一个,因此if
语句是多余的。 HashSet.add()
再次进行完全相同的检查。
StringBuilder lastString = directCallList.get(directCallList.size()-1);
我不明白是否需要从列表中获取lastString
然后添加它。如果您的循环正常工作,它应该已添加到HashSet
。
directCallList.clear();
根据列表的实现情况,这可能需要O(n)
时间,因为它可能需要访问列表中的每个元素。
directCallList.addAll(set);
同样,这需要O(n)
次。如果没有重复项,set
包含原始项目。
directCallList.add(lastString);
这一行似乎是一个逻辑错误。您将添加String
已添加到set
并添加到directCallList
。
}
总体而言,此算法需要O(n)
时间,但常量因子为3
。如果可以减少此因素,则可以提高性能。一种方法是简单地创建一个新的ArrayList
,而不是清除现有的removeDuplication()
。
此外,如果您使用正确的构造函数并且返回ArrayList
而不重复,则可以在一行中写入此static List<StringBuilder> removeDuplication(List<StringBuilder> inList) {
return new ArrayList<StringBuilder>(new HashSet<StringBuilder>(inList));
}
函数:
StringBuilder
当然,这仍然没有解决其他人指出的{{1}}问题。
答案 2 :(得分:1)
所以你有其他选择,但我喜欢我的解决方案简短,简单,重点突出。我已将您的方法更改为不再操作参数,而是返回新的List
。我使用Set<String>
查看是否已包含每个StringBuilder
的内容并返回唯一的String
。我还为每个循环使用了a而不是通过索引访问。
static List<StringBuilder> removeDuplication(List<StringBuilder> directCallList) {
HashSet<String> set = new HashSet<String>();
List<StringBuilder> returnList = new ArrayList<StringBuilder>();
for(StringBuilder builder : directCallList) {
if(set.add(builder.toString())
returnList.add(builder);
}
return returnList;
}
答案 3 :(得分:0)
正如Sam所说,StringBuider
不会覆盖hashCode
和equals
,因此Set
将无法正常运作。
我认为答案是将Builder包装在一个只执行一次toString的对象中:
class Wrapper{
final String string;
final StringBuilder builder;
Wrapper(StringBuilder builder){
this.builder = builder;
this.string = builder.toString();
}
public int hashCode(){return string.hashCode();}
public boolean equals(Object o){return string.equals(o);}
}
public Set removeDups(List<StringBuilder> list){
Set<Wrapper> set = ...;
for (StringBuilder builder : list)
set.add(new Wrapper(builder));
return set;
}
可以更新removeDups
方法以从集合中提取构建器并返回List<StringBuilder>
答案 4 :(得分:0)
正如所解释的,StringBuilders不会覆盖Object#equals
而不是Comparable
。
虽然使用StringBuilders连接你的字符串是可行的方法,但我建议你完成连接后,你应该存储基础字符串(stringBuilder.toString()
)而不是列表中的StringBuilders。
删除重复项然后变为一行:
Set<String> set = new HashSet<String>(list);
或者甚至更好,如果您不需要知道重复项,则直接将字符串存储在集合中。