Skip to content

49 add union find data structure - #62

Open
c-m-elliott wants to merge 185 commits into
mainfrom
49-add-union-find-data-structure
Open

49 add union find data structure#62
c-m-elliott wants to merge 185 commits into
mainfrom
49-add-union-find-data-structure

Conversation

@c-m-elliott

Copy link
Copy Markdown

So far, this includes the interfaces UnionFind, SortedUnionFind, PersistentUnionFind and PersistentSortedUnionFind as well as the abstract classes AbstractImmutableUnionFind and AbstractImmutableSortedUnionFind. It also contains a naive implementation of SortedUnionFind based on a HashMap of TreeSets (SortedTreeSetUnionFind). There is a set of tests (SortedUnionFindTest), but there seems to be some kind of problem regarding null values which is causing the JDK21 PackageSanityTest to fail. This in particular is something I'd appreciate input on. Thanks!

Colleen added 30 commits May 25, 2026 13:52
… mismatches that will be sorted out later on if I decide to stick with HashMaps
…) as static method problematic due to variable type

@baierd baierd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation currently lacks documentation in many places and has outdated/wrong documentation in other places.

Class structure needs to be improved urgently, as it blocks several default methods/concepts (e.g. Builder) from being implemented correctly, and also mixes multiple distinct implementations. In other cases, e.g. find(), there is no need for having multiple distinct implementations, yet there are multiple 1:1 duplicates.

Several immutable or persistent classes have SuppressWarnings("Immutable") annotations that hide real problems. The biggest of which is that the ImmutableUnionFind is not immutable, since the tree is not immutable. Modifying nodes modifies other copies of the same tree. This is also not covered by tests.

Furthermore, there are code duplicates in multiple places that need to be reduced (I did not mark all, but they should be obvious when working on the marked cases).

While tests cover default behavior, they miss type specifics quite often. But they too suffer from a structural problem, in that there is lots of duplicate code or test cases (ParentPointerTreeUnionFindByRankTest and ParentPointerTreeUnionFindBySizeTest are more or less direct duplicates), type inheritance is not used to test multiple union-finds at the same time etc. This would help with all test problems!
Please refactor the tests into abstract, generic, parameterized tests.
Also, don't modify test class variables in multiple places before using them (e.g. SortedUnionFindTest), as it becomes really hard to follow this, leading to tests easily being based on wrong assumptions.
Several tests for sorted variants test unsorted variants (e.g. PersistentParentPointerTreeUnionFind is tested instead of PersistentSortedParentPointerTreeUnionFind).
The tests for immutable versions don't test mutations of shared existing nodes (see above).
Tests are more or often then not undocumented. This is no problem for trivial tests, but more complex tests need to be documented.
Optimizations are effectively untested.

import org.sosy_lab.common.collect.union_find.ImmutableSortedParentPointerTreeUnionFind;
import org.sosy_lab.common.collect.union_find.ParentPointerTreeUnionFind.UnionType;

public class ImmutableParentPointerTreeUnionFindSortednessTest {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name can be simplified:

Suggested change
public class ImmutableParentPointerTreeUnionFindSortednessTest {
public class ImmutableSortedUnionFindTest {

Possibly even more, depending on whether you test its immutability in this class or not (which would be totally legit).
Specify type information should only be included if you actually test its specifics.
As far as i can see, this only tests sortedness, hence neither immutability nor the implementation specifics of the UnionFind (i.e. that it is a ParentPointerTree or Tree) matter here.

}

@Test
public void testGetAllSubsets_elementsAddedInAscendingOrder_remainSorted() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our Java naming convention (see Google style guide) uses camel case, not snake case.
Also, tests should add the suffix *Test always at the end of their name.

This should be considered in the entire MR.

// tree nodes are not immutable but only used internally and never mutated after creation
// immutable tree nodes would make conversion during build() difficult and time-consuming
@SuppressWarnings("Immutable")
private final ImmutableMap<T, AbstractTreeNode<T>> allNodes;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a immutable UnionFind implementation. While your Map is immutable, the nodes, and therefore the tree, are not immutable. Modifying a previous copy also modifies this copy and vice versa.

@Override
public Collection<? extends Set<T>> getAllSubsets() {

Map<T, Set<T>> allSubsets = new HashMap<>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a immutable class. Why not return a immutable collection here?

super(pUnionType);
}

public static <T> AbstractImmutableParentPointerTreeBuilder<T> getBuilder(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static constructor method for Builder should be part of the public interface of the immutable classes, and this method should be moved there.

The idea of a Builder is that you automatically get the correct class when requesting it through the static constructor method. But here you hard-coded them, making this impossible. A user needs to know the details of the class at all points when programming, making this inconvenient.


Preconditions.checkNotNull(pE);

List<AbstractTreeNode<T>> toBeCompressed = new ArrayList<>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use findNode()?

}

@CanIgnoreReturnValue
public AbstractImmutableParentPointerTreeBuilder<T> add(Set<T> pSet) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing documentation.

What happens if this set contains elements that are already in the union-find?

* @param <T> The type of values.
*/
@Immutable(containerOf = "T")
public interface PersistentSortedUnionFind<T extends Comparable<T>> extends SortedUnionFind<T> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a subtype of PersistentUnionFind.

Also, the type bounds are restrictive and disallow naturally ordered elements if the ordering is defined in a superclass. Please change this to T extends Comparable<? super T>.


public class ImmutableParentPointerTreeUnionFindTest {

private final int[] simpleUnionArgs = new int[] {0, 1};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use the values? Why does this exist at all?

// union-find not immutable but only used internally and never mutated passed outward
// build() returns an immutable union-find that contains a copy of this union-find's map
@SuppressWarnings("Immutable")
final ParentPointerTreeUnionFind<T> unionFind;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing privacy modifier.

@baierd baierd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Furthermore, the node implementations do not define equals() and hashcode(). This is not wrong, since nodes are not re-introduced in the implementations. But there are two problems due to this:

  • the code is fragile, since expression like this while (!node.equals(parent)) depend on reference equality implicitly now. Making this explicit helps code readability and future developers and maintainers. Switching to explicit reference equality should be considered.
  • sorted implementations have a equals/compareTo mismatch. Example:
new BigInteger("1.0").equals(new BigInteger("1.00"))   // false
new BigInteger("1.0").compareTo(new BigInteger("1.00")) // 0, i.e. equal

I am currently unsure whether this can lead to problems in the current implementation.
Defining equals() and hashcode(), avoids these problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

Add Union-Find Data-Structure

2 participants