【问题标题】:Remove duplicates from a list of String Array从字符串数组列表中删除重复项
【发布时间】:2018-09-04 13:56:54
【问题描述】:

我知道有很多关于“删除列表的重复项”的主题。我喜欢HashSet 的解决方案。但是,我所拥有的是一个 String[] 列表,它无法使用它。可能是因为stringArray1.equals(stringArray2) 会返回false,即使两个stringArray 相同;要比较字符串数组,我们必须使用 Arrays.equals,而 HashSet 不是这种情况。

所以我有一个String[] 用户的用户列表,其中只有 2 个字符串:用户名和用户 ID。由于两者都是链接的(每个用户名只有一个用户 ID),我只比较其中一个字符串就足够了。

我需要的是一种从列表中删除重复项的快速方法。

我想过这样的事情:

List<String> userNamesList = new ArrayList<String>();
List<String[]> userListWithoutDuplicates = new ArrayList<String[]>();
for(String[] user : userList){
    if(!userNamesList.contains(user[0])){
        userNamesList.add(user[0]);
        userListWithoutDuplicates.add(user);
    }
}

但是,这需要两个新的 List 和一个循环(我很确定任何其他解决方案仍然需要这个循环)。

我想知道是否没有更好的解决方案。我认为应该已经在某个地方实施了类似的事情。

编辑:我从一个 sql 查询中得到了我的数组。事实上,我有一个数据库和一些用户。一个用户将在 DB 中搜索响应某些条件的其他用户,DB 将 String[] {username, userID} 列表发送回该用户。 所以我已经有一个用户类,其中包含的不仅仅是用户名和 ID。每个连接的用户都有一个此类的实例,但数据库无法访问这些实例,因此她无法发送它。 我认为字符串数组是一个简单的解决方案。我没想到,在某些情况下,一个用户可以在 DB 中被多次引用,因此被选中多次。这就是为什么我的列表中有重复项。

【问题讨论】:

  • 你为什么使用String[]而不是User类?
  • 您使用的是哪个版本的 Java?
  • 您应该将数组转换为具有 2 个字段的对象,并让它们覆盖 equals()hashcode()
  • 我正在使用 java 10。我从 sql 查询中得到了我的数组。我将编辑帖子以更好地解释。
  • @Abila 是的,我理解,但是当您检索数据时,您仍然可以将它们转换为对象。您如何访问您的数据库?

标签: java arrays arraylist


【解决方案1】:

如果您使用的是 Java 8,则可以使用流

String[] arrWithDuplicates = new String[]{"John", "John", "Mary", "Paul"};
String[] arrWithoutDuplicates = Arrays.stream(arrWithDuplicates).distinct().toArray(String[]::new);

arrWithoutDuplicates 中,您将拥有“John”、“Mary”和“Paul”

【讨论】:

  • 他有一个数组列表
  • 这样他就可以使用flatMap函数,例如list.stream().flatMap(Arrays::stream).distinct().collect(Collectors.toList());
  • 不,他不能,因为会创建一个包含用户名和用户 ID 字符串的流。我会将它们放入一个对象中。毕竟是 Java :)
  • 你是对的,使用正确的 equals()hashCode() 方法创建 User 类将是从“干净代码”的角度来看的最佳解决方案,并结合使用流从用户集合或使用Set
【解决方案2】:

最好的方法是将从数据库返回的每个用户映射到具有两个提到的字符串usernameuserID 的对象。那么hashCodeequals 应该根据你对相等/重复的定义来实现。基于此,有很多方法可以消除重复。您可以将所有找到的用户添加到 Set 或流式传输此类用户列表并调用 Stream.distinct() 以将用户减少为唯一用户:

List<User> distinctUsers = users.stream().distinct().collect(Collectors.toList());

如果您需要继续使用当前结构,则不能使用Stream.distinct(),因为它会通过对象标识来比较字符串数组。必须明确指定相等性。我们可以这样做,例如通过以下方式:

Function<String[], String> comparingBy = user -> user[1]; // user[1] = ID
List<String[]> distinctUsers = users.stream()
        .collect(Collectors.groupingBy(comparingBy))
        .values().stream()
        .map(u -> u.get(0))
        .collect(Collectors.toList());

这将按Function comapringBy 对所有用户进行分组。 comapringBy 应该反映您对平等的定义,因此两个平等用户中的一个是重复的。根据Stream.distinct在遭遇顺序中最先出现的元素被保留”。结果是一个不同的列表,一个没有重复的列表。

