org.reactfx.util.SparseList Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of reactfx Show documentation
Show all versions of reactfx Show documentation
Reactive event streams for JavaFX
package org.reactfx.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import javafx.scene.control.IndexRange;
public final class SparseList {
private static interface Segment {
boolean isPresent();
int getLength();
int getPresentCount();
int getPresentCountBetween(int from, int to);
boolean isPresent(int index);
Optional get(int index);
E getOrThrow(int index);
void setOrThrow(int index, E elem);
List appendTo(List acc);
List appendRangeTo(List acc, int from, int to);
Segment subSegment(int from, int to);
boolean possiblyDestructiveAppend(Segment suffix);
default Stats getStatsBetween(int from, int to) {
return new Stats(to - from, getPresentCountBetween(from, to));
}
}
private static final class AbsentSegment implements Segment {
private int length;
AbsentSegment(int length) {
assert length > 0;
this.length = length;
}
@Override
public String toString() {
return "[Void x " + length + "]";
}
@Override
public boolean isPresent() {
return false;
}
@Override
public int getLength() {
return length;
}
@Override
public int getPresentCount() {
return 0;
}
@Override
public int getPresentCountBetween(int from, int to) {
return 0;
}
@Override
public boolean isPresent(int index) {
return false;
}
@Override
public Optional get(int index) {
return Optional.empty();
}
@Override
public E getOrThrow(int index) {
throw new NoSuchElementException();
}
@Override
public void setOrThrow(int index, E elem) {
throw new NoSuchElementException();
}
@Override
public List appendTo(List acc) {
return acc;
}
@Override
public List appendRangeTo(List acc, int from, int to) {
return acc;
}
@Override
public Segment subSegment(int from, int to) {
assert Lists.isValidRange(from, to, length);
return new AbsentSegment<>(to - from);
}
@Override
public boolean possiblyDestructiveAppend(Segment suffix) {
if(suffix.getPresentCount() == 0) {
length += suffix.getLength();
return true;
} else {
return false;
}
}
}
private static final class PresentSegment implements Segment {
private final List list;
public PresentSegment(Collection extends E> c) {
assert c.size() > 0;
list = new ArrayList<>(c);
}
@Override
public String toString() {
return "[" + list.size() + " items: " + list + "]";
}
@Override
public boolean isPresent() {
return true;
}
@Override
public int getLength() {
return list.size();
}
@Override
public int getPresentCount() {
return list.size();
}
@Override
public int getPresentCountBetween(int from, int to) {
assert Lists.isValidRange(from, to, getLength());
return to - from;
}
@Override
public boolean isPresent(int index) {
assert Lists.isValidIndex(index, getLength());
return true;
}
@Override
public Optional get(int index) {
return Optional.of(list.get(index));
}
@Override
public E getOrThrow(int index) {
return list.get(index);
}
@Override
public void setOrThrow(int index, E elem) {
list.set(index, elem);
}
@Override
public List appendTo(List acc) {
acc.addAll(list);
return acc;
}
@Override
public List appendRangeTo(List acc, int from, int to) {
acc.addAll(list.subList(from, to));
return acc;
}
@Override
public Segment subSegment(int from, int to) {
return new PresentSegment<>(list.subList(from, to));
}
@Override
public boolean possiblyDestructiveAppend(Segment suffix) {
if(suffix.getPresentCount() == suffix.getLength()) {
suffix.appendTo(list);
return true;
} else {
return false;
}
}
}
private static final class Stats {
private static final Stats ZERO = new Stats(0, 0);
final int size;
final int presentCount;
Stats(int size, int presentCount) {
assert size >= presentCount && presentCount >= 0;
this.size = size;
this.presentCount = presentCount;
}
int getSize() { return size; }
int getPresentCount() { return presentCount; }
}
private static final MapToMonoid, Stats> SEGMENT_STATS =
new MapToMonoid, Stats>() {
@Override
public Stats unit() {
return Stats.ZERO;
}
@Override
public Stats reduce(Stats left, Stats right) {
return new Stats(
left.size + right.size,
left.presentCount + right.presentCount);
}
@Override
public Stats apply(Segment> seg) {
return new Stats(seg.getLength(), seg.getPresentCount());
}
};
private static FingerTree, Stats> emptyTree() {
return FingerTree.empty(SEGMENT_STATS);
}
private FingerTree, Stats> tree;
public SparseList() {
tree = emptyTree();
}
public int size() {
return tree.getStats().size;
}
public int getPresentCount() {
return tree.getStats().presentCount;
}
public boolean isPresent(int index) {
return tree.get(Stats::getSize, index, Segment::isPresent);
}
public E getOrThrow(int index) {
return tree.get(Stats::getSize, index, Segment::getOrThrow);
}
public Optional get(int index) {
return tree.get(Stats::getSize, index, Segment::get);
}
public E getPresent(int presentIndex) {
return tree.get(
Stats::getPresentCount,
presentIndex,
Segment::getOrThrow);
}
public int getPresentCountBefore(int position) {
Lists.checkPosition(position, size());
return tree.getStatsBetween(
Stats::getSize,
0, position,
Segment::getStatsBetween).getPresentCount();
}
public int getPresentCountAfter(int position) {
return getPresentCount() - getPresentCountBefore(position);
}
public int getPresentCountBetween(int from, int to) {
Lists.checkRange(from, to, size());
return getPresentCountBefore(to) - getPresentCountBefore(from);
}
public int indexOfPresentItem(int presentIndex) {
Lists.checkIndex(presentIndex, getPresentCount());
return tree.locateProgressively(Stats::getPresentCount, presentIndex)
.map(this::locationToPosition);
}
public IndexRange getPresentItemsRange() {
if(getPresentCount() == 0) {
return new IndexRange(0, 0);
} else {
int lowerBound = tree.locateProgressively(Stats::getPresentCount, 0)
.map(this::locationToPosition);
int upperBound = tree.locateRegressively(Stats::getPresentCount, getPresentCount())
.map(this::locationToPosition);
return new IndexRange(lowerBound, upperBound);
}
}
private int locationToPosition(int major, int minor) {
return tree.getStatsBetween(0, major).size + minor;
}
public List collect() {
List acc = new ArrayList(getPresentCount());
return tree.fold(acc, (l, seg) -> seg.appendTo(l));
}
public List collect(int from, int to) {
List acc = new ArrayList(getPresentCountBetween(from, to));
return tree.foldBetween(
acc,
(l, seg) -> seg.appendTo(l),
Stats::getSize,
from,
to,
(l, seg, start, end) -> seg.appendRangeTo(l, start, end));
}
public void clear() {
tree = emptyTree();
}
public void remove(int index) {
remove(index, index + 1);
}
public void remove(int from, int to) {
Lists.checkRange(from, to, size());
if(from != to) {
spliceSegments(from, to, Collections.emptyList());
}
}
public void set(int index, E elem) {
tree.get(Stats::getSize, index).exec((seg, loc) -> {
if(seg.isPresent()) {
seg.setOrThrow(loc.minor, elem);
// changing an element does not affect stats, so we're done
} else {
splice(index, index + 1, Collections.singleton(elem));
}
});
}
public boolean setIfAbsent(int index, E elem) {
if(isPresent(index)) {
return false;
} else {
set(index, elem);
return true;
}
}
public void insert(int position, E elem) {
insertAll(position, Collections.singleton(elem));
}
public void insertAll(int position, Collection extends E> elems) {
if(elems.isEmpty()) {
return;
}
tree = tree.split(Stats::getSize, position).map((l, m, r) -> {
return join(l, m, new PresentSegment<>(elems), m, r);
});
}
public void insertVoid(int position, int length) {
if(length < 0) {
throw new IllegalArgumentException(
"length cannot be negative: " + length);
} else if(length == 0) {
return;
}
tree = tree.split(Stats::getSize, position).map((l, m, r) -> {
return join(l, m, new AbsentSegment<>(length), m, r);
});
}
public void splice(int from, int to, Collection extends E> elems) {
if(elems.isEmpty()) {
remove(from, to);
} else if(from == to) {
insertAll(from, elems);
} else {
spliceSegments(
from, to,
Collections.singletonList(new PresentSegment<>(elems)));
}
}
public void spliceByVoid(int from, int to, int length) {
if(length == 0) {
remove(from, to);
} else if(length < 0) {
throw new IllegalArgumentException(
"length cannot be negative: " + length);
} else if(from == to) {
insertVoid(from, length);
} else {
spliceSegments(
from, to,
Collections.singletonList(new AbsentSegment<>(length)));
}
}
private void spliceSegments(int from, int to, List> middle) {
tree = tree.split(Stats::getSize, from).map((left, lSuffix, r) -> {
return tree.split(Stats::getSize, to).map((l, rPrefix, right) -> {
return join(left, lSuffix, middle, rPrefix, right);
});
});
}
private FingerTree, Stats> join(
FingerTree, Stats> left,
Optional, Integer>> lSuffix,
Segment middle,
Optional, Integer>> rPrefix,
FingerTree, Stats> right) {
return join(
left, lSuffix,
Collections.singletonList(middle),
rPrefix, right);
}
private FingerTree, Stats> join(
FingerTree, Stats> left,
Optional, Integer>> lSuffix,
List> middle,
Optional, Integer>> rPrefix,
FingerTree, Stats> right) {
if(lSuffix.isPresent()) {
Segment lSeg = lSuffix.get()._1;
int lMax = lSuffix.get()._2;
left = left.append(lSeg.subSegment(0, lMax));
}
if(rPrefix.isPresent()) {
Segment rSeg = rPrefix.get()._1;
int rMin = rPrefix.get()._2;
right = right.prepend(rSeg.subSegment(rMin, rSeg.getLength()));
}
return join(left, middle, right);
}
private FingerTree, Stats> join(
FingerTree, Stats> left,
List> middle,
FingerTree, Stats> right) {
for(Segment seg: middle) {
left = append(left, seg);
}
return join(left, right);
}
private FingerTree, Stats> join(
FingerTree, Stats> left,
FingerTree, Stats> right) {
if(left.isEmpty()) {
return right;
} else if(right.isEmpty()) {
return left;
} else {
Segment lastLeft = left.getLeaf(left.getLeafCount() - 1);
Segment firstRight = right.getLeaf(0);
if(lastLeft.possiblyDestructiveAppend(firstRight)) {
left = left.updateLeaf(left.getLeafCount() - 1, lastLeft);
right = right.split(1)._2;
}
return left.join(right);
}
}
private FingerTree, Stats> append(
FingerTree, Stats> left,
Segment right) {
if(left.isEmpty()) {
return left.append(right);
} else {
Segment lastLeft = left.getLeaf(left.getLeafCount() - 1);
if(lastLeft.possiblyDestructiveAppend(right)) {
return left.updateLeaf(left.getLeafCount() - 1, lastLeft);
} else {
return left.append(right);
}
}
}
/**
* For testing only.
*/
int getDepth() {
return tree.getDepth();
}
/**
* For testing only.
* @return
*/
FingerTree, Stats> getTree() {
return tree;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy