¶Ô±ÈÐÂÎļþ |
| | |
| | | package org.jeecg.modules.dnc.utils; |
| | | |
| | | import it.unimi.dsi.fastutil.longs.*; |
| | | import org.jeecg.modules.dnc.entity.ProductMix; |
| | | |
| | | import java.util.*; |
| | | import java.util.concurrent.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | public class TreeBuilder { |
| | | //========================= æ°æ®é¢å¤ç =========================// |
| | | public CleanResult preprocessData(List<ProductMix> rawList) { |
| | | // é¶æ®µ1ï¼æå»ºçº¿ç¨å®å
¨çèç¹æ å° |
| | | ConcurrentHashMap<Long, ProductMix> tempMap = (ConcurrentHashMap<Long, ProductMix>) rawList.parallelStream() |
| | | .collect(Collectors.toConcurrentMap( |
| | | ProductMix::getId, |
| | | item -> item, |
| | | (existing, replacement) -> existing |
| | | )); |
| | | |
| | | // 转æ¢ä¸ºFastUtilç»æä»¥ä¼åå
å |
| | | Long2ObjectOpenHashMap<ProductMix> nodeMap = new Long2ObjectOpenHashMap<>(tempMap.size()); |
| | | nodeMap.putAll(tempMap); |
| | | |
| | | // é¶æ®µ2ï¼è¿æ»¤ææèç¹ |
| | | List<ProductMix> validNodes = rawList.parallelStream() |
| | | .filter(item -> { |
| | | if (item.getParentId() == 0) return true; // æ ¹èç¹ç´æ¥éè¿ |
| | | if (!nodeMap.containsKey(item.getParentId())) { |
| | | System.err.println("æ æç¶èç¹å¼ç¨: ID=" + item.getId() + " ç¶ID=" + item.getParentId()); |
| | | return false; |
| | | } |
| | | return true; |
| | | }) |
| | | .collect(Collectors.toList()); |
| | | |
| | | return new CleanResult(nodeMap, validNodes); |
| | | } |
| | | |
| | | //========================= æææåº =========================// |
| | | public List<ProductMix> topologicalSort(List<ProductMix> nodes, |
| | | Long2ObjectMap<ProductMix> nodeMap) { |
| | | // é²å¾¡ç©ºæ°æ® |
| | | if (nodes.isEmpty()) return Collections.emptyList(); |
| | | |
| | | // 1. æå»ºé»æ¥è¡¨ï¼ç¶èç¹ -> åèç¹åè¡¨ï¼ |
| | | Long2ObjectMap<List<Long>> adjacencyList = new Long2ObjectOpenHashMap<>(nodes.size()); |
| | | nodes.forEach(node -> { |
| | | long parentId = node.getParentId(); |
| | | adjacencyList.computeIfAbsent(parentId, k -> new ArrayList<>()) |
| | | .add(node.getId()); |
| | | }); |
| | | |
| | | // 2. åå§åå
¥åº¦è®¡æ°å¨ |
| | | Long2IntMap inDegree = new Long2IntOpenHashMap(); |
| | | nodes.forEach(node -> inDegree.put(node.getId(), 0)); |
| | | |
| | | // 3. 计ç®å
¥åº¦ |
| | | nodes.forEach(node -> { |
| | | if (node.getParentId() != 0 && nodeMap.containsKey(node.getParentId())) { |
| | | inDegree.put(node.getId(), inDegree.get(node.getId()) + 1); |
| | | } |
| | | }); |
| | | |
| | | // 4. åå§åéå |
| | | Queue<Long> queue = new ConcurrentLinkedQueue<>( |
| | | nodes.stream() |
| | | .filter(node -> inDegree.get(node.getId()) == 0) |
| | | .map(ProductMix::getId) |
| | | .collect(Collectors.toList()) |
| | | ); |
| | | |
| | | // 5. æææåºæ ¸å¿é»è¾ |
| | | List<ProductMix> sorted = new ArrayList<>(nodes.size()); |
| | | while (!queue.isEmpty()) { |
| | | Long currentId = queue.poll(); |
| | | ProductMix current = nodeMap.get(currentId); |
| | | sorted.add(current); |
| | | |
| | | List<Long> children = adjacencyList.getOrDefault(currentId, Collections.emptyList()); |
| | | children.forEach(childId -> { |
| | | int degree = inDegree.get(childId) - 1; |
| | | inDegree.put(childId, Integer.valueOf(degree)); |
| | | if (degree == 0) { |
| | | queue.offer(childId); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | // 6. å¾ªç¯æ£æµ |
| | | if (sorted.size() != nodes.size()) { |
| | | handleSortingFailure(nodes, sorted, nodeMap); |
| | | return Collections.emptyList(); |
| | | } |
| | | return sorted; |
| | | } |
| | | |
| | | //========================= æ ç»æç»è£
=========================// |
| | | public List<ProductMix> assembleTree(List<ProductMix> sortedNodes, |
| | | Long2ObjectMap<ProductMix> nodeMap) { |
| | | // 1. å建èç¹å¯æ¬ |
| | | Map<Long, ProductMix> treeMap = new ConcurrentHashMap<>(sortedNodes.size()); |
| | | sortedNodes.forEach(node -> { |
| | | ProductMix newNode = new ProductMix( |
| | | node.getId(), |
| | | node.getParentId(), |
| | | node.getName(), |
| | | node.getCode(), |
| | | node.getType() |
| | | ); |
| | | treeMap.put(newNode.getId(), newNode); |
| | | }); |
| | | |
| | | // 2. 建ç«ç¶åå
³ç³» |
| | | sortedNodes.forEach(node -> { |
| | | if (node.getParentId() != 0) { |
| | | ProductMix parent = treeMap.get(node.getParentId()); |
| | | if (parent != null) { |
| | | parent.getChildren().add(treeMap.get(node.getId())); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | // 3. è¿åæ ¹èç¹ |
| | | return treeMap.values().stream() |
| | | .filter(node -> node.getParentId() == 0) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | //========================= è¾
婿¹æ³ =========================// |
| | | private void handleSortingFailure(List<ProductMix> originalNodes, |
| | | List<ProductMix> sortedNodes, |
| | | Long2ObjectMap<ProductMix> nodeMap) { |
| | | // æ¥æ¾æªå¤ççèç¹ID |
| | | Set<Long> processedIds = sortedNodes.stream() |
| | | .map(ProductMix::getId) |
| | | .collect(Collectors.toSet()); |
| | | |
| | | List<ProductMix> unprocessed = originalNodes.stream() |
| | | .filter(node -> !processedIds.contains(node.getId())) |
| | | .collect(Collectors.toList()); |
| | | |
| | | System.err.println("æææåºå¤±è´¥ï¼æªå¤çèç¹æ°: " + unprocessed.size()); |
| | | detectCycles(unprocessed, nodeMap); |
| | | } |
| | | |
| | | private void detectCycles(List<ProductMix> nodes, |
| | | Long2ObjectMap<ProductMix> nodeMap) { |
| | | Set<Long> visited = new HashSet<>(); |
| | | nodes.forEach(node -> { |
| | | Long currentId = node.getId(); |
| | | Set<Long> path = new LinkedHashSet<>(); |
| | | |
| | | while (currentId != 0) { |
| | | if (!path.add(currentId)) { |
| | | System.err.println("循ç¯è·¯å¾: " + path); |
| | | return; |
| | | } |
| | | ProductMix current = nodeMap.get(currentId); |
| | | if (current == null) break; |
| | | currentId = current.getParentId(); |
| | | } |
| | | visited.addAll(path); |
| | | }); |
| | | } |
| | | |
| | | //========================= æ°æ®ç»æ =========================// |
| | | public static class CleanResult { |
| | | private final Long2ObjectMap<ProductMix> nodeMap; |
| | | private final List<ProductMix> validNodes; |
| | | |
| | | public CleanResult(Long2ObjectMap<ProductMix> nodeMap, |
| | | List<ProductMix> validNodes) { |
| | | this.nodeMap = nodeMap; |
| | | this.validNodes = validNodes; |
| | | } |
| | | |
| | | public Long2ObjectMap<ProductMix> getNodeMap() { return nodeMap; } |
| | | public List<ProductMix> getValidNodes() { return validNodes; } |
| | | } |
| | | } |