我有List
个以下类别的对象:
public class Foo {
private Date date;
private String name;
private Long number;
}
此列表是使用order by date asc, number desc
从数据库中获取的,但是始终需要保留的部分是date asc
的排序。
结果示例(日期格式= MM/dd/yyyy
):
01/01/2016 Name1 928562
01/01/2016 Name2 910785
01/01/2016 Name3 811290
01/01/2016 Name4 811289
01/01/2016 Name5 5000000
02/01/2016 Name3 877702
02/01/2016 Name1 852960
02/01/2016 Name2 749640
02/01/2016 Name4 749500
02/01/2016 Name5 5000000
现在我要订购该列表,以便其显示:
01/01/2016 Name2 910785
01/01/2016 Name1 928562
01/01/2016 Name3 811290
01/01/2016 Name4 811289
01/01/2016 Name5 5000000
02/01/2016 Name2 749640
02/01/2016 Name1 852960
02/01/2016 Name3 877702
02/01/2016 Name4 749500
02/01/2016 Name5 5000000
如您所见,它现在按日期升序和名称排序。名称的顺序存储在另一个列表(NameSortingList
)中:
Name2
Name1
Name3
请注意,Name4
中缺少Name5
和NameSortingList
,无法添加到其中,因此应在订购所有内容后添加。有序列表之后的所有内容都可以有任何顺序。
如果它变得更容易,则可以将所有不在lsit中的所有内容与每个唯一元素合并成一个Foo
与name = "Other"
,Numbers
。此类结果的示例:
01/01/2016 Name2 910785
01/01/2016 Name1 928562
01/01/2016 Name3 811290
01/01/2016 Other 5811289
02/01/2016 Name2 749640
02/01/2016 Name1 852960
02/01/2016 Name3 877702
02/01/2016 Other 5749500
我目前用于这种排序的方法是首先提取所有日期作为不重要的值,然后构建NameSortingList,然后迭代数据多象素时间以正确的顺序添加数据。我遇到的问题是
NameSortingList
中不存在该名称,它可能会丢失条目
data
是Foo
的列表,如最前面所述:
List<String> sortedNames = data.stream().filter(e -> e.getDate().equals(getCurrentMonthDate()))
.map(e -> e.getName()).collect(Collectors.toCollection(ArrayList<String>::new));
Set<Date> uniqueDates = data.stream().map(e -> e.getDate())
.collect(Collectors.toCollection(LinkedHashSet<Date>::new));
List<Foo> sortedFoo= new ArrayList<Foo>();
for (Date d : uniqueDates) {
for (String name : sortedNames) {
for (Foo fr : data) {
if (fr.Date().equals(d) && fr.getName().equals(name)) {
sortedFoo.add(fr);
break;
}
}
}
}
如何解决我描述的两个问题?也许对此甚至有一个流式解决方案,我都无法解决?
如有任何疑问,请随时提问
答案 0 :(得分:4)
如果我的理解正确,那么您会从数据库中获得一个列表,该列表由于查询而默认情况下按date asc, number desc
排序。现在,您要在date asc, name desc
上对其进行排序,其中名称按字母顺序 进行排序,但要根据它们在nameSortingList
中的顺序进行排序(不在此列表中的名称将在末尾排序)?
如果确实如此,那怎么办:
myList.sort(Comparator.comparing(Foo::getDate)
.thenComparing(foo-> {
int index = nameSortingList.indexOf(foo.getName());
return i == -1 ? // If not found, it should be sorted as trailing instead of leading name
Integer.MAX_VALUE
: // Otherwise, sort it on the index in the nameSortingList:
i;} ));
编辑: @tobias_k 在评论中正确指出。最好首先为您的nameSortingList
创建一个Map,其中names
是键,而nameSortingList
中的索引是值。这样会提高性能,因此您可以改成以下方式:
myList.sort(Comparator.comparing(Foo::getDate)
.thenComparing(foo-> nameSortingMap.getOrDefault(foo.getName(), Integer.MAX_VALUE));
尽管我怀疑这对小的清单是否会有很大的不同。
答案 1 :(得分:3)
创建一个辅助地图,以建立名称->常规顺序,然后在排序中使用它。按照规定,所有缺少的名称都应以相同的顺序-最后。如果不希望这样做,则应首先动态添加它们。
public class Sorter {
static String input[] = {
"01/01/2016 Name1 928562",
"01/01/2016 Name2 910785",
"01/01/2016 Name3 811290",
"01/01/2016 Name4 811289",
"02/01/2016 Name3 877702",
"02/01/2016 Name1 852960",
"02/01/2016 Name2 749640",
"02/01/2016 Name4 749500",
"02/01/2016 Name5 5000000"
};
static String names[] = { "Name2", "Name1", "Name3" };
static class Foo {
private Date date;
private String name;
private Long number;
@Override
public String toString() {
return "Foo{" + "date=" + date + ", name=" + name + ", number=" + number + '}';
}
}
static Foo parseInput(String s) throws Exception {
Foo result = new Foo();
String[] strs = s.split(" *");
result.date = new SimpleDateFormat("dd/MM/yyyy").parse(strs[0]);
result.name = strs[1];
result.number = Long.parseLong(strs[2]);
return result;
}
static class NameOrderCompare implements Comparator<Foo> {
final Map<String,Integer> nameOrder = new HashMap<>();
NameOrderCompare(String names[]) {
for (String name : names) {
nameOrder.put(name, nameOrder.size());
}
}
@Override
public int compare(Foo foo1, Foo foo2) {
int cmp = foo1.date.compareTo(foo2.date);
if (cmp != 0) return cmp;
Integer order1 = nameOrder.getOrDefault(foo1.name, Integer.MAX_VALUE);
Integer order2 = nameOrder.getOrDefault(foo2.name, Integer.MAX_VALUE);
return order1 - order2;
}
}
public static void main(String[] args) throws Exception {
List<Foo> foos = new ArrayList<>();
for (String s : input) {
foos.add(parseInput(s));
}
Collections.sort(foos, new NameOrderCompare(names));
for (Foo foo : foos) {
System.out.println(foo);
}
}
}
运行时会产生:
Foo{date=Fri Jan 01 00:00:00 MST 2016, name=Name2, number=910785}
Foo{date=Fri Jan 01 00:00:00 MST 2016, name=Name1, number=928562}
Foo{date=Fri Jan 01 00:00:00 MST 2016, name=Name3, number=811290}
Foo{date=Fri Jan 01 00:00:00 MST 2016, name=Name4, number=811289}
Foo{date=Sat Jan 02 00:00:00 MST 2016, name=Name2, number=749640}
Foo{date=Sat Jan 02 00:00:00 MST 2016, name=Name1, number=852960}
Foo{date=Sat Jan 02 00:00:00 MST 2016, name=Name3, number=877702}
Foo{date=Sat Jan 02 00:00:00 MST 2016, name=Name4, number=749500}
Foo{date=Sat Jan 02 00:00:00 MST 2016, name=Name5, number=5000000}
应该注意,这没有利用输入已经按日期排序的事实。尽管对于像这样的小型数据集而言并不重要,但尤其重要的是当您有太多数据应洒到磁盘上时。在这种情况下,您可以采用逐块策略:按日期读取块,按名称排序,输出每个排序的块。如果块足够小以适合内存,则可以节省基于磁盘的排序工作。
对不起,这个答案太冗长了。毫无疑问,还有更多更聪明,更紧凑的方法可以达到相同的目的,但这应该可以清楚地说明这一概念。
答案 2 :(得分:0)
只要没有重复的元素,我就只用一个TreeSet
,并提供一个比较器,用于按日期,名称,然后按数字排序的元素。
(即使可能有重复的元素,我也只会引入一个全局唯一的字段,并对该字段进行排序)
public class Foo implements Comparable<Foo> {
private Date date;
private String name;
private Long number;
public int compareTo(Foo f) {
if(!date.equals(f.date))return date.compareTo(f.date);
if(!name.equals(f.name))return name.compareTo(f.name);
return number-f.number;
}
}
并将项目添加到TreeSet<Foo>
中。
indexOf
,我建议在构造时将索引存储在对象中:
public class Foo implements Comparable<Foo> {
private Date date;
private String name;
private Long number;
private int index;
public Foo(Date date, String name, Long number, List<String> NameSortingList) {
this.date=date;
this.name=name;
this.number=number;
index=NameSortingList.indexOf(name);
if(index<0)index=Integer.MAX_VALUE;
}
public int compareTo(Foo f) {
if(!date.equals(f.date))return date.compareTo(f.date);
//if(!name.equals(f.name))return name.compareTo(f.name);
if(index!=f.index)return index-f.index;
return number-f.number;
}
}
答案 3 :(得分:0)
您可以链接两个比较器,一个用于日期,另一个用于名称
List<Foo> collect = data.stream().filter(e -> e.getDate().equals(LocalDate.now()))
.sorted(Comparator.comparing(Foo::getDate)
.thenComparing(Foo::getName))
.collect(Collectors.toList());