Объединить список из двух списков в объекте Java в потоке

#java

#java

Вопрос:

У меня есть два списка для двух классов, где id и месяц являются общими

 public class NamePropeties{
    private String id;
    private Integer name;
    private Integer months;
}


public class NameEntries {
    private String id;
    private Integer retailId;
    private Integer months;
}
  

Список NamePropetiesList = новый ArrayList<>();
Список NameEntries = новый ArrayList<>();

Теперь я хочу JOIN создать два списка (например Sql , JOIN ON month and id исходя из двух результатов) и вернуть данные в новый список, где месяц и идентификатор одинаковы в заданных двух списках.

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

я пытался сделать это многими способами, но есть ли какой-либо способ потока?

Комментарии:

1. У вас есть класс, который содержит результат? Потому что в противном случае это спорно.

2. да, у меня есть … оба класса содержат результаты в списке

3. Улучшение полигнома заключается в том, что при объединении sql вы получите набор строк, столбцы которых являются столбцами из двух таблиц. Эквивалентом Java будет список объектов, поля которых являются полями из двух классов. У вас есть такой класс? В идеале он должен иметь конструктор, который принимает экземпляр каждого класса

4. @fps это может быть случайным … мы не можем сказать

5. @fps прелесть этого вопроса заключается в размере списка любого из тех, кого мы не знаем, иначе я бы просто повторил больший список… но опять же, мы можем это сделать .. но это будет долгое и грязное решение

Ответ №1:

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

Лучше создать карту из списка с большим количеством соединенных элементов. Почему? Поскольку поиск по карте выполняется O(1) независимо от размера карты. Итак, если вы создаете карту из списка с большим количеством соединенных элементов, то при повторении второго списка (который меньше) вы будете перебирать меньшее количество элементов.

Помещаем все это в код:

 public static <B, S, J, R> List<R> join(
    List<B> bigger, 
    List<S> smaller,
    Function<B, J> biggerKeyExtractor,
    Function<S, J> smallerKeyExtractor,
    BiFunction<B, S, R> joiner) {

    Map<J, List<B>> map = new LinkedHashMap<>();
    bigger.forEach(b -> 
        map.computeIfAbsent(
                biggerKeyExtractor.apply(b),
                k -> new ArrayList<>())
            .add(b));

    List<R> result = new ArrayList<>();
    smaller.forEach(s -> {
        J key = smallerKeyExtractor.apply(s);
        List<B> bs = map.get(key);
        if (bs != null) {
            bs.forEach(b -> {
                R r = joiner.apply(b, s);
                result.add(r);
            }
        }
    });

    return resu<
}
  

Это универсальный метод, который объединяет большее List<B> и меньшее List<S> с помощью J ключей объединения (в вашем случае, поскольку ключ объединения является составным из String и Integer типов, J будет List<Object> ). Он обрабатывает дубликаты и возвращает результат List<R> . Метод получает оба списка, функции, которые будут извлекать ключи соединения из каждого списка, и объединяющую функцию, которая создаст новые R элементы результата из соединенных B S элементов и .

Обратите внимание, что карта на самом деле является multimap. Это связано с тем, что в соответствии biggerKeyExtractor с функцией join могут быть дубликаты. Мы используем Map.computeIfAbsent для создания этого multimap.

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

 public class JoinedResult {

    private final NameProperties properties;
    private final NameEntries entries;

    public JoinedResult(NameProperties properties, NameEntries entries) {
        this.properties = properties;
        this.entries = entries;
    }

    // TODO getters
}
  

Или, если вы используете Java 14 , вы можете просто использовать запись:

 public record JoinedResult(NameProperties properties, NameEntries entries) { }
  

Или, на самом деле, подойдет любой Pair класс оттуда, или вы могли бы даже использовать Map.Entry .

При наличии результирующего класса (или записи) вы должны вызвать join метод следующим образом:

 long propertiesSize = namePropertiesList.stream()
    .map(p -> Arrays.asList(p.getMonths(), p.getId()))
    .distinct()
    .count();
long entriesSize = nameEntriesList.steram()
    .map(e -> Arrays.asList(e.getMonths(), e.getId()))
    .distinct()
    .count();

List<JoinedResult> result = propertiesSize > entriesSize ? 
    join(namePropertiesList, 
         nameEntriesList, 
         p -> Arrays.asList(p.getMonths(), p.getId()),
         e -> Arrays.asList(e.getMonths(), e.getId()),
         JoinedResult::new)                                    :
    join(nameEntriesList, 
         namePropertiesList, 
         e -> Arrays.asList(e.getMonths(), e.getId()),
         p -> Arrays.asList(p.getMonths(), p.getId()),
         (e, p) -> new JoinedResult(p, e));
  

Ключ в том, чтобы использовать дженерики и вызывать join метод с правильными аргументами (они переворачиваются в соответствии со сравнением размеров ключей соединения).

Примечание 1: мы можем использовать List<Object> в качестве ключа карты, потому что все списки Java реализуются equals и hashCode последовательно (таким образом, их можно безопасно использовать в качестве ключей карты)

Примечание 2: если вы используете Java9 , вам следует использовать List.of вместо Arrays.asList

Примечание 3: я не проверял ни null недопустимые, ни недопустимые аргументы

Примечание 4: есть возможности для улучшений, т.Е. Функции извлечения ключей могут быть сохранены в памяти, ключи соединения могут использоваться повторно, а не вычисляться более одного раза, а multimap может иметь Object значения для отдельных элементов и списки для дубликатов и т. Д

Ответ №2:

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

Класс держателя результата

 public class Tuple<A, B> {
    public final A a;
    public final B b;

    public Tuple(A a, B b) {
        this.a = a;
        this.b = b;
    }
}
  

Объединение с помощью предиката:

 public static <A, B> List<Tuple<A, B>> joinOn(
    List<A> l1,
    List<B> l2,
    Predicate<Tuple<A, B>> predicate) {
    return l1.stream()
        .flatMap(a -> l2.stream().map(b -> new Tuple<>(a, b)))
        .filter(predicate)
        .collect(Collectors.toList());
}
  

Назовите это так:

 List<Tuple<NamePropeties, NameEntries>> joined = joinOn(
    properties,
    names,
    t -> Objects.equals(t.a.id, t.b.id) amp;amp; Objects.equals(t.a.months, t.b.months)
);