12

I'm working on some piece of code, which is quite hot, and I need to add elements of one LinkedList (l1) to another LinkedList (l2).

It's not possible to use addAll(Collection) method as it uses Iterator to iterate over the entire Collection.

It seems to me that it should be possible to just set the last Node of l1 to point to the first Node of l2. But I couldn't find any suitable method for this? Do I need my own LinkedList implementation to get that?

Draken
  • 3,134
  • 13
  • 34
  • 54
user3663882
  • 6,957
  • 10
  • 51
  • 92
  • 5
    It appears that the standard `LinkedList` implementation does not support this functionality, q.v. [this SO post](http://stackoverflow.com/questions/4726160/java-beginner-how-do-i-link-one-linked-list-to-another). – Tim Biegeleisen Jul 11 '16 at 09:57
  • 2
    Would it make sense to just use a list of lists and iterate over those later? – Silverclaw Jul 11 '16 at 10:01
  • 2
    I think `google-collections Iterables.concat` is what you're looking for: http://stackoverflow.com/a/2494530/3850595 – Jordi Castilla Jul 11 '16 at 10:02
  • 2
    You should find that appending an ArrayList to another ArrayList to be faster as there is less pointer chasing. – Peter Lawrey Jul 11 '16 at 10:05
  • @PeterLawrey Really? Even if the lists are pretty huge? With LinkedList we need to update just one pointer? Why ArrayLists are faster? – user3663882 Jul 11 '16 at 10:12
  • @Silverclaw Make sense, probably that's the solution I've been looking for. – user3663882 Jul 11 '16 at 10:13
  • 2
    Do you need the result to be a `LinkedList` with some of its specific functionality? Or would it be sufficient if it implemented the `List` interface? Are you sure that you won't do indexed access on the resulting list, but **only** iteration via `Iterator`? (It might be possible to trivially wrap two lists into a new class extending `AbstractList` and offering a simple "concatenating `Iterator`") – Marco13 Jul 11 '16 at 10:58
  • What about using BigList https://dzone.com/articles/biglist-scalable-high ? – J Fabian Meier Jul 11 '16 at 10:59
  • @Marco13 Very good point! No, I don't. I need to concatenate collection in constant tilme in order to iterate through the resulting collection. – user3663882 Jul 11 '16 at 11:01
  • 1
    @user3663882 LinkedList is a doubly linked list and there is no supported way to move all the nodes from one list to another. As ArrayList only have references which are typically 32 bit, you can have 16 references per cache line and copying these is pretty efficient. You can have a `List>` if you append large lists often. – Peter Lawrey Jul 11 '16 at 11:08
  • When you change one of the original lists, should that affect the merged list as well, and vice versa? – tobias_k Jul 11 '16 at 11:23

3 Answers3

5

According to the comments, the goal is to create something like a "view" on the concatenated list - meaning that the data should not be copied. Instead, the given lists should "appear" like a single list.

One way of how this could be implemented is to extend AbstractList. The implementation of get(int) and size() are fairly trivial. The crucial point is to create an Iterator for the concatenated list. The following is a very simple sketch of how this could be accomplished (but see the notes below)

import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class MergedListTest
{
    public static void main(String[] args)
    {
        testBasic();
        testEmptyA();
        testEmptyB();
    }

    private static void testBasic()
    {
        List<Integer> list0 = Arrays.asList(0,1,2);
        List<Integer> list1 = Arrays.asList(3,4,5);
        List<Integer> expected = Arrays.asList(0,1,2,3,4,5);
        List<Integer> actual = new MergedList<Integer>(list0, list1);
        System.out.println(actual.equals(expected));
    }

    private static void testEmptyA()
    {
        List<Integer> list0 = Collections.emptyList();
        List<Integer> list1 = Arrays.asList(3,4,5);
        List<Integer> expected = Arrays.asList(3,4,5);
        List<Integer> actual = new MergedList<Integer>(list0, list1);
        System.out.println(actual.equals(expected));
    }

    private static void testEmptyB()
    {
        List<Integer> list0 = Arrays.asList(0,1,2);
        List<Integer> list1 = Collections.emptyList();
        List<Integer> expected = Arrays.asList(0,1,2);
        List<Integer> actual = new MergedList<Integer>(list0, list1);
        System.out.println(actual.equals(expected));
    }

}


class MergedList<T> extends AbstractList<T>
{
    private final List<T> list0;
    private final List<T> list1;

    MergedList(List<T> list0, List<T> list1)
    {
        this.list0 = list0;
        this.list1 = list1;
    }

    @Override
    public T get(int index)
    {
        if (index < list0.size())
        {
            return list0.get(index);
        }
        return list1.get(index - list0.size());
    }

    @Override
    public Iterator<T> iterator()
    {
        return new Iterator<T>() 
        {
            private Iterator<T> current = list0.iterator();
            private boolean first = true;

            @Override
            public boolean hasNext() 
            {
                return current != null && current.hasNext();
            }

            @Override
            public T next() 
            {
                T result = current.next();
                if (!current.hasNext())
                {
                    if (first)
                    {
                        current = list1.iterator();
                    }
                    else
                    {
                        current = null;
                    }
                }
                return result;
            }
        };
    }

    @Override
    public int size()
    {
        return list0.size() + list1.size();
    }
}

Conceptually, it would make more sense to inherit from AbstractSequentialList: The AbstractList offers stub implementations, e.g. of iterator(), that eventually delegate to get(int), whereas AbstractSequentialList offers the "opposite" stub implementations, e.g. of get(int) that eventually delegate to iterator(). However, this requires a ListIterator<T> implementation, which is a bit more fiddly than the trivial sketch above.

Also note that I assumed that the resulting view should be unmodifiable - but this should be in line with the given description.

And finally, note that there are (of course) already implementations for this and similar tasks, and these implementations may be far more sophisticated than the one sketched above. For example, Google Guava offers different Iterators#concat methods that allow you to concatenate multiple iterators. So if you are already using Guava, the implementation of the iterator() method above could boil down to

@Override
public Iterator<T> iterator()
{
    return Iterators.concat(list0.iterator(), list1.iterator());
}
Marco13
  • 53,703
  • 9
  • 80
  • 159
2

It seems like required feature is not supported by the standard library. However, you can use reflection to achieve constant time when merging linked lists. I haven't tested the following code properly, just to show how it could be achieved:

public class ListConcatenation {

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
    LinkedList<String> list1 = new LinkedList<>();
    list1.add("a");
    list1.add("b");
    list1.add("c");

    LinkedList<String> list2 = new LinkedList<>();
    list2.add("d");
    list2.add("e");
    list2.add("f");

    System.out.println(merge(list1, list2));
}

public static List<String> merge(List<String> list1, List<String> list2) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
    Field first = getDeclaredField(LinkedList.class, "first");
    Field last = getDeclaredField(LinkedList.class, "last");
    Field size = getDeclaredField(LinkedList.class, "size");

    Class<?> nodeClass = Class.forName("java.util.LinkedList$Node");
    Field next = nodeClass.getDeclaredField("next");
    next.setAccessible(true);

    Object list1Last = last.get(list1);
    Object list2First = first.get(list2);
    Object list2Last = last.get(list2);

    // link the last element of list1 with the first element of list2
    next.set(list1Last, list2First);
    // set last element of list1 equal to the last element of list2
    last.set(list1, list2Last);
    // update list1 size
    size.set(list1, list1.size() + list2.size());

    return list1;
}

private static Field getDeclaredField(Class<?> clazz, String name) throws NoSuchFieldException {
    Field field = clazz.getDeclaredField(name);
    field.setAccessible(true);
    return field;
}

}

Michael Potter
  • 530
  • 4
  • 11
  • 9
    This is really a hacky solution, and since it's messing with the implementation details of `java.util.LinkedList` it is not guaranteed to work on future (or past) versions of Java. This is not a solution that is to be recommended. – Jesper Jul 11 '16 at 10:52
2

No. You cannot add two java.util.LinkedLists to output another one in constant time O(1).

Conceptually, you can indeed add two Linked List data structures together in constant time O(1) but they'll need to be implemented in the right way. As you mention, the implementation should combine the two lists by joining the node of the last to the node of the first and does not iterate over either list.

isak gilbert
  • 2,541
  • 1
  • 14
  • 11