4

I have a task where I need to write a method which allows me to mix two generic lists and return a new mixed list.

The mixed list should have the first element of l1 at the first position of the new mixed list, the first element of the l2 should be in second position and then the second element of l1 should be in third position and so on.

If one list is longer than the other the rest should just be added in original order.

An example is: l1 = (1,2,3) and l2=(9,8) --> mixed list = (1,9,2,8,3)

public <S, T> List<T> listeMischen(List<S> l1, List<T> l2) { List<T> newlist = new ArrayList<T>(); for(int i = 0; i < l1.size(); i++) { for(int j = 0; j < l2.size(); j++) { newlist.add(charAt(i)); newlist.add(charAt(j)); } } return newlist; } 

P.S. I do not know how to properly add the elements as they are generic. I have typed in the absolutely wrong method of "charAt", just to showcase what I would attempt to do if the type wasn't generic but a character. Since the elements however can be generic I am very unsure of what to do.

5
  • 2
    Does your return list must be of type T? In that case that would implied that S is a subclass fo T. If this is not the case I guess that the best would be to return a list of Object (or a superclass of both S and T if such exists) Commented Jan 18, 2018 at 16:26
  • will the two lists always have the same types? Commented Jan 18, 2018 at 16:27
  • Also, be careful that your algorithm won't do what you want to achieve. you should have a first loop from 0 to the smaller list size. Then you could have two loops going from that size to the end of each list, completing your result list with remaining elements (don't know if I'm clear :-P) Commented Jan 18, 2018 at 16:29
  • 1
    May I suggest that you use "zip" as part of the method name... the way your merge goes is like "pulling a zipper up/down". Commented Jan 18, 2018 at 17:11
  • It would sure be a good name. I, however live in Germany and my lecturer wanted me to use this name :) Commented Jan 18, 2018 at 18:16

5 Answers 5

9

This will return a list of a common supertype of the generic types of the two argument lists:

public <R, S extends R, T extends R> List<R> listeMischen(List<S> l1, List<T> l2) { List<R> newList = new ArrayList<>(l1.size() + l2.size()); int sizeOfLargerList = Math.max(l1.size(), l2.size()); for (int i = 0; i < sizeOfLargerList; i++) { if (i < l1.size()) newList.add(l1.get(i)); if (i < l2.size()) newList.add(l2.get(i)); } return newList; } 

Usage:

public static void main(String[] args) { List<Number> list = listeMischen(Arrays.asList(1, 2, 3), Arrays.asList(4.5, 5.5, 6.5, 7.5, 8.5)); System.out.println(list); } 

Expected output == Actual output:

[1, 4.5, 2, 5.5, 3, 6.5, 7.5, 8.5] 


UPDATE: adding optimized methods taking the comments into account

The following shows two overloads of the method: one for random-access lists (such as ArrayList) and another for any old iterable types, with bodies optimized for each type.

