"Partially" POJO排序列表

"Partially" sorting list of POJO

我有 List 个以下 class 的对象:

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

请注意,NameSortingList 中缺少 Name4Name5,无法添加,因此应在订购的所有商品之后添加。有序列表之后的所有内容都可以有任何顺序。

如果它更容易,所有不在 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,然后多次迭代数据以按正确的顺序添加数据。我遇到的问题是

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

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

如何解决我描述的 2 个问题?也许甚至有一个我无法理解的流式解决方案?


如有任何问题,欢迎提问

只要没有重复的元素,我就只使用一个 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;
  }
}

您可以链接两个比较器,一个用于日期,另一个用于名称

      List<Foo> collect = data.stream().filter(e -> e.getDate().equals(LocalDate.now()))
                                     .sorted(Comparator.comparing(Foo::getDate)
                                                       .thenComparing(Foo::getName))
                              .collect(Collectors.toList());

创建一个辅助地图,建立名称->序号顺序,然后在您的排序中使用它。正如指定的那样,所有缺失的名字都应该以相同的顺序出现——在末尾。如果不需要,您应该先动态添加它们。

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}

应该注意的是,这并没有利用输入已经按日期排序的事实。虽然这对于像这样的小数据集并不重要,但当您有太多数据以至于它应该溢出到磁盘时,它就很重要了。在这种情况下,您可以采用分块策略:按日期读取块,按名称排序,输出每个排序的块。如果块足够小以适合内存,这可以节省基于磁盘排序的工作量。

抱歉,这个回答太冗长了。毫无疑问,还有更聪明、更紧凑的方法可以达到同样的目的,但这应该清楚地说明了这个概念。

如果我没理解错的话,你有一个来自数据库的列表,由于查询,默认情况下按 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 创建一个映射,其中 names 是键,nameSortingList 中的索引是值。这对性能会更好,因此您可以将其更改为:

myList.sort(Comparator.comparing(Foo::getDate)
                      .thenComparing(foo-> nameSortingMap.getOrDefault(foo.getName(), Integer.MAX_VALUE));

虽然我怀疑这对小列表会有多大影响。