从已排序的ArrayList中删除重复项,同时保留某些重复项中的元素

3
起初,我认为这将是相当直接的。但我想不出一种有效的解决方法。我想到了一种暴力破解的方法,但那并不是很优雅。我有一个ArrayList。联系人是一个VO类,它有多个成员 - 名称、地区、ID。由于不同的地区出现多次,所以ArrayList中有重复项。该列表按ID排序。以下是一个示例:
条目0 - 名称:John Smith;地区:N;ID:1 条目1 - 名称:John Smith;地区:MW;ID:1 条目2 - 名称:John Smith;地区:S;ID:1 条目3 - 名称:Jane Doe;地区:NULL;ID:2 条目4 - 名称:Jack Black;地区:N;ID:3 条目6 - 名称:Jack Black;地区:MW;ID:3 条目7 - 名称:Joe Don;地区:NE;ID:4
我想将列表转换为下面的样子,通过组合相同ID的重复地区来合并它们。因此,最终列表应该只有4个不同的元素,其中包括合并后的地区。
因此,输出应该如下所示: 条目0 - 名称:John Smith;地区:N、MW、S;ID:1 条目1 - 名称:Jane Doe;地区:NULL;ID:2 条目2 - 名称:Jack Black;地区:N、MW;ID:3 条目3 - 名称:Joe Don;地区:NE;ID:4
你对解决这个问题的最佳方式有什么想法?我不是在寻找实际的代码,而是想要一些思路或提示来完成它。
感谢您的时间!
4个回答

2
你可以在将它们转储(并合并重复项)到TreeMap时对它们进行迭代。然后从TreeMap的值的排序视图中创建一个列表。
在示例代码中,我假设您有一个Entry类,其中包含id、name和regions字段,最后一个字段是Region实例的List。这很容易改为Set,并将Region更改为字符串或其他你正在使用的内容。示例在将条目插入地图之前复制条目,因为当与其他条目合并时,它们将被修改。
SortedMap<Integer, Entry> mergedEntriesMap = new TreeMap<Integer, Entry>();
for (Entry e : entries) {
  if (mergedEntriesMap.contains(e.id)) {
    Entry m = mergedEntriesMap.get(e);
    m.regions.addAll(e.regions);
  } else {
    Entry m = new Entry();
    // copy the entry to keep the original array clean
    m.id = e.id;
    m.name = e.name;
    m.regions = new ArrayList<Region>(e.regions);
    mergedEntriesMap.put(m.id, m);
  }
}

List<Entry> mergedEntries = new ArrayList<Entry>(mergedEntriesMap.values());

1
TreeMapO(log N) 的时间内回答 containsKey。但是这个解决方案的时间复杂度为 O(N log N),因此并不是最优的。 - polygenelubricants
“Optimal”是一个相当模糊的概念。OP可以使用HashMap,但如果这是一个非常大的数据集,上面的代码是一个相当不错的解决方案。优化的方法是根本不使用contains()调用 - 只需调用get()并在get()返回null时构造新的。然而,在这里使用SortedMap并没有真正帮助 - 任何映射实现都可以工作。 - Kevin Day
他希望输出有序,如果输入也是有序的,那么您可以通过迭代它并仅期望合并在连续条目中发生来在O(N)中解决它。我想他已经在处理输入列表的预排序或输出列表的排序时面临着O(N log N),因此我的解决方案尝试同时解决合并和排序问题。 - Santi P.
这是一个非常好的解决方案。我使用了HashMap而不是TreeMap,普通的Map而不是SortedMap。效果挺不错的。非常感谢! - CoolBeans

2

初始数据是否被固定在这个格式中?如果不是,您可能需要考虑更改查询以通过将所有ID分组并形成逗号分隔列表列来检索数据。以下是SQL示例:

SELECT      Id, [Name], Regions = replace
            ((SELECT Region AS [data()]
            FROM RegionTable
            WHERE  Id = u.Id
            ORDER BY Region FOR xml path('')), ' ', ', ')
FROM        [User] u
WHERE       Id IS NOT NULL
GROUP BY Id, [Name]

啊哈,我不知道你可以使用SQL将多行数据合并为单行。不,数据不会被困在这种格式中。我可以修改SQL。它正在针对DB2进行操作。我熟悉REPLACE函数,但是我不确定我是否可以在DB2中按ORDER BY的方式执行FOR。数据不是以XML格式存储的,只是纯文本数据。谢谢! - CoolBeans

1

这是一个伪代码,用于实现你想要的功能。在抽象层面上,你有一个按 K 排序的 Pair<K,V> (first, second) 列表,并且没有两个对是真正相等的(即你可以有 (k1,v1)(k1,v2),但列表中不能有两个 (k1,v1))。

你想要将连续的对 (k,v1),(k,v2),(k,v3) 合并成一组 (k,[v1,v2,v3])

List<Pair<K,V>> in;
List<Pair<K,List<V>>> out = [ ];

Pair<K,V> lastP = SENTINEL_PAIR; // lastP.first matches nothing
Pair<K,List<V>> lastGroup;

for (Pair<K,V> p : in) {
  if (p.first == lastP.first) {  // same group as last
    lastGroup.second.add(p.second);
  } else {                       // start a new group
    lastGroup = (p.first, [ p.second ]);
    out.add(lastGroup);
  }
  lastP = p;
}

在您的情况下,K 是ID,V 是区域。这是O(N)

你可以使用Jakarta Commons Multimap来更优雅地完成这个任务。 - Rahul

0
你看过谷歌的Multimap吗?它几乎是为这种数据结构而创建的,其中有一个键映射到一个Collection的项。因此,在这种情况下,一个String名称将映射到一组Region对象。
Multimap<String, Region> names = HashMultimap.create();
for (Entry entry : entries) {
    names.put(entry.getName(), entry.getRegion());
}
// Now u can get the collection of regions by name
Collection<Region> johnsRegions = names.get("John Smith");

看起来雅加达也提供了类似的功能。谢谢你的提示。 - CoolBeans

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接