4

I'm looking for a opensource library that has an implementation of a composite list. I need a list that reads its values from some other lists and can be constructed something like this:

List list1 = new ArrayList();
list1.add("0");
List list2 = new LinkedList();
list2.add("1");
list3.add("2");
List list3 = new CompositeList(list1, list2...)
then:
assertEquals("0", list3.get(0));
assertEquals("1", list3.get(1));
assertEquals("2", list3.get(2));
The idea is that I don't need to copy over everything from the source lists.

A quick google didn't find anything, I didn't see it in Guava or commons collections (I may have overlooked it). I don't really have the time to implement it properly right now.

skaffman
  • 398,947
  • 96
  • 818
  • 769
AmanicA
  • 4,659
  • 1
  • 34
  • 49
  • So are you looking for a live view? What behaviour do you expect if you go back and add something to list1? – Mark Peters Nov 18 '10 at 15:23
  • Could explain why you would want this? You are better off creating a new list which has all the desired elements (please don't say this is for performance reasons :P ) – Peter Lawrey Nov 18 '10 at 15:27
  • 2
    Do you really need the result to be a List? If, say, you just want to be able to iterate through the elements of some Lists in order, you can use Iterables.concat in Guava. – ColinD Nov 18 '10 at 17:41
  • @CollinD that could be useful in certain circumstances. – AmanicA Nov 18 '10 at 21:26
  • @PeterLawrey What's wrong with performance reasons? – Mehdi Charife Jul 30 '23 at 22:49
  • @MehdiCharife In this example, the performance is probably worse, not better. For more extreme examples with multiple large ArrayLists, it might be better. If you want it to perform, don't use LinkedlIst.get(int) on large lists. Most of the time, clarity is more important than performance unless you have profiled your code and it tells you it could be a problem. – Peter Lawrey Jul 31 '23 at 09:48
  • @PeterLawrey For the performance it could depend on your particular use case. I was thinking of a problem where you have to retrieve an element from a certain list, and in which the logic of retrieval could be optimized depending on the nature of the components of the list. So the idea would be to partition your elements in different lists and apply a customized algorithm to each list and then apply the normal "naive" algorithm to the list of retrieved elements from each list. – Mehdi Charife Aug 02 '23 at 10:54
  • @PeterLawrey Otherwise, my understanding was that design patterns help make the code cleaner, so I was surprised to hear you say that a composite collection would make the code harder to understand. Could you please elaborate on thIs point? – Mehdi Charife Aug 02 '23 at 10:54
  • @MehdiCharife either way you have a plan List, however, if something odd happens, it could take someone by surprise as to what is happening and they could spend a lot of time understanding it only to find it wasn't the problem in the first place. – Peter Lawrey Aug 09 '23 at 08:03

5 Answers5

11

I was looking inside Guava for something similar this morning, and finally stumbled upon Iterables.concat().

You asked specifically for a List view, so this might not exactly solve your problem, but this is one option to keep in mind.

I also thought I needed a Collection / List at first, but I later realized it wasn't mandatory to solve my problem: I was mainly looking for something to concat multiple iterables (obtained by various Guava filtering/transformations), before filtering/transforming the result, to finally dump it into an ImmutableList. Returning the Iterable view is also an option, if you only need to iterate on the result.

PS (a few years later): those transformations / concatenations can now be done using Guava's FluentIterable or Java 8 streams too.

Etienne Neveu
  • 12,604
  • 9
  • 36
  • 59
8

CompositeCollection from Commons Collections seems to do what you need, even if it's not generified.

AmanicA
  • 4,659
  • 1
  • 34
  • 49
Valentin Rocher
  • 11,667
  • 45
  • 59
  • I'm actually somewhat horrified that any library has this. It would reorder the elements it returns any time any of the underlying collections changes. Unless, of course, it copies them, which the OP expressly doesn't want. – Powerlord Nov 18 '10 at 15:26
  • 5
    @R. Bemrose: There are all kinds of instances of decorator-type classes requiring you to *only* use the decorator from then on for it to work properly. If you obtain an `unmodifiableList` from `Collections`, you can still go back and change the list you passed in, thus changing the unmodifiable one as well. Guarantees can't be made if the OP doesn't follow the conditions of use. Now, that definitely should be *documented* in the Javadoc... – Mark Peters Nov 18 '10 at 15:27
  • @AmanicA : they are in the way of generifying that, for the new versions, but it's still non-generified to be usable in 1.4 – Valentin Rocher Nov 19 '10 at 08:35
  • Seems like the answer at the moment is that it does not exist, so this is the closest alternative. – AmanicA Dec 02 '10 at 10:33
  • BTW OP's sample code can't be achieved with `CompositeCollection` since it has no `get` method ... – Unda Jun 07 '16 at 14:24
1

You can use org.apache.commons.collections15.collection.CompositeCollection, and it's generic.

See http://search.maven.org/#artifactdetails%7Cnet.sourceforge.collections%7Ccollections-generic%7C4.01%7Cjar

rodche
  • 76
  • 1
  • 5
  • A link or reference would have been nice since the package name makes it look like it's an Apache project. – madth3 Oct 26 '12 at 22:44
  • It's here: http://search.maven.org/#artifactdetails%7Cnet.sourceforge.collections%7Ccollections-generic%7C4.01%7Cjar – rodche Oct 01 '13 at 03:49
1

We can build one. I use Guava for iterator() implementation but it wouldn't be hard to roll your own.

/**
 * A list composed of zero to many child lists.  Additions occur in the first
 * acceptable list: if an insertion is attempted at an index that lies on a break
 * between lists, the insert occurs in the first list. Modifications are undefined
 * if list of lists has no elements.
 * @param <T> Type of element stored in list.
 */
public class CompositeList<T> extends AbstractList<T> {
// member variables ---------------------------------------------------------------
    private Collection<List<T>> mLists;

// constructors -------------------------------------------------------------------
    public CompositeList(Collection<List<T>> pLists) {mLists = pLists;}

// methods ------------------------------------------------------------------------
    /** Sum of sizes of component lists. */
    public int size() {return mLists.stream().mapToInt(Collection::size).sum();}

    @Override public T get(int pIdx) {
        final Map.Entry<List<T>,Integer> m = findIndex(pIdx);
        return m.getKey().get(m.getValue());
    }

    /**
     * If add could occur at end of one list or beginning of the next, the former
     * behavior is guaranteed.
     */
    @Override public void add(int pIdx, T pElement) {
        if (pIdx == 0) {
            mLists.iterator().next().add(0, pElement);
        } else {
            // find prior object
            final Map.Entry<List<T>,Integer> m = findIndex(pIdx - 1);
            m.getKey().add(m.getValue() + 1, pElement);
        }
    }

    @Override public T remove(int pIdx) {
        final Map.Entry<List<T>,Integer> m = findIndex(pIdx);

        // don't auto-box because remove(Object) and remove(int) can be confused
        return m.getKey().remove(m.getValue().intValue());
    }

    @Override public T set(int pIdx, T pElement) {
        final Map.Entry<List<T>,Integer> m = findIndex(pIdx);
        return m.getKey().set(m.getValue(), pElement);
    }

    /** More efficient than superclass implementation. */
    @Override public Iterator<T> iterator() {
        return Iterators.concat(
            Collections2.transform(mLists, Collection::iterator).iterator()
        );
    }

    @Override public void clear() {mLists.forEach(Collection::clear);}

    /**
     * Identify list and index that composite index refers to. For
     * [A], [], [], [B, C]; composite index 1 would return the fourth list
     * mapped to the number 0.
     */
    private Map.Entry<List<T>,Integer> findIndex(int pCompositeIdx) {
        // composite index of list's starting point
        int listStart = 0;
        for (final List<T> list : mLists) {
            if (listStart + list.size() > pCompositeIdx) {
                return new AbstractMap.SimpleImmutableEntry<>(
                    list, pCompositeIdx - listStart
                );
            }
            listStart += list.size();
        }
        throw new IndexOutOfBoundsException(pCompositeIdx + " >= " + size());
    }
}
Hollis Waite
  • 1,079
  • 1
  • 20
  • 31
0

Check javakarta collections, CompositeCollection:

http://commons.apache.org/collections/apidocs/index.html

AlexR
  • 114,158
  • 16
  • 130
  • 208