CrisprList.java
package org.klojang.collections;
import org.klojang.check.Check;
import org.klojang.check.Tag;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import static java.util.Collections.singletonList;
import static org.klojang.check.CommonChecks.*;
import static org.klojang.check.CommonExceptions.noSuchElement;
import static org.klojang.util.MathMethods.divUp;
/**
* A doubly-linked list, much like the JDK's {@link LinkedList}, but exclusively
* focused on list manipulation while disregarding the queue-like aspects of linked
* lists. Functionally, this class is exactly equivalent to {@link WiredList}. The
* difference lies in how the two classes deal with removed elements and list
* segments. {@code WiredList} (like {@code LinkedList}) meticulously nullifies
* everything that can be nullified about them. {@code CrisprList} just leaves them
* floating in deep space (the heap). In principle the first strategy should make it
* easier for the garbage collector to detect unreachable chunks of heap memory. The
* extra administration can itself become non-negligible, however, when removing
* large list segments, or when repetitively removing medium-sized list segments.
*
* @param <E> the type of the elements in the list
* @author Ayco Holleman
* @see WiredList
*/
public final class CrisprList<E> extends AbstractLinkedList<E> {
//
//
//
// ======================================================= //
// ================ [ Iterator classes ] ================ //
// ======================================================= //
//
//
//
final class CFwdWiredIterator extends
ForwardWiredIterator {
private CFwdWiredIterator() {
super();
}
private CFwdWiredIterator(Node<E> curr) {
super(curr);
}
@Override
public void remove() {
Check.that(curr).isNot(sameAs(), beforeHead, callNextFirst());
Check.that(sz).isNot(zero(), emptyList());
if (curr == head) {
unlink(curr);
curr = beforeHead = justBeforeHead();
} else {
Check.that(curr = curr.prev)
.is(notNull(), concurrentModification())
.then(prev -> unlink(prev.next));
}
}
@Override
WiredIterator<E> getReverseWiredIterator(Node<E> curr) {
return new CRevWiredIterator(curr);
}
}
final class CRevWiredIterator extends ReverseWiredIterator {
private CRevWiredIterator() {
super();
}
private CRevWiredIterator(Node<E> curr) {
super(curr);
}
@Override
public void remove() {
Check.that(curr).isNot(sameAs(), afterTail, callNextFirst());
Check.that(sz).isNot(zero(), emptyList());
if (curr == tail) {
unlink(curr);
curr = afterTail = justAfterTail();
} else {
Check.that(curr = curr.next)
.is(notNull(), concurrentModification())
.then(next -> unlink(next.prev));
}
}
@Override
WiredIterator<E> getForwardWiredIterator(Node<E> curr) {
return new CFwdWiredIterator(curr);
}
}
//
//
//
// ======================================================= //
// ==================== [ CrisprList ] ==================== //
// ======================================================= //
//
//
//
/**
* Returns a new, empty {@code CrisprList}. Note that, although the {@code of(..)}
* methods look like the {@code List.of(...)} methods, they return ordinary,
* mutable, {@code null}-accepting {@code CrisprList} instances.
*
* @param <E> the type of the elements in the list
* @return a new, empty {@code CrisprList}
*/
public static <E> CrisprList<E> of() {
return new CrisprList<>();
}
/**
* Returns a new {@code CrisprList} containing the specified element.
*
* @param e the element
* @param <E> the type of the elements in the list
* @return a new {@code CrisprList} containing the specified elements
*/
public static <E> CrisprList<E> of(E e) {
return new CrisprList<E>().append(e);
}
/**
* Returns a new {@code CrisprList} containing the specified elements.
*
* @param e0 the first element in the list
* @param e1 the second element in the list
* @param <E> the type of the elements in the list
* @return a new {@code CrisprList} containing the specified elements
*/
public static <E> CrisprList<E> of(E e0, E e1) {
return new CrisprList<E>().append(e0).append(e1);
}
/**
* Returns a new {@code CrisprList} containing the specified elements.
*
* @param e0 the first element in the list
* @param e1 the second element in the list
* @param e2 the third element in the list
* @param moreElems more elements to include in the list
* @param <E> the type of the elements in the list
* @return a new {@code CrisprList} containing the specified elements
*/
@SafeVarargs
public static <E> CrisprList<E> of(E e0, E e1, E e2, E... moreElems) {
Check.notNull(moreElems, Tag.ARRAY);
var cl = new CrisprList<E>();
Node<E> head = new Node<>(e0);
Node<E> tail = head;
tail = new Node<>(tail, e1);
tail = new Node<>(tail, e2);
for (E e : moreElems) {
tail = new Node<>(tail, e);
}
cl.head = head;
cl.tail = tail;
cl.sz = moreElems.length + 3;
return cl;
}
/**
* Returns a new {@code CrisprList} containing the specified elements.
*
* @param elements the elements to add to the list
* @param <E> the type of the elements in the list
* @return a new {@code CrisprList} containing the specified elements
*/
public static <E> CrisprList<E> ofElements(E[] elements) {
Check.notNull(elements, Tag.ARRAY);
var cl = new CrisprList<E>();
if (elements.length != 0) {
Node<E> head = new Node<>(elements[0]);
Node<E> tail = head;
for (int i = 1; i < elements.length; ++i) {
tail = new Node<>(tail, elements[i]);
}
cl.head = head;
cl.tail = tail;
cl.sz = elements.length;
}
return cl;
}
/**
* Concatenates the provided {@code CrisprList} instances. This is a destructive
* operation for the {@code CrisprList} instances in the provided {@code List}.
* They will be empty when the method returns.
*
* @param lists the {@code CrisprList} instances to concatenate
* @param <E> the type of the elements in the list
* @return a new {@code CrisprList} containing the elements in the individual
* {@code CrisprList} instances
* @see #attach(CrisprList)
* @see #group(List)
*/
public static <E> CrisprList<E> join(List<CrisprList<E>> lists) {
CrisprList<E> cl = new CrisprList<>();
Check.notNull(lists).ok().forEach(cl::attach);
return cl;
}
/**
* Creates a new, empty {@code CrisprList}.
*/
public CrisprList() {}
/**
* Creates a new {@code CrisprList} containing the elements in the specified
* {@code Collection}.
*
* @param c the collection whose elements to copy to this {@code CrisprList}
*/
public CrisprList(Collection<? extends E> c) {
addAll(0, c);
}
@SuppressWarnings({"unchecked"})
private CrisprList(Chain chain) {
makeHead(chain.head);
makeTail(chain.tail);
sz = chain.length;
}
/**
* Overwrites the elements at, and following the specified index with the provided
* values. For linked lists this is more efficient than setting each of the
* elements individually, especially if the elements are somewhere in the middle of
* the lists. The number of values must not exceed {@code list.size() - index}.
*
* @param index the index of the first element the set
* @param e0 the first value to write
* @param e1 the second value to write
* @param moreElems more values to write
* @return this instance
*/
@SuppressWarnings("unchecked")
public CrisprList<E> set(int index, E e0, E e1, E... moreElems) {
set0(index, e0, e1, moreElems);
return this;
}
/**
* Sets the element at the specified index to the specified value <i>if</i> the
* original value passes the specified test. This method mitigates the relatively
* large cost of index-based retrieval with linked lists, which would double if you
* had to execute a get-compare-set sequence.
*
* @param index the index of the element to set
* @param condition The test that the original value has to pass in order to be
* replaced with the new value. The original value is passed to the predicate's
* {@code test} method.
* @param value The value to set
* @return The original value
*/
public E setIf(int index, Predicate<? super E> condition, E value) {
return setIf0(index, condition, value);
}
/**
* Removes the element at the specified position in this list.
*
* @param index the index of the element to be removed
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
*/
@Override
public E remove(int index) {
var x = node(index);
E val = x.val;
unlink(x);
return val;
}
/**
* Removes the first occurrence of the specified element from this list, if it is
* present.
*
* @param o element to be removed from this list, if present
* @return {@code true} if this list contained the specified element
*/
@Override
public boolean remove(Object o) {
if (o == null) {
for (var x = head; x != null; ) {
if (x.val == null) {
unlink(x);
return true;
}
x = x.next;
}
} else {
for (var x = head; x != null; ) {
if (o.equals(x.val)) {
unlink(x);
return true;
}
x = x.next;
}
}
return false;
}
/**
* Removes all elements of this collection that satisfy the given predicate.
*
* @param filter a predicate which returns {@code true} for elements to be
* removed
* @return {@code true} if any elements were removed
*/
@Override
public boolean removeIf(Predicate<? super E> filter) {
Check.notNull(filter, "filter");
int size = sz;
for (var x = head; x != null; ) {
if (filter.test(x.val)) {
var next = x.next;
unlink(x);
x = next;
} else {
x = x.next;
}
}
return size != this.sz;
}
/**
* Returns the first element of the list. A {@link NoSuchElementException} is
* thrown if the list is empty.
*
* @return the first element of the list
*/
public E first() {
Check.that(sz).isNot(zero(), noSuchElement());
return head.val;
}
/**
* Returns the last element of the list. A {@link NoSuchElementException} is thrown
* if the list is empty.
*
* @return the last element of the list
*/
public E last() {
Check.that(sz).isNot(zero(), noSuchElement());
return tail.val;
}
/**
* Inserts the specified value at the start of the list, right-shifting the
* original elements.
*
* @param value The value to insert
* @return this instance
*/
public CrisprList<E> prepend(E value) {
prepend0(value);
return this;
}
/**
* Inserts the specified collection at the start of the list, right-shifting the
* original elements.
*
* @param values The values to prepend to the list
* @return this instance
*/
public CrisprList<E> prependAll(Collection<? extends E> values) {
Check.notNull(values, Tag.COLLECTION);
if (!values.isEmpty()) {
insert(0, Chain.of(values));
}
return this;
}
/**
* Appends the specified value to the end of the list. Equivalent to
* {@link #add(Object) add(value)}.
*
* @param value The value to append to the list
* @return this instance
*/
public CrisprList<E> append(E value) {
add(value);
return this;
}
/**
* Appends the specified collection to this {@code CrisprList}.
*
* @param values The values to append to the list
* @return this instance
* @see #addAll(Collection)
* @see #attach(CrisprList)
*/
public CrisprList<E> appendAll(Collection<? extends E> values) {
Check.notNull(values, Tag.COLLECTION);
if (!values.isEmpty()) {
insert(sz, Chain.of(values));
}
return this;
}
/**
* Inserts a value into the list. All elements <i>at and following</i> the
* specified index will be right-shifted. The index value must be {@code >= 0} and
* {@code <= list.size()}. Specifying 0 (zero) is equivalent to
* {@link #prepend(Object) prepend(value)}. Specifying {@code list.size()} is
* equivalent to {@link #append(Object) append(value} and
* {@link #add(Object) add(value)}.
*
* @param index the index at which to insert the value
* @param value the value
* @return this instance
*/
public CrisprList<E> insert(int index, E value) {
checkInclusive(index);
insert(index, new Node<>(value));
return this;
}
/**
* Inserts the specified collection at the specified index, right-shifting the
* elements at and following the index.
*
* @param index the index at which to insert the collection
* @param values The collection to insert into the list
* @return this instance
* @see #addAll(int, Collection)
*/
public CrisprList<E> insertAll(int index, Collection<? extends E> values) {
checkInclusive(index);
Check.notNull(values, Tag.COLLECTION);
if (!values.isEmpty()) {
insert(index, Chain.of(values));
}
return this;
}
/**
* Removes the first element from the list, left-shifting the remaining elements. A
* {@link NoSuchElementException} is thrown if the list is empty.
*
* @return this instance
*/
public E removeFirst() {
Check.that(sz).isNot(zero(), noSuchElement());
E val = head.val;
unlink(head);
return val;
}
/**
* Removes the last element from the list. A {@link NoSuchElementException} is
* thrown if the list is empty.
*
* @return this instance
*/
public E removeLast() {
Check.that(sz).isNot(zero(), noSuchElement());
E val = tail.val;
unlink(tail);
return val;
}
/**
* Replaces each element of this list with the result of applying the operator to
* that element. Errors or runtime exceptions thrown by the operator are relayed to
* the caller.
*
* @param operator the operator to apply to each element
* @throws UnsupportedOperationException if this list is unmodifiable.
* Implementations may throw this exception if an element cannot be replaced or
* if, in general, modification is not supported
*/
@Override
public void replaceAll(UnaryOperator<E> operator) {
if (sz != 0) {
for (var x = head; ; x = x.next) {
x.val = operator.apply(x.val);
if (x == tail) {
break;
}
}
}
}
/**
* Replaces the segment between {@code fromIndex} and {@code toIndex} with the
* elements in the specified collection.
*
* @param fromIndex the start index (inclusive) of the segment to replace
* @param toIndex the end index (exclusive) of
* @param values The values to replace the segment with
* @return this instance
* @see #replace(int, int, CrisprList)
*/
public CrisprList<E> replaceAll(int fromIndex,
int toIndex,
Collection<? extends E> values) {
int len = Check.fromTo(this, fromIndex, toIndex);
Check.notNull(values, Tag.COLLECTION);
if (len == 0) {
if (!values.isEmpty()) {
insert(fromIndex, Chain.of(values));
}
} else if (len < values.size()) {
unlink(fromIndex, toIndex);
if (!values.isEmpty()) {
insert(fromIndex, Chain.of(values));
}
} else {
var node = nodeAt(fromIndex);
for (E e : values) {
node.val = e;
node = node.next;
}
if (len > values.size()) {
var end = nodeAfter(node, fromIndex + values.size(), toIndex - 1);
var chain = new Chain(node, end, len - values.size());
unlink(chain);
}
}
return this;
}
/**
* Replaces the segment between {@code fromIndex} and {@code toIndex} with the
* elements in the specified list. This method is functionally equivalent to
* {@link #replaceAll(int, int, Collection) replace}, but more efficient. However,
* it will leave the specified list empty. If you don't want this to happen, use
* {@code replaceAll}.
*
* @param fromIndex the start index (inclusive) of the segment to replace
* @param toIndex the end index (exclusive) of
* @param other the values to replace the segment with
* @return this instance
*/
public CrisprList<E> replace(int fromIndex,
int toIndex,
CrisprList<? extends E> other) {
int len = Check.fromTo(this, fromIndex, toIndex);
Check.notNull(other, className).isNot(sameAs(), this, autoEmbedNotAllowed());
if (len != 0) {
unlink(fromIndex, toIndex);
}
if (!other.isEmpty()) {
insert(fromIndex, other.unlink(0, other.sz));
}
return this;
}
/**
* Returns a copy of this {@code CrisprList}. Changes made to the copy will not
* propagate to this instance, and vice versa.
*
* @return a deep copy of this {@code CrisprList}
*/
public CrisprList<E> copy() {
return sz > 0 ? new CrisprList<>(Chain.copyOf(head, sz)) : CrisprList.of();
}
/**
* Returns a copy of the specified segment. Changes made to the copy will not
* propagate to this instance, and vice versa.
*
* @param fromIndex the start index (inclusive) of the segment
* @param toIndex the end index (exclusive) of the segment
* @return a deep copy of the specified segment
*/
public CrisprList<E> copy(int fromIndex, int toIndex) {
int len = Check.fromTo(this, fromIndex, toIndex);
return len > 0
? new CrisprList<>(Chain.copyOf(nodeAt(fromIndex), len))
: CrisprList.of();
}
/**
* Shrinks the list to between the specified boundaries. If {@code toIndex} is
* equal to {@code fromIndex}, the list will, in effect, be
* {@link #clear() cleared}.
*
* @param fromIndex the index (inclusive) of the new start of the list
* @param toIndex the index (exclusive) of the new end of the list
* @return this instance
*/
public CrisprList<E> shrink(int fromIndex, int toIndex) {
int len = Check.fromTo(this, fromIndex, toIndex);
if (len == 0) {
clear();
} else if (len != sz) {
Node<E> x = nodeAt(fromIndex);
Node<E> y = nodeAfter(x, fromIndex, toIndex - 1);
makeHead(x);
makeTail(y);
}
return this;
}
/**
* Removes and returns a segment from the list.
*
* @param fromIndex the start index (inclusive) of the segment to delete
* @param toIndex the end index (exclusive) of the segment to delete
* @return the deleted segment
*/
public CrisprList<E> cut(int fromIndex, int toIndex) {
if (Check.fromTo(this, fromIndex, toIndex) > 0) {
return new CrisprList<>(unlink(fromIndex, toIndex));
}
return CrisprList.of();
}
/**
* Inserts this list into the specified list at the specified position. Equivalent
* to {@link #embed(int, CrisprList) into.embed(index, this)}. This list will be
* empty afterwards. Note that this method does not return <i>list</i> list but the
* paste-into list.
*
* @param into the list into which to insert this list
* @param index the index at which to insert this list
* @return the specified list
*/
public CrisprList<? super E> paste(CrisprList<? super E> into, int index) {
return into.embed(index, this);
}
/**
* Embeds the specified list in this list. This method is functionally equivalent
* to {@link #insertAll(int, Collection) insertAll} and
* {@link #addAll(int, Collection) addAll}, but more efficient. However, it is a
* destructive operation for the provided list. It will be empty afterwards. If you
* don't want this to happen, use {@code insertAll} or {@code addAll}.
*
* @param index the index at which to embed the list
* @param other the list to embed
* @return this instance
*/
public CrisprList<E> embed(int index, CrisprList<? extends E> other) {
checkInclusive(index);
Check.notNull(other, className).isNot(sameAs(), this, autoEmbedNotAllowed());
if (!other.isEmpty()) {
insert(index, new Chain(other.head, other.tail, other.sz));
other.clear();
}
return this;
}
/**
* Exchanges list segments between this list and the specified list.
*
* @param myFromIndex the start index (inclusive) of the segment within this
* list
* @param myToIndex the end index (exclusive) of the segment within this list
* @param other the list to exchange segments with
* @param itsFromIndex the start index (inclusive) of the segment within the
* other list
* @param itsToIndex the end index (exclusive) of the segment within the other
* list
* @return this instance
*/
public CrisprList<E> exchange(int myFromIndex,
int myToIndex,
CrisprList<E> other,
int itsFromIndex,
int itsToIndex) {
int len0 = Check.fromTo(this, myFromIndex, myToIndex);
int len1 = Check.fromTo(other, itsFromIndex, itsToIndex);
Check.that(other).isNot(sameAs(), this, autoEmbedNotAllowed());
if (len0 == 0) {
if (len1 != 0) {
insert(myFromIndex, other.unlink(itsFromIndex, itsToIndex));
}
return this;
} else if (len1 == 0) {
other.insert(itsFromIndex, unlink(myFromIndex, myToIndex));
return this;
}
// segment boundary nodes
var seg0L = nodeAt(myFromIndex);
var seg0R = nodeAfter(seg0L, myFromIndex, myToIndex - 1);
var seg1L = other.nodeAt(itsFromIndex);
var seg1R = other.nodeAfter(seg1L, itsFromIndex, itsToIndex - 1);
// attach-to nodes
var att0L = seg0L.prev;
var att0R = seg0R.next;
var att1L = seg1L.prev;
var att1R = seg1R.next;
if (att0L == null) {
makeHead(seg1L);
} else {
join(att0L, seg1L);
}
if (att0R == null) {
makeTail(seg1R);
} else {
join(seg1R, att0R);
}
if (att1L == null) {
other.makeHead(seg0L);
} else {
join(att1L, seg0L);
}
if (att1R == null) {
other.makeTail(seg0R);
} else {
join(seg0R, att1R);
}
sz = sz - len0 + len1;
other.sz = other.sz - len1 + len0;
return this;
}
/**
* Removes a segment from the specified list and embeds it in this list.
*
* @param myIndex the index at which to insert segment
* @param other the list to remove the segment from
* @param itsFromIndex the start index of the segment (inclusive)
* @param itsToIndex the end index of the segment (exclusive)
* @return this instance
*/
public CrisprList<E> embed(int myIndex,
CrisprList<? extends E> other,
int itsFromIndex,
int itsToIndex) {
checkInclusive(myIndex);
int len = Check.fromTo(other, itsFromIndex, itsToIndex);
Check.that(other).isNot(sameAs(), this, autoEmbedNotAllowed());
if (len > 0) {
insert(myIndex, other.unlink(itsFromIndex, itsToIndex));
}
return this;
}
/**
* Swaps the two list segments defined by the specified boundary indexes. In other
* words, once this method returns, the first list segment will start where the
* second list segment originally started, and vice versa. The list segments must
* not overlap and they must both contain at least one element. They need not have
* the same number of elements, though.
*
* @param from1 the from-index (inclusive) of the first segment
* @param to1 the to-index (exclusive) of the first segment
* @param from2 the from-index (inclusive) of the second segment
* @param to2 the to-index (exclusive) of the second segment
* @return this {@code WiredList}
*/
public CrisprList<E> swap(int from1, int to1, int from2, int to2) {
swap0(from1, to1, from2, to2);
return this;
}
/**
* Appends the specified list to this list. This method is functionally equivalent
* {@link #appendAll(Collection) appendAll} and {@link #addAll(Collection) addAll},
* but more efficient. However, it will leave the specified list empty. If you
* don't want this to happen, use {@code appendAll} or {@code addAll}.
*
* @param other the list to embed
* @return this instance
* @see #join(List)
*/
public CrisprList<E> attach(CrisprList<? extends E> other) {
Check.notNull(other).isNot(sameAs(), this, autoEmbedNotAllowed());
if (other.sz != 0) {
attach0(other);
}
return this;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private void attach0(CrisprList other) {
if (sz == 0) {
head = other.head;
} else {
join(tail, other.head);
}
tail = other.tail;
sz += other.sz;
other.clear();
}
/**
* Reorders the elements according to the specified criteria. The elements
* satisfying the first criterion (if any) will come first in the list, the
* elements satisfying the second criterion (if any) will come second, etc. The
* elements that did not satisfy any criterion will come last in the list.
*
* @param criteria the criteria used to group the elements
* @return this instance
*/
public CrisprList<E> defragment(List<Predicate<? super E>> criteria) {
return defragment(true, criteria);
}
/**
* Reorders the elements according to the specified criteria. The elements
* satisfying the first criterion (if any) will come first in the list, the
* elements satisfying the second criterion (if any) will come second, etc.
*
* @param keepRemainder whether to keep the elements that did not satisfy any
* criterion, and move them to the end of the list
* @param criteria the criteria used to group the elements
* @return this instance
*/
@SuppressWarnings({"rawtypes"})
public CrisprList<E> defragment(boolean keepRemainder,
List<Predicate<? super E>> criteria) {
Check.that(criteria).is(deepNotEmpty());
List<CrisprList<E>> groups = createGroups(criteria);
Chain rest = new Chain(head, tail, sz);
sz = 0;
for (CrisprList cl : groups) {
if (!cl.isEmpty()) {
attach0(cl);
}
}
if (keepRemainder && rest.length != 0) {
insert(sz, rest);
}
return this;
}
/**
* Groups the elements in those that do, and elements that do not satisfy the
* specified criterion.
*
* @param criterion the test to submit the list elements to
* @param <L0> the type of the lists within the returned list
* @param <L1> the type of returned list
* @return a list containing two lists representing the two groups
* @see #group(List)
*/
public <L0 extends List<E>, L1 extends List<L0>> L1 group(Predicate<? super E> criterion) {
return group(singletonList(criterion));
}
/**
* <p>
* Groups the elements according to the provided criteria. The return value is a
* list-of-lists where each inner {@code List} constitutes a group. <i>This</i>
* {@code CrisprList} is left with all elements that did not satisfy any criterion,
* and it will be the last element in the returned list-of-lists. In other words,
* the size of the returned list-of-lists is the number of criteria plus one. You
* can use the {@link #join(List) join} method to create a single "defragmented"
* list again.
* <p>
* Elements will never be placed in more than one group. As soon as an element is
* found to satisfy a criterion it is placed in the corresponding group and the
* remaining criteria are skipped.
* <p>
* The runtime type of the returned list-of-lists
* {@code CrisprList<CrisprList<E>>}. If you don't care about the exact type of the
* returned {@code List}, you can simply write:
*
* <blockquote><pre>{@code
* CrisprList<String> cl = ...;
* List<List<String>> groups = cl.group(...);
* }</pre></blockquote>
* <p>
* Otherwise use any combination of {@code List} and {@code CrisprList} that suits
* your needs.
*
* @param criteria the criteria used to group the elements
* @param <L0> the type of the lists within the returned list
* @param <L1> the type of returned list
* @return a list of element groups
*/
@SuppressWarnings({"unchecked"})
public <L0 extends List<E>, L1 extends List<L0>> L1 group(List<Predicate<? super E>> criteria) {
Check.that(criteria).is(deepNotEmpty());
List<CrisprList<E>> groups = createGroups(criteria);
var result = new CrisprList<>(groups);
result.add(this);
return (L1) result;
}
@SuppressWarnings({"rawtypes"})
private CrisprList<CrisprList<E>> createGroups(List<Predicate<? super E>> criteria) {
CrisprList<CrisprList<E>> groups = new CrisprList<>();
criteria.forEach(c -> groups.append(new CrisprList<>()));
for (Node<E> node = head; node != null; ) {
var next = node.next;
for (int i = 0; i < criteria.size(); ++i) {
if (criteria.get(i).test(node.val)) {
unlink(node);
CrisprList cl = groups.get(i);
cl.insert(cl.size(), node);
break;
}
}
node = next;
}
return groups;
}
/**
* Splits this {@code CrisprList} into multiple {@code CrisprList} instances of the
* specified size. The partitions are chopped off from the {@code CrisprList} and
* then placed in a separate {@code CrisprList}. The last element in the returned
* list-of-lists is <i>this</i> {@code CrisprList}, and it will now contain at most
* {@code size} elements.
* <p>
* The runtime type of the return value is {@code CrisprList<CrisprList<E>>}. If
* you don't care about the exact type of the returned {@code List}, you can simply
* write:
*
* <blockquote><pre>{@code
* CrisprList<String> cl = ...;
* List<List<String>> partitions = cl.partition(3);
* }</pre></blockquote>
* <p>
* Otherwise use any combination of {@code List} and {@code CrisprList} that suits
* your needs.
*
* @param size The desired size of the partitions
* @param <L0> the type of the lists within the returned list
* @param <L1> the type of returned list
* @return a list of {@code CrisprList} instances of the specified size
*/
@SuppressWarnings("unchecked")
public <L0 extends List<E>, L1 extends List<L0>> L1 partition(int size) {
Check.that(size).is(gt(), 0);
CrisprList<CrisprList<E>> partitions = new CrisprList<>();
while (sz > size) {
Chain chain = new Chain(head, nodeAt(size - 1), size);
partitions.append(new CrisprList<>(unlink(chain)));
}
partitions.append(this);
return (L1) partitions;
}
/**
* Splits this {@code CrisprList} into the specified number of equally-sized
* {@code CrisprList} instances. The last element in the returned list-of-lists is
* this {@code CrisprList}, and it will contain the remainder of the elements after
* dividing the list size by {@code numPartitions}. The runtime type of the return
* value is {@code CrisprList<CrisprList<E>>}. If you don't care about the exact
* type of the returned {@code List}, you can simply write:
*
* <blockquote><pre>{@code
* CrisprList<String> cl = ...;
* List<List<String>> partitions = cl.split(3);
* }</pre></blockquote>
* <p>
* Otherwise use any combination of {@code List} and {@code CrisprList} that suits
* your needs.
*
* @param numPartitions The number of {@code CrisprList} instances to split
* this {@code CrisprList} into
* @param <L0> the type of the lists within the returned list
* @param <L1> the type of returned list
* @return a list containing the specified number of {@code CrisprList} instances
*/
public <L0 extends List<E>, L1 extends List<L0>> L1 split(int numPartitions) {
Check.that(numPartitions).is(gt(), 0);
return partition(divUp(sz, numPartitions));
}
/**
* Removes and returns a segment from the start of the list. The segment will
* include all elements up to (but not including) the first element that does not
* satisfy the specified condition. In other words, all elements in the returned
* list <i>will</i> satisfy the condition. If the condition is never satisfied,
* this list remains unchanged and an empty list is returned. If <i>all</i>
* elements satisfy the condition, the list remains unchanged and is itself
* returned.
*
* @param criterion the criterion that the elements in the returned segment
* will satisfy
* @return a {@code CrisprList} containing all elements preceding the first element
* that does not satisfy the condition; possibly this instance
*/
public CrisprList<E> lchop(Predicate<? super E> criterion) {
Check.notNull(criterion);
if (sz == 0) {
return this;
}
Node<E> first = head;
Node<E> last = justBeforeHead();
int len = 0;
for (; criterion.test(last.next.val) && ++len != sz; last = last.next)
;
if (len == sz) {
return this;
}
return new CrisprList<>(unlink(new Chain(first, last, len)));
}
/**
* Removes and returns a segment from the end of the list. The segment will include
* all elements following the <i>last</i> element that does <i>not</i> satisfy the
* specified condition. In other words, all elements in the returned list
* <i>will</i> satisfy the condition. If the condition is never satisfied, the
* list remains unchanged and an empty list is returned. If <i>all</i> elements
* satisfy the condition, the list remains unchanged and is itself returned.
*
* @param criterion the criterion that the elements in the returned segment
* will satisfy
* @return a {@code CrisprList} containing all elements following the last element
* that does not satisfy the condition; possibly this instance
*/
public CrisprList<E> rchop(Predicate<? super E> criterion) {
Check.notNull(criterion);
if (sz == 0) {
return this;
}
Node<E> last = tail;
Node<E> first = justAfterTail();
int len = 0;
for (; criterion.test(first.prev.val) && ++len != sz; first = first.prev)
;
if (len == sz) {
return this;
}
return new CrisprList<>(unlink(new Chain(first, last, len)));
}
/**
* Reverses the order of the elements in this {@code CrisprList}.
*
* @return this instance
*/
public CrisprList<E> reverse() {
reverse0();
return this;
}
/**
* Moves a list segment forwards or backwards through the list.
*
* @param fromIndex the start index of the segment (inclusive)
* @param toIndex the end index of the segment (exclusive)
* @param newFromIndex the index to which to move the segment. To move the
* segment to the very start of the list, specify 0 (zero). To move the segment
* to the very end of the list specify the {@link #size() size} of the list
* @return this instance
*/
public CrisprList<E> move(int fromIndex, int toIndex, int newFromIndex) {
int len = Check.fromTo(this, fromIndex, toIndex);
Check.that(newFromIndex, "target index").is(indexInclusiveOf(), this);
if (len != 0) {
if (newFromIndex > fromIndex) {
moveRight(fromIndex, toIndex, newFromIndex);
} else if (newFromIndex < fromIndex) {
moveLeft(fromIndex, toIndex, newFromIndex);
}
}
return this;
}
/**
* Removes all elements from this list. The list will be empty after this call
* returns.
*/
@Override
public void clear() {
head = tail = null;
sz = 0;
}
/**
* Returns an {@code Iterator} that traverses the list from the last element to the
* first. See also {@link #iterator()}.
*
* @return an {@code Iterator} that traverses the list from the last element to the
* first
*/
public Iterator<E> reverseIterator() {
return super.reverseIterator0();
}
/**
* Returns a {@link WiredIterator} that traverses the list from the first element
* to the last.
*
* @return a {@code WiredIterator} that traverses the list from the first element
* to the last
*/
public WiredIterator<E> wiredIterator() {
return new CFwdWiredIterator();
}
/**
* Returns a {@link WiredIterator} that traverses the list from the first element
* to the last, or the other way round, depending on the value of the argument
*
* @param reverse Whether to iterate from the first to the last
* ({@code false}), or from the last to the first ({@code true})
* @return a {@code WiredIterator} that traverses the list from the first element
* to the last, or the other way round
*/
public WiredIterator<E> wiredIterator(boolean reverse) {
return reverse ? new CRevWiredIterator() : new CFwdWiredIterator();
}
/**
* Returns an array containing the elements within the specified region of this
* list.
*
* @param fromIndex the start index (inclusive) of the region
* @param toIndex the end index (exclusive) of the region
* @return an array containing the elements within the specified region of this
* list
*/
public Object[] regionToArray(int fromIndex, int toIndex) {
return regionToArray0(fromIndex, toIndex);
}
/**
* Copies the specified region within this list to the specified position within
* the specified array. The array must be large enough to copy the entire region to
* the specified position.
*
* @param fromIndex the start index (inclusive) of the region
* @param toIndex the end index (exclusive) of the region
* @param target the array to which to copy the elements
* @param offset the offset within the array
*/
public void regionToArray(int fromIndex,
int toIndex,
Object[] target,
int offset) {
regionToArray0(fromIndex, toIndex, target, offset);
}
/**
* Throws an {@code UnsupportedOperationException}. The specification for this
* method requires that non-structural changes in the returned list are reflected
* in the original list (and vice versa). However, except for the
* {@link #set(int, Object)} method, all changes to a {@code CrisprList} <i>are</i>
* structural changes. {@code CrisprList} does provide a method that returns a
* sublist ({@link #copy(int, int) copy}). It just has no relation to the original
* list any longer.
*/
@Override
public List<E> subList(int fromIndex, int toIndex) {
throw new UnsupportedOperationException();
}
}