Skip to content

Add a (copy-)efficient Persistent Stack Implementation - #65

Open
baierd wants to merge 23 commits into
mainfrom
feature/persistent-stack
Open

baierd wants to merge 23 commits into
mainfrom
feature/persistent-stack

Conversation

@baierd

@baierd baierd commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

We have a PersistentStack implementation in CPAchecker, but not in this library. It makes sense to move it here.

While doing so, the following was added:

  • a interface for persistent stacks PersistentStack.
  • a test class PersistentLinkedStackTest.
  • a extended PersistentLinkedStack implementation that provides serialization through a proxy and an iterator + proper documentation.

Note: the current state is a hybrid of our PersistentStack in CPAchecker and PersistentLinkedList in this repo, but extended using LLM generated code. I reviewed the changes done by the LLM before opening this PR. I am unsure about the serialization though, so input would be appreciated.

@baierd baierd self-assigned this Sep 7, 2026
Comment thread src/org/sosy_lab/common/collect/PersistentStack.java Outdated
Comment thread src/org/sosy_lab/common/collect/PersistentStack.java Outdated
import java.util.NoSuchElementException;
import org.junit.Test;

public class PersistentLinkedStackTest {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add tests based on Guava's testlib like we have for PathCopyingPersistentTreeMap. These are much more comprehensive than what we would think of.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I tried following it and refactored the tests. Could you please take a look whether this is sufficient?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think testing the result of copyToList() is useful. This would test the creation of the stack, the copyToList() call and then all the features of the returned list implementation and its views. But it will not test any of the other PersistentLinkedStack features and methods.

Comment thread src/org/sosy_lab/common/collect/PersistentStack.java Outdated
Comment thread src/org/sosy_lab/common/collect/PersistentLinkedStack.java Outdated
Comment thread src/org/sosy_lab/common/collect/PersistentLinkedStack.java Outdated
Comment thread src/org/sosy_lab/common/collect/PersistentLinkedStack.java
Comment thread src/org/sosy_lab/common/collect/PersistentLinkedStack.java
@PhilippWendler

Copy link
Copy Markdown
Member

We can consider adding some bulk operations, for example a method to get the bottom n elements, similar to sublist(), and maybe more.

@baierd

baierd commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

We can consider adding some bulk operations, for example a method to get the bottom n elements, similar to sublist(), and maybe more.

I added a implementation for this.

…istentStack impl for hashcode and equals tests
@SuppressWarnings("NoFunctionalReturnType")
public static <T> Collector<T, ?, PersistentLinkedStack<T>> toPersistentLinkedStack() {
return Collectors.collectingAndThen(
ImmutableList.<T>toImmutableList(), PersistentLinkedStack::copyOf);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we want to first copy everything into a list and then into a stack. That is what users could easily do without our collector.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I improved it some more, but we can't fully eliminate temporary buffering other stacks that are combined.


@Override
public ImmutableList<T> copyToList() {
return ImmutableList.copyOf(asTopDownIterable()).reverse();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We could use a Builder with the known size to make this more efficient.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Actually, we should keep the code here as is, but it is a good argument for asTopDownIterable() to return a Collection, because then copyof() will automatically use the correct size.

* Returns an unmodifiable top-to-bottom view in O(1) time. Each iterator traverses this stack
* version independently.
*/
Iterable<T> asTopDownIterable();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If we can return a Collection here it could be more convenient for callers. In principle SequencedCollection would be even better semantically, but it only adds reversed(), which we can hardly support, and some mutation methods. So there is little point in it from the API point of view.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We now return a view that is a unmodifiable Collection.

Comment on lines +285 to +294
@Var PersistentLinkedStack<Object> stack = PersistentLinkedStack.of();
// The serialized order is top-to-bottom; push in the opposite direction.
for (@Var int index = serializedValues.length - 1; index >= 0; index--) {
@Nullable Object value = serializedValues[index];
if (value == null) {
throw new InvalidObjectException("Stack values must not contain null");
}
stack = stack.pushAndCopy(value);
}
return stack;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We can replaced this with something based on copyOf(Arrays.asList(values).reversed()).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. I did not do it in one line so that comments explain what's going on more easily (and a TODO for Java 21).

import java.util.NoSuchElementException;
import org.junit.Test;

public class PersistentLinkedStackTest {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think testing the result of copyToList() is useful. This would test the creation of the stack, the copyToList() call and then all the features of the returned list implementation and its views. But it will not test any of the other PersistentLinkedStack features and methods.

…ering all input stacks into a list, instead directly construct a new stack from them, unless combining 2 LinkedStacks, since then we need to buffer the elements of one stack
…stack, not the list copy of it, and extend with more cases that were not covered

This branch has not been deployed

No deployments
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.

2 participants