POJO的“部分”排序列表

时间:2018-11-02 12:42:17

标签: java sorting java-8 nested-loops

我有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中缺少Name5NameSortingList,无法添加到其中,因此应在订购所有内容后添加。有序列表之后的所有内容都可以有任何顺序。

如果它变得更容易,则可以将所有不在lsit中的所有内容与每个唯一元素合并成一个Fooname = "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,然后迭代数据多象素时间以正确的顺序添加数据。我遇到的问题是

  1. 如果NameSortingList中不存在该名称,它可能会丢失条目
  2. 性能真的很差

dataFoo的列表,如最前面所述:

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;
            }
        }
    }
}

如何解决我描述的两个问题?也许对此甚至有一个流式解决方案,我都无法解决?


如有任何疑问,请随时提问

4 个答案:

答案 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());