Как создать компаратор с динамическими правилами? - PullRequest
1 голос
/ 11 июня 2019

Мне нужно отсортировать список элементов на основе списка фильтров, которые упорядочены по приоритету.Однако эти фильтры поступают из тела запроса API, поэтому они могут меняться.

У меня есть класс фильтров

public class Filter {
  private String fieldName;
  private String order;

  // Getters and Setters...
}

Некоторые объекты фильтров

Filter filter1 = new Filter("price", "desc");
Filter filter2 = new Filter("size", "asc");

Мой товарКласс выглядит так:

public class Item {
  private String productName;
  private double size;
  private double price;

  // Getters and Setters...
}

Затем я должен отсортировать элементы следующим образом:

Если Item.price равен следующему элементу, сравнить их размер и т. д. ...

Я уже пытался создать Comparator для каждого фильтра, но я не могу связать их в цепочку, поэтому каждый фильтр сортирует список самостоятельно, независимо от предыдущего метода сортировки (иногда переворачивая весь список вверхвниз).

Я также пытался реализовать интерфейс Comparable в классе Item, но метод интерфейса compareTo принимает только один параметр (следующий элемент), но не список правил.

Итак, учитывая список элементов, таких как

List<Item> items = new ArrayList<Item>(
  new Item("foo", 10.0, 5.0),
  new Item("bar", 6.0, 15.0),
  new Item("baz", 7.0, 5.0)
);

и список фильтров, таких как

List<Filter> filters = new ArrayList<Filter>(
  new Filter("price", "desc"),
  new Filter("size", "asc")
);

, я ожидаю, что результат будет

List<Item> sortedItems = new ArrayList<Item>(
  new Item("bar", 6.0, 15.0),
  new Item("baz", 7.0, 5.0),
  new Item("foo", 10.0, 5.0)
);

Не могли бы вы помочь мне?Заранее спасибо!

ВАЖНО: У меня нет проблем со сравнением самих полей.Моя проблема заключается в создании динамического компаратора, который меняет свои сравнения на основе списка фильтров.

Ответы [ 2 ]

1 голос
/ 11 июня 2019

Я полагаю, что следующее должно подтолкнуть вас в правильном направлении в цепочке компараторов для элемента с учетом сравнения переменных фильтров.Он использует рефлексию для вызова геттеров и предполагает, что сравнения выполняются с двойными числами.

Если не гарантируется, что они будут двойными, то адаптируйте приведение отражения к тому, что подходит для всех ваших сценариев использования.

PropertyUtils.getProperty () является частью Apache Commons BeanUtils и может быть заменен на ваш выбор получения значений, будь то с помощью отражения или статических компараторов.

public class Filter {

    // properties, constructors, getters, setters ...

    public Comparator<Item> itemComparator() {
        return (item1, item2) -> {
            Double val1 = (Double) PropertyUtils.getProperty(item1, fieldName);
            Double val2 = (Double) PropertyUtils.getProperty(item2, fieldName);
            return (order.equals("asc") ? val1.compareTo(val2) : val2.compareTo(val1);
        };
    }

    public static Comparator<Item> chainedItemComparators(List<Filter> filters) {
        return filters.stream()
            .map(Filter::itemComparator)
            .reduce((item1, item2) -> 0, (f1, f2) -> f1.thenComparing(f2));
    }
}

К томуиспользуйте цепной компаратор:

public static void main(String[] args) {
    List<Filter> filters = new ArrayList<>(Arrays.asList(
        new Filter("price", "desc"),
        new Filter("size", "asc")
    ));
    List<Item> items = new ArrayList<>(Arrays.asList(
        new Item("bar", 6.0, 15.0),
        new Item("baz", 7.0, 5.0),
        new Item("foo", 10.0, 5.0)
    ));
    items.sort(Filter.chainedItemComparators(filters));
}
0 голосов
/ 11 июня 2019

Вы можете составить компараторы, исходя из начальных реализаций на основе цены и размера.

Следующий метод создает компаратор элементов для данного фильтра:

//Preferring static implementations to reflection-based ones
//if there are too many fields, you may want to use a Map<String, Comparator<Item>>
static Comparator<Item> priceComparator = Comparator.comparing(Item::getPrice);
static Comparator<Item> sizeComparator = Comparator.comparingDouble(Item::getSize);

private static Comparator<Item> itemComparator(Filter filter) {
    Comparator<Item> comparator = "price".equals(filter.getFieldName()) ? 
                                     priceComparator : sizeComparator;

    if ("desc".equals(filter.getOrder()))
        return comparator.reversed();

    return comparator;
}

Из этого вы можетецепные компараторы из списка фильтров:

public static void main(String args[]) {

    Comparator<Item> comparator = itemComparator(filters.get(0));
    for (Filter f : filters.subList(1, filters.size())) {
        comparator = comparator.thenComparing(itemComparator(f));
    }

    items.sort(comparator);
}

Проверяя это, я получаю следующий (ожидаемый) вывод:

[[productName=bar, size=6.0, price=15.0],
 [productName=baz, size=7.0, price=5.0],
 [productName=foo, size=10.0, price=5.0]]
...