«Частично» сортировка списка POJO - PullRequest
0 голосов
/ 02 ноября 2018

У меня есть List объектов следующего класса:

public class Foo {
    private Date date;
    private String name;
    private Long number;
}

Этот список извлекается из базы данных с order by date asc, number desc, но часть, которую нужно постоянно сохранять, это упорядочение по date asc.

Пример результата (Dateformat = 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

Мой текущий подход к этой сортировке состоит в том, чтобы сначала извлечь все даты в виде значений unqiue, затем создать NameSortingList, а затем выполнить многократное повторение данных, чтобы добавить данные в правильном порядке. У меня есть проблема

  1. Может пропустить записи, если имя не существует в NameSortingList
  2. Производительность действительно очень плохая

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

Как я могу исправить 2 проблемы, которые я описал? Может быть, есть даже потоковое решение, которое я не мог бы обернуть?


Если у вас есть какие-либо вопросы, не стесняйтесь спрашивать

Ответы [ 4 ]

0 голосов
/ 02 ноября 2018

Если я правильно понимаю, у вас есть список из базы данных, который по запросу отсортирован по 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));

Хотя я сомневаюсь, что это будет иметь большое значение для небольших списков.

0 голосов
/ 02 ноября 2018

вы можете связать два компаратора один для даты и другой для имени

      List<Foo> collect = data.stream().filter(e -> e.getDate().equals(LocalDate.now()))
                                     .sorted(Comparator.comparing(Foo::getDate)
                                                       .thenComparing(Foo::getName))
                              .collect(Collectors.toList());
0 голосов
/ 02 ноября 2018

Создайте вторичную карту, устанавливающую порядок имен и порядков, затем используйте ее в своей сортировке. Как указано, все отсутствующие имена должны иметь одинаковый порядок - в конце. Если это нежелательно, вы должны сначала добавить их динамически.

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}

Следует отметить, что это не использует тот факт, что входные данные уже отсортированы по дате. Хотя это не важно для небольших наборов данных, таких как это, это может иметь значение, особенно когда у вас так много данных, что они должны пролиться на диск. В этом случае вы можете принять блочную стратегию: читать блоки по дате, упорядочивать их по имени, выводить каждый отсортированный блок. Если блоки достаточно малы, чтобы поместиться в памяти, это может сэкономить усилия по сортировке на диске.

Извините, этот ответ настолько многословен. Несомненно, есть более умные и компактные способы достижения той же цели, но это должно ясно проиллюстрировать концепцию.

0 голосов
/ 02 ноября 2018

Пока нет повторяющихся элементов, я бы просто выбрал один 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;
  }
}
...