Объедините 2 списка объектов на основе более чем одного аналогичного свойства в Java 8 [duplicate]

Обратите внимание, что есть случаи, когда вы определили свой собственный пользовательский класс и хотите сохранить атрибуты, тогда вы должны использовать copy.copy() или copy.deepcopy(), а не альтернативы, например, в Python 3:

import copy

class MyList(list):
    pass

lst = MyList([1,2,3])

lst.name = 'custom list'

d = {
'original': lst,
'slicecopy' : lst[:],
'lstcopy' : lst.copy(),
'copycopy': copy.copy(lst),
'deepcopy': copy.deepcopy(lst)
}


for k,v in d.items():
    print('lst: {}'.format(k), end=', ')
    try:
        name = v.name
    except AttributeError:
        name = 'NA'
    print('name: {}'.format(name))

Выходы:

lst: original, name: custom list
lst: slicecopy, name: NA
lst: lstcopy, name: NA
lst: copycopy, name: custom list
lst: deepcopy, name: custom list
8
задан Stephane 31 August 2014 в 16:57
поделиться

6 ответов

Вы пытаетесь сделать то, что Java на самом деле не предназначено для.

Если вы в состоянии это сделать, вам лучше добавить атрибут к Object1, который будет списком из Object2, содержащего объекты, связанные с this.

Если вы не можете, у нас еще есть возможность сделать это наивно, иначе вы можете попробовать что-то вроде этого:

HashSet<Integer> hs = new HashSet<Integer>(list2.size());
for(Object2 o : list2) {
    hs.add(o.object1id);
}
//hs contains all the ids of list2
List<Object1> result = new ArrayList<Object1>(); //Or another class implementing List
for(Object1 o : list1) {
    if(hs.contains(o.id))
        result.add(o);
}

Не красиво, поскольку вам нужно сохранить все идентификаторы в HashSet, но так как добавление и доступ к элементам в HashSet - это O (1) (теоретически), алгоритм O (n + m)

Если ваш Object3 класс построен с помощью Object1 и Object2, используйте HasMap вместо HashSet, где ключи являются идентификаторами, а значения - объектом2. Последний цикл for в коде будет выглядеть следующим образом:

