#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)
);