Найти все объекты в ArrayList, которые удовлетворяют списку критериев в Java - PullRequest
0 голосов
/ 05 мая 2018

Для конкретного примера у меня есть класс City, который выглядит так:

public class City {
    private String name;
    private int total; // total population
    private int men;   // male population
    private int women; // female population

    // constructor and getters...
}

Я хочу написать метод, который может принимать список City объектов и список критериев (total, men, women) и возвращать все объекты внутри, которые удовлетворяют этим критериям. У каждого критерия есть диапазон, так что я могу, например, найти все города с общей численностью населения 1000, 2000 И женское население между 200, 300.

Я думаю, мы можем создать класс Criterion, который выглядит следующим образом:

public class Criterion {
    String crit;
    int min; 
    int max; 

    public Criterion (String crit, int min, int max){
        this.crit = crit;
        this.min = min;
        this.max = max;
    }
} 

Затем мы можем передать в список список этих Criterion объектов с диапазоном. Это хороший способ решить это? Что мне нужно сначала отсортировать, чтобы использовать бинарный поиск? Какова общая идея для решения этой проблемы?

Ответы [ 4 ]

0 голосов
/ 05 мая 2018

Объект Criterion можно заменить на Спецификация .

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

Скажем, мы хотим отфильтровать все города, включая только те, которые имеют:

  1. Население выше X;
  2. но ниже Y;
  3. Независимо от население, мы хотим включить все города, где больше женщин затем мужчины.

Нам нужно разместить где-нибудь логику вроде:

population > x && population < y || men < women

Спецификация - это просто интерфейс с одним методом isSatisfiedBy(city), который определяет, соответствует ли объект всем критериям и должен ли он быть включен в список результатов.

interface Specification<T> {
    public boolean isSatisfiedBy(T t);
}

Затем нам нужно иметь несколько объектов логической композиции, каждый из которых реализует интерфейс Specification:

class Or<T> implements Specification<T> {
    Specification<T> left;
    Specification<T> right;
    Or(Specification<T> left, Specification<T> right) {
        this.left = left;
        this.right = right;
    }
    @Override
    public boolean isSatisfiedBy(T t) {
        return left.isSatisfiedBy(t) || right.isSatisfiedBy(t);
    }
}

class And<T> implements Specification<T> {
    Specification<T> left;
    Specification<T> right;
    And(Specification<T> left, Specification<T> right) {
        this.left = left;
        this.right = right;
    }
    @Override
    public boolean isSatisfiedBy(T t) {
        return left.isSatisfiedBy(t) && right.isSatisfiedBy(t);
    }
}

class Not<T> implements Specification<T> {
    Specification<T> specification;
    Not(Specification<T> specification) {
        this.specification = specification;
    }
    @Override
    public boolean isSatisfiedBy(T t) {
        return !this.specification.isSatisfiedBy(t);
    }
}

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

class MaxPopulation implements Specification<City> {
    private final int max;
    MaxPopulation(int max) {
        this.max = max;
    }
    @Override
    public boolean isSatisfiedBy(City city) {
        return city.getTotal() <= this.max;
    }
}

class MinPopulation implements Specification<City> {
    private final int min;
    MinPopulation(int min) {
        this.min = min;
    }
    @Override
    public boolean isSatisfiedBy(City city) {
        return city.getTotal() >= this.min;
    }
}

class HasMoreMen implements Specification<City> {
    @Override
    public boolean isSatisfiedBy(City city) {
        return city.getMen() >= city.getWomen();
    }
}

И эти городские спецификации объединяются вместе, используя ранее определенные логические спецификации, поэтому наша объединенная спецификация будет выглядеть так:

Specification<City> spec = new Or<City>(
    new And<City> (
        new MinPopulation(60000),
        new MaxPopulation(100000)
    ),
    new Not<City>(new HasMoreMen())
);

После того, как фильтрация будет легкой, нам нужно передать спецификации параметр:

List<City> filtered = cities.stream().filter(city -> spec.isSatisfiedBy(city))
    .collect(Collectors.toList());

for (City eachCity : filtered) {
    System.out.println(eachCity.getName());
}

Критерий объекта из исходного вопроса может быть выражен в виде Спецификации, но мы можем гибко настраивать бизнес-правила любым удобным для нас способом:

Specification<City> criterion = new And<City>(
        new And<City>(new MinPopulation(1000), new MaxPopulation(2000)),
        new And<City>(new MinFemalePopulation(200), new MaxFemalePopulation(300))
);

(здесь MaxFemalePopulation и MinFemalePopulation должны быть определены)

Часто в спецификации используется шаблон Builder, который позволяет создавать спецификации с использованием «плавных интерфейсов»:

Specification<City> criterion = new SpecBuilder::from(new MinPopulation(1000))
    .and(new MaxPopulation(2000))
    .and(new MinFemalePopulation(200))
    .and()
    .build(MaxFemalePopulation(300));

Сортировка может быть выполнена после фильтрации, поскольку сортировки будет меньше:

Collections.sort(filtered);

Чтобы вызвать этот метод, нам нужен наш город для реализации Comparable:

class City implements Comparable<City> {
    private String name;
    private int total;
    private int men;
    private int women;
    public City(String name, int total, int men, int women) {
        this.name = name;
        this.total = total;
        this.men = men;
        this.women = women;
    }
    public String getName() { return name; }
    public int getTotal() { return total; }
    public int getMen() { return men; }
    public int getWomen() { return women; }

    @Override
    public int compareTo(City city) {
        return this.getName().compareTo(city.getName());
    }
}
0 голосов
/ 05 мая 2018

Решение 1: (лямбда)

Самый чистый путь - пройти в лямбду. Я уверен, что к тому времени, когда я закончу печатать, будет 5 таких примеров ...

Решение 2: (отражение и аннотации)

Если ваш критерий (строка) относится к имени атрибута, значит, вы включаете рефлексию. Используя java, вы не можете получить из String имя класса или метода без него - вот что такое отражение.

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

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

Решение 3: (создание сценариев)

Если вы все равно будете принимать штрафы за рефлексию, то есть другая альтернатива - в Java встроен механизм сценариев javascript. Вы можете сообщить движку javascript о своем объекте, а затем просто передать запрос, например:

findCities(cities, "city.men < city.women");

Ваш метод findCities создаст движок javascript, передаст каждый город, чтобы затем сказать ему выполнить эту строку. Он вернет логическое значение, если есть совпадение. Магия. Я только что реализовал это, так что я мог загружать свои запросы из текстового файла, и это было быстрее, чем я думал, и довольно просто реализовать.

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

0 голосов
/ 05 мая 2018

Не усложняйте задачу. Вы можете просто передать свой список и Predicate<T>, представляющие критерии для метода.

пример:

List<City> findAll(List<City> cities, Predicate<City> predicate){
     List<City> accumulator = new ArrayList<>();
     for (City city : cities) 
          if(predicate.test(city))
              accumulator.add(city);
     return accumulator;
}

затем назовите его следующим образом:

findAll(cities, c -> c.getTotal() >= 1000 && c.getTotal() <= 2000);
findAll(cities, c -> c.getWomen() >= 200 && c.getWomen() <= 300);
...
...
...
0 голосов
/ 05 мая 2018

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

public List<City> sortByTotalPopulation(){      
    cities.sort((City a1, City a2) -> a1.getTotal() - a2.getTotal());
    return cities;
}
...