Java 将列表项排序到映射中的算法性能

Java 将列表项排序到映射中的算法性能,java,algorithm,performance,sorting,dictionary,Java,Algorithm,Performance,Sorting,Dictionary,给定一个列表,其中每个条目都是 class Entry { public String id; public Object value; } ID | VALUE ---+------------ a | foo a | bar b | foobar a--+- foo '- bar b---- foobar 多个条目可以具有相同的id。我需要一个可以访问属于某个id的所有值的映射: Map<String, List<Object>>

给定一个列表,其中每个条目都是

class Entry {
    public String id;
    public Object value;
}
ID | VALUE
---+------------
a  | foo
a  | bar
b  | foobar
a--+- foo 
   '- bar
b---- foobar
多个条目可以具有相同的
id
。我需要一个可以访问属于某个id的所有值的映射:

Map<String, List<Object>> map;
到一张看起来像

class Entry {
    public String id;
    public Object value;
}
ID | VALUE
---+------------
a  | foo
a  | bar
b  | foobar
a--+- foo 
   '- bar
b---- foobar
如您所见,
contains
为源列表的每个条目调用。这就是为什么我想知道如果我对源列表进行预排序,然后执行以下操作,是否可以改进我的算法:

List<Object> listOfValues = new List<Object>();
String prevId = null;
for (Entry entry : listOfEntries) {
    if (prevId != null && prevId != entry.id) {
        map.put(prevId, listOfValues);
        listOfValues = new List<Object>();
    }
    listOfValues.add(entry.value);
    prevId = entry.id;
}
if (prevId != null) map.put(prevId, listOfValues);
List listOfValues=new List();
字符串prevId=null;
对于(条目:条目列表){
if(prevId!=null&&prevId!=entry.id){
map.put(prevId,listOfValues);
listOfValues=新列表();
}
添加(entry.value);
prevId=entry.id;
}
if(prevId!=null)map.put(prevId,listOfValues);
第二种解决方案的优点是,我不需要为每个条目调用
map.contains()
,但缺点是我必须先排序。此外,第一种算法更容易实现,也不容易出错,因为必须在实际循环之后添加一些代码

因此,我的问题是:哪种方法的性能更好


示例是用Java伪代码编写的,但实际问题也适用于其他编程语言。

不要预先排序。即使是像take这样的快速排序算法,对于n个项目,其平均值也是O(n logn)。之后,您仍然需要O(n)来遍历列表<代码>包含(哈希)映射上的需要固定的时间(签出),不用担心。在线性时间内遍历列表并使用
包含

如果您有一个哈希映射和大量条目,则逐个插入项目将比按列表排序和插入项目快(O(n)vs O(n log n))。如果使用基于树的映射,则两种方法的复杂性相同


然而,我真的怀疑您是否有足够多的条目,所以内存访问模式,以及比较和哈希函数的生效速度有多快。您有两个选项:忽略它,因为差异不会很大,或者对两个选项进行基准测试,看看哪一个在您的系统上工作得更好。如果你没有数以百万计的条目,我会忽略这个问题,选择更容易理解的内容。

希望提供另一个使用流的解决方案

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;

Map<String, List<Object>> map = listOfValues.stream()
    .collect(groupingBy(entry -> entry.id, mapping(entry -> entry.value, toList())));
导入静态java.util.stream.Collectors.groupingBy;
导入静态java.util.stream.Collectors.mapping;
导入静态java.util.stream.Collectors.toList;
Map Map=listOfValues.stream()
.collect(分组方式(entry->entry.id,映射方式(entry->entry.value,toList());
此代码更具声明性—它只指定列表应转换为映射。
然后,库有责任以有效的方式实际执行转换。

如果不回答您的问题,您的数据结构称为多重映射。你可以在番石榴的帮助下得到你需要的东西和/或用它。@Sorin的回答基本上是正确的。在表演方面,我自己也遇到过类似的问题。在我的例子中(整数id;数十亿个条目;许多重复id),第二种方法的速度要快得多,因为排序是缓存有效的,并且与一个很小的常量相关联。但是,在您的情况下,排序字符串会抵消排序的缓存效率;较大的
对象
也可能会稍微降低排序性能。此外,如果没有太多重复的ID,那么第一种方法可能会更快。不过,我不能肯定,“你仍然需要O(n)来完成列表”是吗?如果在添加时进行预排序,则可以使用二进制搜索,有效地将线性探测减少到O(logn)。在Java8的
HashMap
中,当可比较的
值落在同一个散列桶中时,它们就是这样存储的。@Slanec O(n*log(n))+O(logn)仍然大于O(n)+O(n)。@Slanec:我指的是“因此我的问题是:哪种方法的性能更好?”因为OP在这两种情况下都使用了一个简单的foreach循环,所以它是O(n) 。然而,你所说的“[…]在添加时预先排序,然后可以使用二进制搜索[…]”是什么意思?当你必须查看每个值时,你会如何使用二进制搜索?@Vesper:这是O(n log n)+O(log n)=O(n log n)vs.O(n log n),你不必遍历列表两次。虽然我也更喜欢声明性代码,但我不同意你的观点“那么,以有效的方式实际执行转换是库的责任”。当然,给定操作的性能取决于库的实现者。但是对于Java的
groupingBy
“没有保证返回的映射的类型、可变性、序列化性或线程安全性“。因此,如果涉及性能,我会说这是您想要控制的属性,因此,您必须注意。