/** * diqube: Distributed Query Base. * * Copyright (C) 2015 Bastian Gloeckle * * This file is part of diqube. * * diqube is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.diqube.util; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; /** * Executes a simple topological sort based on a few helper functions. * * @author Bastian Gloeckle */ public class TopologicalSort<T> { private Function<T, List<T>> allSuccessorsFunction; private Function<T, Long> idFunction; private BiConsumer<T, Integer> newIndexConsumer; /** * * @param allSuccessorsFunction * Find and return all successors to a specific object. * @param idFunction * Return a unique ID for a specific object. * @param newIndexConsumer * Will be called when the final position for one of the objects was found. */ public TopologicalSort(Function<T, List<T>> allSuccessorsFunction, Function<T, Long> idFunction, BiConsumer<T, Integer> newIndexConsumer) { this.allSuccessorsFunction = allSuccessorsFunction; this.idFunction = idFunction; this.newIndexConsumer = newIndexConsumer; } /** * Topologically sort the given values. * * @return A topologically sorted list of the objects * @throws IllegalArgumentException * If the values cannot be sorted topologically. */ public List<T> sort(List<T> values) throws IllegalArgumentException { List<T> res = new ArrayList<>(values.size()); Map<Long, T> idToValueMap = new HashMap<>(); Map<Long, List<Long>> successors = new HashMap<>(); for (T value : values) { long id = idFunction.apply(value); idToValueMap.put(id, value); successors.put(id, allSuccessorsFunction.apply(value).stream().map(idFunction).collect(Collectors.toList())); } Map<Long, Set<Long>> predecessors = new HashMap<>(); for (Long id : successors.keySet()) predecessors.put(id, new HashSet<>()); for (Entry<Long, List<Long>> successorEntry : successors.entrySet()) { for (Long successor : successorEntry.getValue()) predecessors.get(successor).add(successorEntry.getKey()); } Deque<Long> emptyPredecessors = new LinkedList<Long>(predecessors.entrySet().stream() .filter(entry -> entry.getValue().isEmpty()).map(entry -> entry.getKey()).collect(Collectors.toList())); while (!emptyPredecessors.isEmpty()) { Long id = emptyPredecessors.poll(); for (Long successorId : successors.get(id)) { predecessors.get(successorId).remove(id); if (predecessors.get(successorId).isEmpty()) emptyPredecessors.add(successorId); } predecessors.remove(id); if (newIndexConsumer != null) newIndexConsumer.accept(idToValueMap.get(id), res.size()); res.add(idToValueMap.get(id)); } if (!predecessors.isEmpty()) throw new IllegalArgumentException("Cyclic dependencies, cannot sort topologically."); return res; } }