Найти объект в списке, не повторяя его

У меня есть класс с 3 свойствами:

private static class OrderListItem {
    public int price;
    public int size;
    public Type type;
}

Элементы этого класса добавляются в список. List<OrderListItem> orders_book = new ArrayList<>();

Я хочу найти объект в списке с указанной ценой. Но размер и тип не имеют значения. Могу ли я сделать что-то вроде этого:

int index = orders_book.indexOf(new OrderListItem(price, **any size**, **any type**)); ?

Или я просто должен сделать "петлевой" поиск?

Вы можете использовать stream() или forEach() в ArrayList, если вы не хотите использовать буквальный цикл for, хотя внутри они делают в основном одно и то же.

markspace 16.05.2022 16:13

Что ж, что-нибудь придется зацикливаться, будь то в вашем коде или Stream и т. д.

Jon Skeet 16.05.2022 16:13

Вы можете использовать Stream и filter и findAny

Stefan Warminski 16.05.2022 16:13

Метод indexOf использует метод objects equals для определения возвращаемого результата, как указано в его документации: docs.oracle.com/javase/8/docs/api/java/util/… Итак, можете ли вы использовать indexOf, зависит от того, как ваш класс реализовал метод equals.

OH GOD SPIDERS 16.05.2022 16:14

Это отсортированный массив или обычный массив?

Sadhvik Chirunomula 16.05.2022 16:35

Несвязанный: узнайте о соглашениях об именах Java. Например, хорошим названием будет orderedBook. Используйте только "_" для SOME_CONSTANT. Все остальные имена — camelCase или UpperCamelCase, в зависимости от контекста.

GhostCat 16.05.2022 16:36

Что, если несколько заказов имеют некоторые или все эти характеристики? Не лучше ли вернуть список совпадений.

WJS 16.05.2022 16:36

Даже если бы вы могли использовать indexOf, это все равно зацикливается на списке. Вы ищете только по цене, а не по размеру или типу? Вы можете сохранить список в порядке возрастания цены и выполнить бинарный поиск. Или поддерживайте карту от цены до (списка) заказов.

David Conrad 16.05.2022 17:13
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
8
84
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Если вам нужно получить экземпляр с желаемой ценой, вы можете использовать поток сбора с операциями filter и findAny.

int myPrice = /* any value you want */

//This will retrieve an instance with the disired price or null if none is found
OrderListItem item = orders_book.stream()
        .filter(i -> i.getPrice() == myPrice)
        .findFirst()
        .orElse(null);

В качестве альтернативы, если вы хотите получить индекс объекта с нужной ценой, у вас есть два варианта:

  1. Переопределите метод equals вашего класса OrderListItem, чтобы он равнялся двум элементам OrderListItem по их цене, поскольку документация метода indexOf извлекает элемент по его методу equals. Тем не менее, я бы не стал склоняться к этому решению, так как, на мой взгляд, единой цены недостаточно, чтобы равняться двум позициям заказа, но вы определенно лучше меня знаете, какую проблему вы моделируете. Итак, я оставляю выбор за вами. Кроме того, если вы переопределяете метод equals, вы также должны переопределить метод hashCode, как указано в общем контракт хэш-кода.

