Skip to content

Commit b3e3b00

Browse files
committed
Do not store before and after children in DiffNodes
Because the invariant `node.getBeforeParent().getBeforeChildren().contains(node)` holds for each `DiffNode node` the `beforeChildren` and `afterChildren` lists can be computed and do not have to be stored. By using this invariant, the check if a node is a before/after child can be implemented in `O(1)` in contrast to the `O(n)` version which uses the `beforeChildren` and `afterChildren` lists. This speedup is especially important for parsing `DiffTree`s. It is also possible to store `Set`s instead of `List`s for `DiffNode` children to get a similar time complexity, but as demonstrated by this commit that memory can be saved. If there is a need to compute a `beforeChildren` or `afterChildren` set (these lists didn't actually guarantee any ordering) more efficiently than by traversing `childOrdering` (which requires about the double time) this would be a valid approach.
1 parent 7043fae commit b3e3b00

2 files changed

Lines changed: 77 additions & 88 deletions

File tree

src/main/java/org/variantsync/diffdetective/diff/difftree/DiffNode.java

Lines changed: 76 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020
* Includes methods for creating a node by getting its code type and diff type and for getting the feature mapping of the node.
2121
*/
2222
public class DiffNode {
23-
private static final short ID_OFFSET = 3;
23+
private static final short ID_OFFSET = 3;
2424

25-
public DiffType diffType;
26-
public CodeType codeType;
25+
public final DiffType diffType;
26+
public final CodeType codeType;
2727
private boolean isMultilineMacro = false;
2828

2929
private final DiffLineNumber from = DiffLineNumber.Invalid();
@@ -34,29 +34,31 @@ public class DiffNode {
3434

3535
/**
3636
* The parent {@link DiffNode} before the edit.
37+
*
38+
* Invariant: Iff {@code beforeParent != null} then
39+
* {@code beforeParent.childOrder.contains(this)}.
3740
*/
3841
private DiffNode beforeParent;
39-
42+
4043
/**
4144
* The parent {@link DiffNode} after the edit.
45+
*
46+
* Invariant: Iff {@code afterParent != null} then
47+
* {@code afterParent.childOrder.contains(this)}.
4248
*/
4349
private DiffNode afterParent;
4450

4551
/**
4652
* We use a list for children to maintain order.
53+
*
54+
* Invariant: Iff {@code childOrder.contains(child)} then
55+
* {@code child.beforeParent == this || child.afterParent == this}.
56+
*
57+
* Note that it's explicitly allowed to have
58+
* {@code child.beforeParent == this && child.afterParent == this}.
4759
*/
4860
private final List<DiffNode> childOrder;
4961

50-
// These lists store the outgoing edges. These lists do not respect order.
51-
private List<DiffNode> beforeChildren;
52-
private List<DiffNode> afterChildren;
53-
54-
private DiffNode() {
55-
this.childOrder = new ArrayList<>();
56-
this.beforeChildren = new ArrayList<>();
57-
this.afterChildren = new ArrayList<>();
58-
}
59-
6062
public DiffNode(DiffType diffType, CodeType codeType,
6163
DiffLineNumber fromLines, DiffLineNumber toLines,
6264
Node featureMapping, String label) {
@@ -67,7 +69,8 @@ public DiffNode(DiffType diffType, CodeType codeType,
6769
public DiffNode(DiffType diffType, CodeType codeType,
6870
DiffLineNumber fromLines, DiffLineNumber toLines,
6971
Node featureMapping, List<String> lines) {
70-
this();
72+
this.childOrder = new ArrayList<>();
73+
7174
this.diffType = diffType;
7275
this.codeType = codeType;
7376
this.from.set(fromLines);
@@ -220,14 +223,6 @@ public int getTotalNumberOfChildren() {
220223
return childOrder.size();
221224
}
222225

223-
/**
224-
* @return The number of edges going to children.
225-
* This is the sum of the number of edges to before children and the number of edges to after children.
226-
*/
227-
public int getCardinality() {
228-
return beforeChildren.size() + afterChildren.size();
229-
}
230-
231226
/**
232227
* Gets the amount of nodes with diff type REM in the path following the before parent
233228
* @return the amount of nodes with diff type REM in the path following the before parent
@@ -275,10 +270,12 @@ public int getAddAmount() {
275270
}
276271

277272
private void setBeforeParent(final DiffNode newBeforeParent) {
273+
Assert.assertTrue(beforeParent == null);
278274
this.beforeParent = newBeforeParent;
279275
}
280276

281277
private void setAfterParent(final DiffNode newAfterParent) {
278+
Assert.assertTrue(afterParent == null);
282279
this.afterParent = newAfterParent;
283280
}
284281

@@ -336,8 +333,9 @@ public boolean insertChildAt(final DiffNode child, int index, Time time) {
336333

337334
public boolean insertBeforeChild(final DiffNode child, int index) {
338335
if (!child.isAdd()) {
339-
addWithoutDuplicates(beforeChildren, child);
340-
insertWithoutDuplicates(childOrder, child, index);
336+
if (!isChild(child)) {
337+
childOrder.add(index, child);
338+
}
341339
child.setBeforeParent(this);
342340
return true;
343341
}
@@ -346,8 +344,9 @@ public boolean insertBeforeChild(final DiffNode child, int index) {
346344

347345
public boolean insertAfterChild(final DiffNode child, int index) {
348346
if (!child.isRem()) {
349-
addWithoutDuplicates(afterChildren, child);
350-
insertWithoutDuplicates(childOrder, child, index);
347+
if (!isChild(child)) {
348+
childOrder.add(index, child);
349+
}
351350
child.setAfterParent(this);
352351
return true;
353352
}
@@ -356,8 +355,9 @@ public boolean insertAfterChild(final DiffNode child, int index) {
356355

357356
public boolean addBeforeChild(final DiffNode child) {
358357
if (!child.isAdd()) {
359-
addWithoutDuplicates(beforeChildren, child);
360-
addWithoutDuplicates(childOrder, child);
358+
if (!isChild(child)) {
359+
childOrder.add(child);
360+
}
361361
child.setBeforeParent(this);
362362
return true;
363363
}
@@ -366,8 +366,9 @@ public boolean addBeforeChild(final DiffNode child) {
366366

367367
public boolean addAfterChild(final DiffNode child) {
368368
if (!child.isRem()) {
369-
addWithoutDuplicates(afterChildren, child);
370-
addWithoutDuplicates(childOrder, child);
369+
if (!isChild(child)) {
370+
childOrder.add(child);
371+
}
371372
child.setAfterParent(this);
372373
return true;
373374
}
@@ -386,31 +387,19 @@ public void addAfterChildren(final Collection<DiffNode> afterChildren) {
386387
}
387388
}
388389

389-
private static void addWithoutDuplicates(final List<DiffNode> childrenlist, final DiffNode child) {
390-
if (!childrenlist.contains(child)) {
391-
childrenlist.add(child);
392-
}
393-
}
394-
395-
private static void insertWithoutDuplicates(final List<DiffNode> childrenlist, final DiffNode child, int index) {
396-
if (!childrenlist.contains(child)) {
397-
childrenlist.add(index, child);
398-
}
399-
}
400-
401390
public boolean removeBeforeChild(final DiffNode child) {
402-
if (this.beforeChildren.remove(child)) {
403-
removeFromCache(this.afterChildren, this.childOrder, child);
391+
if (isBeforeChild(child)) {
404392
dropBeforeChild(child);
393+
removeFromCache(child);
405394
return true;
406395
}
407396
return false;
408397
}
409398

410399
public boolean removeAfterChild(final DiffNode child) {
411-
if (this.afterChildren.remove(child)) {
412-
removeFromCache(this.beforeChildren, this.childOrder, child);
400+
if (isAfterChild(child)) {
413401
dropAfterChild(child);
402+
removeFromCache(child);
414403
return true;
415404
}
416405
return false;
@@ -424,34 +413,44 @@ public void removeChildren(final Collection<DiffNode> childrenToRemove) {
424413
}
425414

426415
public List<DiffNode> removeBeforeChildren() {
427-
for (final DiffNode child : beforeChildren) {
428-
removeFromCache(this.afterChildren, this.childOrder, child);
416+
final List<DiffNode> orphans = new ArrayList<>();
417+
418+
// Note that the following method call can't be written using a foreach loop reusing
419+
// {@code removeBeforeChild} because lists can't be modified during traversal.
420+
childOrder.removeIf(child -> {
421+
if (!isBeforeChild(child)) {
422+
return false;
423+
}
424+
425+
orphans.add(child);
429426
dropBeforeChild(child);
430-
}
427+
return !isAfterChild(child);
428+
});
431429

432-
final List<DiffNode> orphans = beforeChildren;
433-
beforeChildren = new ArrayList<>();
434430
return orphans;
435431
}
436432

437433
public List<DiffNode> removeAfterChildren() {
438-
for (final DiffNode child : afterChildren) {
439-
removeFromCache(this.beforeChildren, this.childOrder, child);
434+
final List<DiffNode> orphans = new ArrayList<>();
435+
436+
// Note that the following method call can't be written using a foreach loop reusing
437+
// {@code removeAfterChild} because lists can't be modified during traversal.
438+
childOrder.removeIf(child -> {
439+
if (!isAfterChild(child)) {
440+
return false;
441+
}
442+
443+
orphans.add(child);
440444
dropAfterChild(child);
441-
}
445+
return !isBeforeChild(child);
446+
});
442447

443-
final List<DiffNode> orphans = afterChildren;
444-
afterChildren = new ArrayList<>();
445448
return orphans;
446449
}
447450

448-
private static void removeFromCache(
449-
final List<DiffNode> childrenTypeB,
450-
final List<DiffNode> allChildren,
451-
final DiffNode child)
452-
{
453-
if (!childrenTypeB.contains(child)) {
454-
allChildren.remove(child);
451+
private void removeFromCache(final DiffNode child) {
452+
if (!isChild(child)) {
453+
childOrder.remove(child);
455454
}
456455
}
457456

@@ -506,18 +505,6 @@ public List<DiffNode> getAllChildren() {
506505
return getChildOrder();
507506
}
508507

509-
public List<DiffNode> getBeforeChildren() {
510-
return Collections.unmodifiableList(beforeChildren);
511-
}
512-
513-
public List<DiffNode> getAfterChildren() {
514-
return Collections.unmodifiableList(afterChildren);
515-
}
516-
517-
public List<DiffNode> getChildren(Time time) {
518-
return time.match(getBeforeChildren(), getAfterChildren());
519-
}
520-
521508
public void setIsMultilineMacro(boolean isMultilineMacro) {
522509
this.isMultilineMacro = isMultilineMacro;
523510
}
@@ -645,11 +632,15 @@ public Node getPresenceCondition(Time time) {
645632
}
646633

647634
public boolean isBeforeChild(DiffNode child) {
648-
return beforeChildren.contains(child);
635+
return child.beforeParent == this;
649636
}
650637

651638
public boolean isAfterChild(DiffNode child) {
652-
return afterChildren.contains(child);
639+
return child.afterParent == this;
640+
}
641+
642+
public boolean isChild(DiffNode child) {
643+
return isBeforeChild(child) || isAfterChild(child);
653644
}
654645

655646
public boolean isChild(DiffNode child, Time time) {
@@ -733,16 +724,14 @@ public static DiffNode fromID(final int id, String label) {
733724

734725
public void assertConsistency() {
735726
// check consistency of children lists and edges
736-
for (final DiffNode bc : beforeChildren) {
737-
Assert.assertTrue(bc.beforeParent == this, () -> "Before child " + bc + " of " + this + " has another parent " + bc.beforeParent + "!");
738-
Assert.assertTrue(childOrder.contains(bc), () -> "Before child " + bc + " of " + this + " is not in the list of all children!");
739-
}
740-
for (final DiffNode ac : afterChildren) {
741-
Assert.assertTrue(ac.afterParent == this, () -> "After child " + ac + " of " + this + " has another parent " + ac.afterParent + "!");
742-
Assert.assertTrue(childOrder.contains(ac), () -> "After child " + ac + " of " + this + " is not in the list of all children!");
743-
}
744727
for (final DiffNode c : childOrder) {
745-
Assert.assertTrue(beforeChildren.contains(c) || afterChildren.contains(c), () -> "Child " + c + " of " + this + " is neither a before not an after child!");
728+
Assert.assertTrue(isChild(c), () -> "Child " + c + " of " + this + " is neither a before nor an after child!");
729+
if (c.getBeforeParent() != null) {
730+
Assert.assertTrue(c.getBeforeParent().isBeforeChild(c), () -> "The beforeParent of " + c + " doesn't contain that node as child");
731+
}
732+
if (c.getAfterParent() != null) {
733+
Assert.assertTrue(c.getAfterParent().isAfterChild(c), () -> "The afterParent of " + c + " doesn't contain that node as child");
734+
}
746735
}
747736

748737
// a node with exactly one parent was edited

src/main/java/org/variantsync/diffdetective/diff/difftree/DiffTree.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public int computeSize() {
159159
}
160160

161161
public boolean isEmpty() {
162-
return root == null || root.getCardinality() == 0;
162+
return root == null || root.getTotalNumberOfChildren() == 0;
163163
}
164164

165165
/**

0 commit comments

Comments
 (0)