Подтвердить что ты не робот

Как сгладить все элементы из вложенной коллекции Java в один список?

Учитывая сложный вложенный набор объектов, таких как:

Set<List<Map<String, List<Object>>>> complexNestedCollection;

Существует ли общий метод, чтобы сгладить это и получить единственный List из всех Object, содержащийся внутри?

Несколько деталей:

  • В список не должны включаться объекты коллекции или ключи карты - только значения на самом низком уровне.
  • Он должен следовать тем же самым порядком, где это возможно, поэтому в этом примере элементы в списке будут в порядке, тогда как упорядочение карт/наборов будет зависеть от реализации.
  • Он может необязательно исключать дубликаты
  • UPDATE: Он должен идеально определять/обрабатывать циклические ссылки на любом уровне, например. a List<List<Object>>, где внешний список содержит себя как член. (Кредит Адриану Ялошевскому за упоминание об этом в комментариях ниже).

Примечание. Фактический прецедент состоит в том, чтобы получить все строки из List<List<String>>, что можно сделать достаточно легко с двумя циклами, но это заставило меня задуматься об общем случае.

4b9b3361

Ответ 1

Вот класс FlattenEverythingButTheKitchenSink, слегка измененная версия предыдущего ответа. Он был протестирован с Java 7 и Java 8.

Он работает со списками, наборами, картами, очередями и даже массивами произвольной глубины. Он компилируется и запускается без предупреждения, и я не мог найти контрпример. Следовательно, имя класса:)

Если вы хотите Список объектов с возможными дубликатами, используйте flatten, если вы хотите Set, используйте uniqFlatten.

EDITED: рефакторинг, чтобы избежать повторения кода.

package stackOverflow;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;


// Answer for
// https://stackoverflow.com/questions/20144826/how-to-flatten-all-items-from-a-nested-collection-into-a-single-list
public class FlattenEverythingButTheKitchenSink
{
    public static void main(String[] args) {
        int[][][] int3dArray = { { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } },
                { { 10, 11, 12 }, { 13, 14, 15 }, { 16, 17, 18 } },
                { { 19, 20, 21 }, { 22, 23, 24 }, { 25, 26, 27 }, { 28 }, { 29, 30 } } };
        String[][] string2dArray = { { "He, llo" }, { "Wo", "rld" } };
        String[] stringArray = { "Hello", "World" };
        Set<Integer> integersSet = new HashSet<Integer>();
        integersSet.add(1);
        integersSet.add(2);
        integersSet.add(3);

        Map<String, String> stringMap = new HashMap<>();
        stringMap.put("key1", "value1");
        stringMap.put("key2", "value2");
        stringMap.put("key3", "value3");

        Queue<String> qe = new LinkedList<String>();
        qe.add("x");
        qe.add("y");
        qe.add("z");

        Object[] objectArray = { "Hell", 0, "W", 0, "orld", integersSet, stringMap, qe };

        List<Object> mixList = new ArrayList<Object>();
        mixList.add("String");
        mixList.add(3);
        mixList.add(string2dArray);

        System.out.println(flatten(int3dArray));
        System.out.println(flatten(flatten(int3dArray)));
        System.out.println(flatten(3));
        System.out.println(flatten(stringArray));
        System.out.println(flatten(string2dArray));
        System.out.println(flatten(objectArray));
        System.out.println(flatten(mixList));

        mixList.add(int3dArray);

        System.out.println(uniqFlatten(mixList));
    }

    public static List<Object> flatten(Object object) {
        return (List<Object>) recursiveFlatten(object, true);
    }

    public static Set<Object> uniqFlatten(Object object) {
        return (Set<Object>) recursiveFlatten(object, false);
    }

    private static Collection<Object> recursiveFlatten(Object object, Boolean allowDuplicates) {
        Collection<Object> setOrList;
        if (allowDuplicates) {
            setOrList = new ArrayList<Object>();
        } else {
            setOrList = new LinkedHashSet<Object>();
        }
        if (object.getClass().isArray()) {
            for (int i = 0; i < Array.getLength(object); i++) {
                setOrList.addAll(recursiveFlatten(Array.get(object, i), allowDuplicates));
            }
        } else if (object instanceof Map) {
            for (Object element : ((Map<?, ?>) object).values()) {
                setOrList.addAll(recursiveFlatten(element, allowDuplicates));
            }
        } else if (object instanceof Iterable) {
            for (Object element : (Iterable<?>) object) {
                setOrList.addAll(recursiveFlatten(element, allowDuplicates));
            }
        } else {
            setOrList.add(object);
        }
        return setOrList;
    }
}

Он выводит:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
[3]
[Hello, World]
[He, llo, Wo, rld]
[Hell, 0, W, 0, orld, 1, 2, 3, value1, value2, value3, x, y, z]
[String, 3, He, llo, Wo, rld]
[String, 3, He, llo, Wo, rld, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]

и не должен иметь никаких проблем с

Set<List<Map<String, List<Object>>>> complexNestedCollection;

Он также будет работать с

Set<List<Map<String, List<int[][][][]>>>>

Инициализационный код будет не очень приятным: D

Ответ 2

Я не уверен, будет ли эта точная реализация работать, поскольку она полна непроверенных предупреждений и других опасных вещей, но вы должны получить общую идею.

public static Set<Object> recursiveExtract(Object stuff) {

    Set<Object> set = new HashSet<Object>();

    if(stuff instanceof Iterable) {
        for(Object o : (Iterable<?>)stuff) {
            set.addAll(recursiveExtract(o));
        }
    } else if(stuff instanceof Map) {
        for(Object o : ((Map<?, ? extends Object>) stuff).values()) {
            set.addAll(recursiveExtract(o));
        }
    } else {
        set.add(stuff);
    }

    return set;
}