https://docs.oracle.com/javase/8/docs/api/java/util/List.html#indexOf-java.lang.Object-

Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element. More formally, returns the lowest index i such that (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no such index.

class OrderListItem {
    public int price;
    public int size;
    public Type type;

    /* ... your implementation ...*/

    @Override
    public int hashCode() {
        return Objects.hash(price);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (this == obj) return true;
        if (getClass() != obj.getClass()) return false;
        OrderListItem other = (OrderListItem) obj;
        return Objects.equals(price, other.price);
    }
}
  1. Не переопределяя метод equals, а извлекая точный экземпляр с помощью потока (как в первом фрагменте), а затем используя тот же экземпляр в качестве параметра для вызова indexOf.
int myPrice = /* any value you want */

//This will retrieve an instance with the disired price or null if none is found
OrderListItem item = orders_book.stream()
        .filter(i -> i.getPrice() == myPrice)
        .findFirst()
        .orElse(null);

//Retrieving the index
int index = orders_book.indexOf(item);

Спасибо за ответ! Это то, что я искал. Будет ли это решение иметь какой-либо выигрыш в скорости по сравнению с простой итерацией цикла?

Laughing_Man 16.05.2022 16:21

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

markspace 16.05.2022 16:21

@markspace спасибо. Мне нужно оптимизировать мой код, но, похоже, я смотрю не в ту сторону.

Laughing_Man 16.05.2022 16:23

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

Dan 16.05.2022 16:25

Вам, вероятно, не нужно оптимизировать поток выше. Это никогда не проявится в реальном тесте производительности. Напишите код, измерьте производительность достаточно большого куска (не микротест), потом решит, что оптимизировать.

markspace 16.05.2022 16:26

За исключением поиска set и map, которые являются hash based, любой метод, который вы можете найти в API, в конечном итоге будет использовать какой-либо цикл или итератор (даже потоки). Таким образом, в некотором смысле вы добавляете накладные расходы при вызове или вызовах метода. В любом случае, не беспокойтесь об этом и просто делайте то, что имеет смысл и что проще всего поддерживать. Не беспокойтесь об оптимизации (если это заметная проблема) до тех пор, пока ваш код не будет написан.

WJS 16.05.2022 16:31

Если вы собираетесь определить equals так, чтобы это зависело только от price, вы ДОЛЖЕН также сделаете то же самое для hashCode.

David Conrad 16.05.2022 17:14

@DavidConrad хорошее замечание, забыл упомянуть об этом. Я добавлю! Спасибо, что позволили мне заметить!

Dan 16.05.2022 17:15

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

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

  • здесь используется IntStream для потоковой передачи всех возможных индексов.
  • он отображает индекс и объект по этому индексу в Map.entry
  • затем он фильтрует, используя value из entry по желаемому атрибуту.
  • и возвращает первое обнаруженное значение или запись с -1 и нулевым значением. (AbstractMap.SimpleEntry был использован здесь, чтобы разрешить нулевое значение)
int desiredPrice = 3;
Entry<Integer, OrderListItem> result = IntStream
        .range(0, orderList.size())
        .mapToObj(i -> Map.entry(i, orderList.get(i)))
        .filter(e -> e.getValue().getPrice() == desiredPrice)
        .findFirst().orElse(new AbstractMap.SimpleEntry<>(-1,null));

Для облегчения демонстрации вместо определения класса используется следующее record. Для этого можно напрямую заменить класс. Также предоставляется перечисление типов.

enum Type {
    FICTION, TECHNICAL, BIOGRAPHY
}

private record OrderListItem(int getPrice, int getSize,
        Type getType) {
    
    @Override
    public String toString() {
        return String.format("[%s, %s, %s]", getPrice, getSize,
                getType);
    }
}

Теперь определим некоторые предикаты. Каждый берет orderListItem объект и Type. Метод сравнения (равно или ==) зависит от типа атрибута. Этот предикат и требуемое значение атрибута будут переданы методу, как показано ниже. Даже если вас интересует только price, остальные добавить было достаточно просто.

static BiPredicate<OrderListItem, Integer> CHECK_PRICE =
        (order, val) -> order.getPrice() == val;

static BiPredicate<OrderListItem, Type> CHECK_TYPE =
        (order, val) -> order.getType().equals(val);

static BiPredicate<OrderListItem, Integer> CHECK_SIZE =
            (order, val) -> order.getSize() == val;

Некоторые данные

List<OrderListItem> orderList =
        ArrayList<>(List.of(new OrderListItem(3, 20, Type.FICTION),
                new OrderListItem(4, 20, Type.TECHNICAL),
                new OrderListItem(5, 20, Type.TECHNICAL),
                new OrderListItem(3, 30, Type.FICTION),
                new OrderListItem(4, 30, Type.FICTION),
                new OrderListItem(5, 30, Type.TECHNICAL),
                new OrderListItem(3, 40, Type.FICTION),
                new OrderListItem(4, 40, Type.BIOGRAPHY),
                new OrderListItem(5, 40, Type.FICTION),
                new OrderListItem(3, 50, Type.FICTION),
                new OrderListItem(4, 50, Type.BIOGRAPHY),
                new OrderListItem(5, 50, Type.BIOGRAPHY)));

Демо

List<OrderListItem> matches =
        retrieve(orderList, CHECK_SIZE, 20, 2);
    
matches.forEach(System.out::println);

отпечатки

[3, 20, FICTION]
[4, 20, TECHNICAL]

Метод принимает следующие аргументы.

  • список заказов
  • предикат для сравнения
  • значение атрибута для сравнения
  • желаемое количество возвращаемых успешных совпадений (отрицательное значение приведет к возврату всех совпадений).

Метод работает путем простой итерации по заказам, применяя предикат к заказу и атрибуту. Если совпадение найдено, оно добавляется в список возврата, и счетчик уменьшается. Когда счетчик достигнет 0 или будет проверен весь список, список будет возвращен.

public static <T> List<OrderListItem> retrieve(
        List<OrderListItem> orders,
        BiPredicate<OrderListItem, T> pred, T attribute,
        int count) {
    List<OrderListItem> matches = new ArrayList<>();
    for (OrderListItem order : orders) {
        if (pred.test(order, attribute)) {
            matches.add(order);
            if (--count == 0) {
                break;
            }
        }
    }
    return matches;
}

Спектакль

Не лучший тест по сравнению с JMH, но грубая идея.

  • Сначала я добавил список данных к самому себе 20 раз, чтобы создать большой список.
  • Я повторил вышеуказанный поиск для всех записей без распечатки.
for (int i = 0; i < 20; i++) {
    orderList.addAll(orderList);
}
System.out.printf("%,d total objects%n",orderList.size());
    
long time = System.nanoTime();
List<OrderListItem> matches =
        retrieve(orderList, CHECK_SIZE, 20, -1);
System.out.println("Execution time ~ "+(System.nanoTime()-time)/1e9 + " sec");

отпечатки

12,582,912 total objects
Execution time ~ 0.632698253 sec

Поскольку совпадений для всего списка не найдено, результат был Execution time ~ 0.033943752 sec.

Для сопоставления всего списка результат был Execution time ~ 0.939900075 sec.

Разница во времени в основном связана (imo) с тем, что резервный массив необходимо обновлять по мере увеличения емкости. Это требует нескольких повторных копий массива. Установив начальную емкость возвращаемого списка на orders.size(), вы улучшите результат сопоставления и возврата всех элементов.

Для сопоставления всего списка с скорректированной емкостью результат был Execution time ~ 0.19100866 sec

Я использую Windows на 4-ядерном процессоре Intel I7.

Я понятия не имею, как что-либо из вышеперечисленного относится к вашим требованиям, но это для вашего назидания.

Другие вопросы по теме