另一种数据类型是提到的Set。创建TreeSet 时,也可以显式提供相等的定义。我们可以使用和上面一样的comapringBy

Set<String[]> distinctUsers = new TreeSet<>(Comparator.comparing(comparingBy));
distinctUsers.addAll(users);

【讨论】:

  • 这是完全正确的。+ 用于设置比较器
【解决方案3】:

您可以使用toMap 收集器提供自定义keyMapper 函数作为唯一性测试,然后只需使用地图的values 作为结果。

对于您的唯一性测试,我认为使用索引 1(用户 ID)而不是索引 0(用户名)更有意义。但是,如果您想改回它,请使用arr[0] 而不是下面的arr[1]

List<String[]> userList = new ArrayList<>();
userList.add(new String[]{"George","123"});
userList.add(new String[]{"George","123"});
userList.add(new String[]{"George","456"});
List<String[]> userListNoDupes = new ArrayList<>(userList.stream()
    .collect(Collectors.toMap(arr-> arr[1], Function.identity(), (a,b)-> a)).values());
for(String[] user: userListNoDupes) {
    System.out.println(Arrays.toString(user));
}

输出:

[乔治,123]

[乔治,456]

【讨论】:

  • 这项工作并避免使用另一个列表,其中只有名称。谢谢。
【解决方案4】:

已编辑:将 userNamesList 转换为 HashSet,感谢 @Aris_Kortex。这可以将复杂度从 O(n^2) 降低到 O(n),因为在 HashSet 中搜索的复杂度是 O(1)。

    Set<String> userSet = new HashSet<>(userNamesList);
    List<String[]> userListWithoutDuplicates = userList.stream()
        .filter(user -> !userSet.contains(user[0]))
        .collect(Collectors.toList());

distinct() 对流没有帮助,因为它会从流中删除所有重复项:在这种情况下,它会删除第 0 和第 1 个元素等于其他数组中对应元素的数组的重复项。

但据我了解,TC 只想删除那些名称(第 0 个元素)包含在某些预定义列表中的用户。

【讨论】:

  • 不是很理想,因为这将有效地为字符串流的任何给定项目重新迭代整个列表。
  • 可以通过在流之前将 userNamesList 转换为 HashSet 进行一些优化
  • 也许吧,但我看不出有什么理由不使用distinct()
  • 虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。
  • 我说仅根据名称(第 0 个元素)删除就足够了,因为如果名称相同,则 ID(第 1 个元素)也将相同。所以 distinct() 也可以工作。我从来没有使用过这个流(),所以我会看看它。
【解决方案5】:

我当然认为您应该首先使用 Set 而不是列表。 我们可以根据您的时间和空间复杂度进行修改,这里是您代码的简单 2 行答案。

        Set set = new HashSet(userNamesList);
        List<String> list = new ArrayList(set);

在这里运行一个工作示例:https://ideone.com/JznZCE 这真的取决于你需要实现什么,如果你的用户是独一无二的,你应该简单地得到一个集合而不是一个列表,另外如果不是“字符串”,信息包含在用户对象中,用户的顺序不需要可以通过这个来改变,并且可以实现以后按 id 或 name 放置用户。

然后,您可以通过覆盖用户类的 Equals 和 hashcode 方法来更改 equals 的比较方式,以使用自定义实现进行比较。

希望这会有所帮助!

编辑:如果信息源来自数据库,请查看如何使用“DISTINCT”关键字(类似的 mysql 构造)获取唯一列表,以便从代码中处理此逻辑。

【讨论】:

  • 查看我帖子的第二个短语。
  • @Ablia 您需要在自定义的 equals 和 hashcode 方法中处理比较逻辑,覆盖默认值。
  • 是的,我也是这么想的,但是我不知道该怎么做。我的意思是,覆盖 java 类的默认代码。但我正在调查它。
【解决方案6】:

查看本主题:Removing duplicate elements from a List

如果您确实需要这种类型的集合,您可以将列表转换为集合(不允许重复),然后再转换回列表。

【讨论】:

  • 不是答案。您需要回答问题,而不是链接到某些内容。
  • 我已经在帖子中说过它不起作用,因为我有一个字符串列表Array。 HashSet 用来删除重复的方法是 Object1.equals(Object2),它不适用于数组。
猜你喜欢
  • 2016-04-26
  • 1970-01-01
  • 2013-08-20
  • 2012-09-15
  • 1970-01-01
  • 2019-05-17
  • 2014-08-02
  • 2016-01-06
  • 1970-01-01
相关资源
最近更新 更多