Вы также можете использовать List<Object>, если вы настаиваете на List, но тогда вы можете получить повторяющиеся результаты или LinkedHashSet<Object>, если вам нужен порядок.


Пожалуйста, вместо downvotes, дайте мне предложения по улучшению. Это лучше.

Ответ 3

Предполагая, что вы используете Java 8, вы можете сделать это с помощью Stream API благодаря методу flatMap(Function<? super T,? extends Stream<? extends R>> mapper) следующим образом:

// 1. Convert the Set as a Stream of List<Map<String, List<Object>>>
// 2. Extract the elements of the lists to get a Stream of Map<String, List<Object>>
// 3. Extract values of the maps to get a Stream of List<Object>
// 4. Extract the elements of the lists to get a Stream of Object
// 5. Get rid of duplicates
// 6. Collect the result as a List of Object
List<Object> result = complexNestedCollection.stream()
    .flatMap(List::stream)
    .flatMap(m -> m.values().stream())
    .flatMap(List::stream)
    .distinct()
    .collect(Collectors.toList());

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

Возвращает поток, состоящий из результатов замены каждого элемента этого потока с содержимым отображаемого потока, создаваемого применяя предоставленную функцию отображения к каждому элементу. Каждый сопоставленный поток закрывается после того, как его содержимое было помещено в этот поток. (Если отображаемый поток равен нулю, вместо него используется пустой поток.) ​​


Для предыдущих версий Java вы все равно можете использовать FluentIterable из Google Guava, чтобы заменить Stream и использовать transformAndConcat(Function<? super E,? extends Iterable<? extends T>> function) вместо flatMap, чтобы сгладить вашу коллекцию.

Предыдущий фрагмент кода затем будет переписан следующим образом:

List<Object> result =
    new ArrayList<>(
        new LinkedHashSet<>(
            FluentIterable.from(complexNestedCollection)
                .transformAndConcat(
                    new Function<List<Map<String, List<Object>>>, Iterable<Map<String, List<Object>>>> () {
                        public Iterable<Map<String, List<Object>>> apply(final List<Map<String, List<Object>>> input) {
                            return input;
                        }
                    }
                ).transformAndConcat(
                    new Function<Map<String, List<Object>>, Iterable<List<Object>>> () {
                        public Iterable<List<Object>> apply(final Map<String, List<Object>> input) {
                            return input.values();
                        }
                    }
                ).transformAndConcat(
                    new Function<List<Object>, Iterable<Object>> () {
                        public Iterable<Object> apply(final List<Object> input) {
                            return input;
                        }
                    }
                ).toList()
        )
    );

Ответ 4

Вы можете использовать LambdaJ функцию сглаживания.

List<Object> simpleCollection = flatten(flatten(flatten(complexNestedCollection)));

Ответ 5

Мое предложение решить проблему - создать класс, который сглаживает коллекции и карты рекурсивно, который запоминает коллекции и карты, которые уже были посещены, для обработки круговых зависимостей. Это прямая forawart реализация алгоритма DFS.

public class CollectionFlattener {
    private List<Object> returnList = new LinkedList<>();
    private Visited visited = new Visited();

    public CollectionFlattener(Object o) {
        handle(o);
    }

    private void handle(Object o) {
        if (o instanceof Map) {
            handleMap((Map) o);
        } else if (o instanceof Collection) {
            handleCollection((Collection) o);
        } else {
            returnList.add(o);
        }
    }

    private void handleCollection(Collection<?> collection) {
        if (!visited.isVisited(collection)) {
            visited.visit(collection);
            collection.forEach(this::handle);
        }
    }

    private void handleMap(Map<?, ?> map) {
        if (!visited.isVisited(map)) {
            visited.visit(map);
            handleCollection(map.values());
        }
    }

    public Collection<Object> getFlatCollection() {
        return new LinkedList<>(returnList);
    }
}

Класс Visited должен предложить способ проверить, является ли тот объект, с которым мы столкнулись, ТО ЖЕ (это причина, по которой я использую здесь оператор == вместо equals). Это единственный способ уменьшить круговые зависимости, не теряя информацию о коллекциях, которые по совпадению содержат одни и те же элементы.

public class Visited {
    private List<Object> visited = new LinkedList<>();

    public void visit(Object o) {
        if (!isVisited(o)) {
            visited.add(o);
        }
    }
    public boolean isVisited(Object o) {
        long count = visited.stream().filter(object -> object == o).count();
        return count != 0;
    }
}

Единственное, что здесь необходимо, это проверка null, но не нужно понимать логику этого решения.

Ответ 6

Мне интересно, что такое сценарий, и если не было бы лучше определить некоторую конкретную структуру данных, такую ​​как дерево. Но в любом случае:

Я бы избегал дженериков, поскольку система типов java слишком упрощена для обработки рекурсивных типов:

public static Collection flatten(Iterable collection, boolean duplicatesAllowed) {
    // create the result collection it just once and 
    ///pass it around as an accumulator
    // it gives you better time/space complexity
    Collection result = duplicatesAllowed ? new ArrayList() : new LinkedHashSet();
    flattenImpl(collection, result);
    return result;
}

Это поддерживается двумя частными функциями, которые выполняют фактическое извлечение заполненной коллекции result:

private static void flattenImpl(Object obj, Collection result) {
    if (obj instanceof Iterable) {
        flattenImpl((Iterable)obj, result);
    } 
    else if (obj instanceof Map) {
        flattenImpl( ((Map)obj).values(), result);
    } 
    else {
        result.add(obj);
    }
}

private static void flattenImpl(Iterable collection, Collection result) {
    for(Object o : collection) {
        flattenImpl(o, result);
    }
}