How to concatenate lists into one list

Refresh

February 2019

Views

911 time

16

I have a list of values, some of which could be lists/collections or single values. In JavaScript notation it might look like:

const input = [1,2,[3,4], [5,6], 7];

and I want to get:

const concatenated = [1,2,3,4,5,6,7];

So I have this Java code:

      ArrayList<T> concatenated = new ArrayList<>();

      for (T v : input) {
        try{
          concatenated.addAll((Collection) v);
        }
        catch (Exception e1){
          try{
            concatenated.addAll((List) v);
          }
          catch (Exception e2){
            concatenated.add(v);
          }
        }

     }

but that code seems pretty terrible to me. First I don't know if attempting to cast to List or Collection is sufficient - are there are other types I should attempt to cast to? Are there any errors I shouldn't ignore?

How to do this right?

4 answers

6

Как уже упоминалось, использование исключений для управления потоком не является идеальным. Вместо этого вы можете использовать instanceofоператор , чтобы проверить , если элемент является Collection. Ответ на NullPointer показывает хороший пример. Если вы хотите более универсальный вариант вы также можете сделать что - то вроде:

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

public static <E> List<E> deepFlatten(final Iterable<?> iterable, final Class<E> type) {
    if (type.isPrimitive() || type.isArray() || Iterable.class.isAssignableFrom(type)) {
        throw new IllegalArgumentException(
                "type must not denote a primitive, array, or java.lang.Iterable type: " + type);
    }
    final List<E> result = new ArrayList<>();
    for (final Object element : iterable) {

        if (element instanceof Iterable<?>) {
            result.addAll(deepFlatten((Iterable<?>) element, type)); // recursion

        } else if (element != null && element.getClass().isArray()) {

            if (element instanceof Object[]) {
                result.addAll(deepFlatten(Arrays.asList((Object[]) element), type)); // recursion
            } else { // primitive array
                final Iterable<?> itrArray = IntStream.range(0, Array.getLength(element))
                        .mapToObj(index -> Array.get(element, index))::iterator; // method reference
                result.addAll(deepFlatten(itrArray, type)); // recursion
            }

        } else {
            /*
             * Will throw ClassCastException if any element is not an instance
             * of "type". You could also throw a NullPointerException here if
             * you don't want to allow null elements.
             */
            result.add(type.cast(element));
        }

    }
    return result;
}

Это также обрабатывает «встроенные» массивы, а также Iterableс, через рекурсию. Обратите внимание , что не обрабатывает MapS из - за неоднозначности; мы должны выравниваться ключи или значения или оба?

Вызов выше:

Iterable<?> iterable = List.of(
        "A", "B", "C", "D",
        List.of("E", "F", List.of("G", "H"), "I", "J"),
        "K",
        new String[]{"L", "M", "N", "O", "P"},
        new String[][]{{"Q", "R"}, {"S", "T"}, {"U"}, {"V"}},
        new Object[]{"W", "X"},
        "Y", "Z"
);
List<String> flattened = deepFlatten(iterable, String.class);
System.out.println(flattened);

Дал мне:

[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]

Обратите внимание , что буквы в порядке , потому что Listс и массивы гарантированно итерационные заказов. Если Iterableсодержал Setрезультат из deepFlattenне может быть в том же порядке каждый раз.

14

The code doesn't need Exception handling as such unless there are null values in the lists. It should be sufficient though in your case to just cast basis of instanceOf as:

// Edit: Since the type of the input `Collection` is not bound strictly
List<Object> flatten(Collection<?> input) {
    List<Object> concatenated = new ArrayList<>();
    for (Object v : input) {
        if (v instanceof Collection) {
            concatenated.addAll(flatten((Collection<?>) v));
        } else {
            concatenated.add(v);
        }
    }
    return concatenated;
} 

using it further on jshell gives me this output:

jshell> List<Object> list = List.of(1,2,List.of(3,4),List.of(5,6),7) 
list ==> [1, 2, [3, 4], [5, 6], 7]

jshell> flatten(list)
$3 ==> [1, 2, 3, 4, 5, 6, 7]

:

14

The code doesn't need Exception handling as such unless there are null values in the lists. It should be sufficient though in your case to just cast basis of instanceOf as:

// Edit: Since the type of the input `Collection` is not bound strictly
List<Object> flatten(Collection<?> input) {
    List<Object> concatenated = new ArrayList<>();
    for (Object v : input) {
        if (v instanceof Collection) {
            concatenated.addAll(flatten((Collection<?>) v));
        } else {
            concatenated.add(v);
        }
    }
    return concatenated;
} 

using it further on jshell gives me this output:

jshell> List<Object> list = List.of(1,2,List.of(3,4),List.of(5,6),7) 
list ==> [1, 2, [3, 4], [5, 6], 7]

jshell> flatten(list)
$3 ==> [1, 2, 3, 4, 5, 6, 7]

:

3

Use of Exceptions to control application flow/business logic is an anti-pattern. You can read more about it here, here and here.

Regarding storing different types of elements in Collections could be difficult to debug and maintain. You can write your own wrapper and encapsulate the handling of it from usage. You can refer this for an inspiration.