Object2 o2 = hs.get(o.id);
if(o2 != null)
    result.add(new Object3(o, o2);

В дополнение к комментарию Óscar López:

Если ваш objectid1 не уникален, вам необходимо адаптировать код следующим образом:

HashMap<Integer, List<Object2>> hm = new HashMap<Integer, List<Object2>>();
for(Object2 o : list2) {
    List<Object2> l = hm.get(o.objectid1);
    if(l != null) {
        l.add(o);
    } else {
        List<Object2> l = new ArrayList<Object2>();
        l.add(o);
        hm.put(o.objectid1, l);
}
//hm is map, where each entry contains the list of Object2 associated with objectid1
List<Object1> result = new ArrayList<Object1>();
for(Object1 o : list1) {
    List<Object2> l = hm.get(o.id);
    //l contains all Object2 with object1id = o.id
    for(Object2 o2 : l)
        result.add(new Object3(o, o2));
}

Все еще в O (n + m), но с большими константами ...

6
ответ дан R2B2 21 August 2018 в 10:06
поделиться
  • 1
    Это не сработает, если несколько объектов list2 имеют одинаковые object1id. – Óscar López 31 August 2014 в 17:22
  • 2
    Я не понимаю вашего комментария, что Java не предназначен для этого. – Tony Ennis 31 August 2014 в 17:42
  • 3
    Вы можете это сделать, но Java не была предназначена для таких целей. Это похоже на выполнение функционального программирования на Java: ничто не запрещает его, но это действительно не лучший и более эффективный способ кодирования. Присоединение строк в таблицах очень хорошо работает с SQL, потому что, как полагали, язык SQL выполняет такие вычисления. В то время как Java не является: вместо применения методов и структуры SQL к Java, вы должны попытаться адаптировать свою проблему и свой образ мышления к используемому вами языку. – R2B2 31 August 2014 в 18:23

Я считаю, что решение O(n*m) неизбежно, , если не создана более сложная инфраструктура структуры данных - эффективные объединения в базе данных реализованы с использованием индексов, хэшей и т. д. Также следует иметь в виду, что правильная реализация должна учитывать случай, когда более одного объекта в list2 имеет тот же object1id - мой код работает в этом случае, но все решения, которые просто добавляют obj2.object1id к Set или как клавиши в Map, не удастся.

Но стоит ли это сложность реализации? если входные списки малы, O(n*m) решение будет работать нормально. Вот мое предложение, используя старые добрые вложенные циклы:

List<Object3> list3 = new ArrayList<>();
for (Object1 obj1 : list1) {
    boolean found = false;
    for (Object2 obj2 : list2) {
        if (obj1.id.equals(obj2.object1id)) {
            list3.add(new Object3(obj1, obj2));
            found = true;
        }
    }
    if (!found)
        list3.add(new Object3(obj1, null));
}

Для работы выше, я использую выходной объект, который выглядит так:

public class Object3 {
    private Object1 obj1;
    private Object2 obj2;
    public Object3(Object1 obj1, Object2 obj2) {
        this.obj1 = obj1;
        this.obj2 = obj2;
    }
}
1
ответ дан Óscar López 21 August 2018 в 10:06
поделиться
  • 1
    Это работает, и это было то, о чем я думал. Но мне было интересно, можно ли сделать несколько оптимизаций, с сортировкой или что-то в этом роде. Я не могу поверить, что левое соединение в SQL имеет сложность O (n * m) ... – Stephane 31 August 2014 в 17:12
  • 2
    @ user3019499, как я сказал выше: эффективное объединение не является такой тривиальной задачей, взгляните на некоторые возможные алгоритмы . Насколько сложна реализация? если списки не слишком велики, приведенный выше ответ будет выполнен просто отлично. – Óscar López 31 August 2014 в 17:14
  • 3
    время для запуска некоторых тестов :) – Stephane 31 August 2014 в 17:22
  • 4
    @ user3019499 также, мой ответ (пока), который работает только в том случае, если в list2 имеется более одного объекта с таким же object1id, очень вероятным сценарием в реальной базе данных. – Óscar López 31 August 2014 в 17:25
  • 5
    @ user3019499 Это будет O (n * m), если вы пытаетесь создать весь набор n строк, и вам нужно выполнить линейный поиск m, чтобы найти все совпадения. Предотвращение этого - это то, почему базы данных индексы (или хэш Java) были созданы. Кроме того, ОП должен решить, что такое достаточно быстро. Не все должно быть оптимально быстрым. – Tony Ennis 31 August 2014 в 17:47

Создайте индекс в списке. Сканировать список и заполнить индекс:

HashMap<Integer, Object2> index=HashMap<Integer, Object2>();
for (Object2 obj2: list2) {
   index.put(obj2.object1id, obj2);
}

Затем сканируйте список и выполните соединение:

for (Object1 obj1: list1) {
   Object2 obj2=index.get(obj1.id); // may be null
   Object3 obj3=new Object3(obj1, obj2);
}
2
ответ дан Alexei Kaigorodov 21 August 2018 в 10:06
поделиться
  • 1
    Это не сработает, если несколько объектов list2 имеют одинаковые object1id. – Óscar López 31 August 2014 в 17:21

Если вы используете Java 8, вы можете использовать потоки . Это может выглядеть примерно так (предполагая, что id является идентификатором Object1 для поиска):

List<Object3> newList = obj2List.stream().filter(x -> x.object1id == id).map(x -> obj2To3(x)).collect(Collectors.toList());

Приведенный случай довольно расплывчатый, поэтому трудно дать более подробный ответ.

1
ответ дан Dogsworth 21 August 2018 в 10:06
поделиться

При условии, что они реализуют некоторый общий интерфейс (это облегчит задачу, особенно при кастинге), тогда это относительно просто.

Он все еще O (nm), так как вам нужно пройти оба длины списка, чтобы найти элементы для добавления.

public interface JoinInterface {
    int getId();
    int getObject1Id(); // likely baggage here
}


public static List<? extends JoinableEntity> leftJoin(List<? extends JoinableEntity> left,
                                               List<? extends JoinableEntity> right) {
    List<JoinableEntity> result = new ArrayList<>();

    result.addAll(left);
    for(JoinableEntity aLeft : left) {
        for(JoinableEntity aRight : right) {
            if(aLeft.getId() == aRight.getObject1Id()) {
                result.add(aRight);
                break;
            }
        }
    }

    return result;
}
0
ответ дан Makoto 21 August 2018 в 10:06
поделиться

Хорошим решением может быть преобразование списка объектов2 в карту. Затем перейдите в список Object1 и получите Object2 из Map, в конечном итоге создайте результат объединения и добавления в Object3 List.

1
ответ дан Rami Stefanidis 21 August 2018 в 10:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: