|
9 | 9 | import gov.nasa.jpl.aerie.types.ActivityDirective; |
10 | 10 | import gov.nasa.jpl.aerie.types.ActivityDirectiveId; |
11 | 11 |
|
| 12 | +import java.util.ArrayList; |
12 | 13 | import java.util.Collection; |
13 | 14 | import java.util.HashMap; |
14 | 15 | import java.util.HashSet; |
| 16 | +import java.util.List; |
15 | 17 | import java.util.Map; |
16 | 18 | import java.util.Optional; |
17 | 19 | import java.util.Set; |
18 | 20 | import java.util.function.Supplier; |
19 | | -import java.util.stream.Collectors; |
20 | 21 |
|
21 | 22 | public interface SimulationFacade { |
22 | 23 | void setInitialSimResults(SimulationData simulationData); |
@@ -82,28 +83,68 @@ public boolean equals(Object other){ |
82 | 83 | * `map` contains a mapping between *only the IDs that are different* between the two plans. |
83 | 84 | */ |
84 | 85 | public Optional<Map<ActivityDirectiveId, ActivityDirectiveId>> equalsWithIdMap(PlanSimCorrespondence other) { |
| 86 | + // If the simulations have a different amount of directives, they must not be equal |
| 87 | + if(directiveIdActivityDirectiveMap.size() != other.directiveIdActivityDirectiveMap.size()) { |
| 88 | + return Optional.empty(); |
| 89 | + } |
| 90 | + |
| 91 | + // Build the maps of directives -> ids from the ids -> directives map |
| 92 | + // Because multiple activities on a plan can be identical sans id, the inverted maps to a list of ids |
| 93 | + final var thisInverted = new HashMap<ActivityDirective, List<ActivityDirectiveId>>(directiveIdActivityDirectiveMap.size()); |
| 94 | + directiveIdActivityDirectiveMap.forEach( |
| 95 | + (key, value) -> { |
| 96 | + thisInverted.putIfAbsent(value, new ArrayList<>()); |
| 97 | + thisInverted.get(value).add(key); |
| 98 | + }); |
| 99 | + |
| 100 | + final var otherInverted = new HashMap<ActivityDirective, List<ActivityDirectiveId>>(other.directiveIdActivityDirectiveMap.size()); |
| 101 | + other.directiveIdActivityDirectiveMap.forEach( |
| 102 | + (key, value) -> { |
| 103 | + otherInverted.putIfAbsent(value, new ArrayList<>()); |
| 104 | + otherInverted.get(value).add(key); |
| 105 | + }); |
| 106 | + |
| 107 | + // If these maps have different sizes, then the simulations must not be equal |
| 108 | + if(thisInverted.size() != otherInverted.size()) { |
| 109 | + return Optional.empty(); |
| 110 | + } |
| 111 | + |
| 112 | + // Check every entry for equality while building up the return mapping |
85 | 113 | final var result = new HashMap<ActivityDirectiveId, ActivityDirectiveId>(); |
86 | | - final var thisInverse = directiveIdActivityDirectiveMap.entrySet() |
87 | | - .stream() |
88 | | - .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); |
89 | | - final var otherInverse = other.directiveIdActivityDirectiveMap.entrySet() |
90 | | - .stream() |
91 | | - .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); |
92 | | - for (var entry : thisInverse.entrySet()) { |
93 | | - final var otherId = otherInverse.remove(entry.getKey()); |
94 | | - if (otherId == null) { |
| 114 | + for (final var entry : thisInverted.entrySet()) { |
| 115 | + // Check that at least one instance of this directive is on the other plan |
| 116 | + if(!otherInverted.containsKey(entry.getKey())){ |
| 117 | + return Optional.empty(); |
| 118 | + } |
| 119 | + |
| 120 | + final var thisIds = entry.getValue(); |
| 121 | + final var otherIds = otherInverted.get(entry.getKey()); |
| 122 | + |
| 123 | + // Check that there is the same number of instances of this directive on the other plan |
| 124 | + if (thisIds.size() != otherIds.size()) { |
95 | 125 | return Optional.empty(); |
96 | 126 | } |
97 | | - if (!otherId.equals(entry.getValue())) { |
98 | | - result.put(entry.getValue(), otherId); |
| 127 | + |
| 128 | + // If the directive is unique (the most common case), then the two ids can be directly compared |
| 129 | + if(thisIds.size() == 1) { |
| 130 | + if(!thisIds.getFirst().equals(otherIds.getFirst())) { |
| 131 | + result.put(thisIds.getFirst(), otherIds.getFirst()); |
| 132 | + } |
| 133 | + continue; |
99 | 134 | } |
100 | | - } |
101 | 135 |
|
102 | | - if (otherInverse.isEmpty()) { |
103 | | - return Optional.of(result); |
104 | | - } else { |
105 | | - return Optional.empty(); |
| 136 | + // Filter out the ids that are on both lists, as this method does not provide a mapping for ids that have not changed |
| 137 | + final List<ActivityDirectiveId> toRemove = thisIds.stream().filter(otherIds::contains).toList(); |
| 138 | + thisIds.removeAll(toRemove); |
| 139 | + otherIds.removeAll(toRemove); |
| 140 | + |
| 141 | + // Add the remaining mappings to the result |
| 142 | + for(int i = 0; i < thisIds.size(); ++i) { |
| 143 | + result.put(thisIds.get(i), otherIds.get(i)); |
| 144 | + } |
106 | 145 | } |
| 146 | + |
| 147 | + return Optional.of(result); |
107 | 148 | } |
108 | 149 | } |
109 | 150 | } |
0 commit comments