static <R> List<R> listeMischen(List<? extends R> l1, List<? extends R> l2) { if (!(l1 instanceof RandomAccess && l2 instanceof RandomAccess)) return listeMischen((Iterable<? extends R>) l1, (Iterable<? extends R>) l2); // Preallocate with known exact required capacity List<R> newList = new ArrayList<>(l1.size() + l2.size()); int sizeOfSmallerList = Math.min(l1.size(), l2.size()); int i; // Zip the lists up to common maximum index for (i = 0; i < sizeOfSmallerList; i++) { newList.add(l1.get(i)); newList.add(l2.get(i)); } // Add any remaining items from one or the other list for (; i < l1.size(); i++) newList.add(l1.get(i)); for (; i < l2.size(); i++) newList.add(l2.get(i)); return newList; } static <R> List<R> listeMischen(Iterable<? extends R> l1, Iterable<? extends R> l2) { List<R> newList = new ArrayList<>(); Iterator<? extends R> it1 = l1.iterator(); Iterator<? extends R> it2 = l2.iterator(); // Zip the lists up to common maximum index while (it1.hasNext() && it2.hasNext()) { newList.add(it1.next()); newList.add(it2.next()); } // Add any remaining items from one or the other lists it1.forEachRemaining(newList::add); it2.forEachRemaining(newList::add); return newList; } 
Sign up to request clarification or add additional context in comments.

7 Comments

Might be a bit more efficient to loop first on the minimum list length without if and repetitive calls to lx.size() inside the loop, and then add the remainder of either list if any using a if { for () } -else-if { for () }
@ValentinRuano true. Alternatively, instead of using List.size() I can use .hasNext() (just like OldCurmudgeon's answer).
@DodgyCodeException I find that in practice if you are working with ArrayLists a for-get loop is faster that using iterators, I think due to the compiler being able to do a better job are optimizing the code. however the iterator version is preferable when one doesn't know if the get operation is inefficient like with LinkedList. In any case hasNext is still a if and a repetitive call on a mutable object (the iterator) so I think in practice as long as you use ArrayList the for loop is going to be faster.
Nice use of bounded type parameters (+1).
Another observation... actually you don't need to declare type-parameters S and T... you can declare the l1 and l2 arguments as List<? extends R>. I guess there is no harm done but I think ? is preferable (more parsimonious).
|
3

The only sensible thing to do is return a list of Object. Generics aren't going to help since your list is non-homogenous (and carries no guarantees, implied or explicit, of the relationship between the two object types of your list).

public List<Object> mixedList(List<?> a, List<?> b) { List<Object> result = new ArrayList<>(); for(int i = 0, j = 0; i < a.size() && j < b.size(); i++, j++) { result.add(a.get(i)); result.add(b.get(j)); } return result; } 

I leave the actual issue of weaving elements in the way you prescribe as an exercise for the reader. This should satisfy the case in which both lists are the same length.

Comments

2

You have to find some common ground for your S and T, either S is a T or the other way around or both are subtypes of some U:

public <S extends T, T> List<T> listeMischen(List<S> l1, List<T> l2) { List<T> newlist = new ArrayList<>(); // i will leave the "adding" logic to you, just for the sake of demonstration that you can add elements of both lists newlist.add(l1.get(0)); newlist.add(l2.get(0)); return newlist; } public <U, S extends U, T extends U> List<U> listeMischen2(List<S> l1, List<T> l2) { List<U> newlist = new ArrayList<>(); // i will leave the "adding" logic to you, just for the sake of demonstration that you can add elements of both lists newlist.add(l1.get(0)); newlist.add(l2.get(0)); return newlist; } 

1 Comment

ah, okay. I have re-watched a video on generics and it makes sense what you were saying. I should keep that in mind
1

Assuming there is a common type between S and T you could do this easily with Iterators.

public <S extends T, T> List<T> listeMischen(Iterable<S> l1, Iterable<T> l2) { List<T> newlist = new ArrayList<T>(); Iterator<S> l1i = l1.iterator(); Iterator<T> l2i = l2.iterator(); // Consume both while we can. while (l1i.hasNext() && l2i.hasNext()) { newlist.add(l1i.next()); newlist.add(l2i.next()); } // Consume whatever remains of the remaining list. while (l1i.hasNext()) { newlist.add(l1i.next()); } while (l2i.hasNext()) { newlist.add(l2i.next()); } return newlist; } 

If there is no common type or interface then you will have to work with List<Object>.

Comments

1

Other answers suggest that generics cannot be used, or that the type of the result must be List<Object>. Better answers suggest that there must be some type relationship between the elements of the two input lists and that of the output list. The best of these is DodgyCodeException's answer (+1) which uses generic type bounds to require the result list's elements to be a supertype of the element types of both input lists.

I'll offer an alternative, which is that the type of the result list is the result of converting elements from the input lists, and that the element types of the input lists need not have any direct relationship to each other, nor to the element types of the result list. Instead, function arguments are supplied that do the conversion.

Here's an example derived from DodgyCodeException's that uses conversion functions instead of requiring a type relationship among the inputs and the output.

static <T, U, R> List<R> listMerge(List<T> l1, List<U> l2, Function<? super T, ? extends R> f1, Function<? super U, ? extends R> f2) { int size1 = l1.size(); int size2 = l2.size(); int max = Math.max(size1, size2); List<R> result = new ArrayList<>(size1 + size2); for (int i = 0; i < max; i++) { if (i < size1) result.add(f1.apply(l1.get(i))); if (i < size2) result.add(f2.apply(l2.get(i))); } return result; } 

This allows list mixing/conversion such as the following:

List<String> list = listMerge(List.of(1, 2, 3), List.of(4.5, 5.5, 6.5, 7.5, 8.5), i -> String.format("0x%x", i), d -> String.format("%.1f", d)); [0x1, 4.5, 0x2, 5.5, 0x3, 6.5, 7.5, 8.5] 

Various stream-zipping approaches, such as Guava's Streams.zip, also take the functional conversion approach instead of a type-bound approach.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.