All Downloads are FREE. Search and download functionalities are using the official Maven repository.

paxel.lib.FrankenList Maven / Gradle / Ivy

The newest version!
package paxel.lib;

import java.util.*;

/**
 * The FrankenList combines an ArrayList and multiple LinkedLists to allow quick
 * navigation to a limited sized linked list and fast add/remove inside that
 * linked list. As a result, this list is exceptionally faster than either Array or
 * LinkedList for big amounts of data. When elements are inserted to the
 * FrankenList (read: not at the end) the new element is always added to a
 * LinkedList, which is very fast. In case this LinkedList reaches the
 * sectionSizeLimit, it is split in half and the lower half is inserted into the
 * ArrayList containing the sections. This is quite slow, but depending on the
 * size of the sections quite rare. Additionally, all sections behind the
 * LinkedList get their global start index incremented. Searching an element in
 * the FrankenList is searching the correct section in the ArrayList, which is
 * fast because random access and then navigating in the small LinkedList to the
 * correct position. This is quite slow.
 * 

* Overall the search and insert times are faster than a pure ArrayList or * LinkedList when the size of the map is very big. */ public class FrankenList extends AbstractList implements RandomAccess { private final ArrayListSection data; public FrankenList() { data = new ArrayListSection<>(); } /** * Sets the maximum size of a section. Different sizes can be quicker in * different scenarios. * * @param sectionSizeLimit The section size limit. */ public FrankenList(int sectionSizeLimit) { data = new ArrayListSection<>(sectionSizeLimit); } @Override public E get(int index) { return data.get(index); } @Override public void clear() { data.clear(); } @Override public int size() { return data.size(); } @Override public E remove(int index) { final E remove = data.remove(index); if (remove != null) { modCount++; } return remove; } @Override public boolean add(E value) { data.add(value); modCount++; return true; } @Override public int hashCode() { int hash = 3; hash = 67 * hash + Objects.hashCode(this.data); return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final FrankenList other = (FrankenList) obj; return Objects.equals(this.data, other.data); } @Override public void add(int index, E value) { data.add(index, value); modCount++; } @Override public E set(int index, E value) { return data.set(index, value); } @Override public void sort(Comparator c) { int expected = super.modCount; Object[] a = this.toArray(); Arrays.sort(a); if (super.modCount != expected) { throw new ConcurrentModificationException("modified while sorting: sort aborted."); } clear(); expected = super.modCount; for (Object e : a) { data.add((E) e); } if (super.modCount != expected) { throw new ConcurrentModificationException("modified while sorting: list is corrupted."); } modCount++; } /** * Retrieve the first element. * * @return The first Element. * @throws NoSuchElementException if this list is empty */ public E getFirst() { if (data.size() == 0) { throw new NoSuchElementException(); } return data.get(0); } /** * Retrieve the last element. * * @return The last Element. * @throws NoSuchElementException if this list is empty */ public E getLast() { if (data.size() == 0) { throw new NoSuchElementException(); } return data.get(data.size() - 1); } /** * Inserts the element at the beginning. * * @param value The value */ public void addFirst(E value) { modCount++; data.add(0, value); } /** * Appends the element at the end. * * @param value The value */ public void addLast(E value) { add(value); } private class ArrayListSection { private final int sectionSizeLimit; private final ArrayList> sections = new ArrayList<>(); private int entryCount; private ArrayListSection(int sectionSizeLimit) { this.sectionSizeLimit = sectionSizeLimit; } private ArrayListSection() { this.sectionSizeLimit = 750; } private F remove(int index) { if (sections.isEmpty() || index < 0 || index > entryCount) { return null; } int rootIndex = guessRootIndex(index); return dec(sections.get(rootIndex).remove(index), rootIndex); } private void add(int index, F element) { if (index > entryCount) { throw new IndexOutOfBoundsException("Index " + index + " is outside of " + entryCount); } if (index == entryCount) { add(element); } else { int rootIndex = guessRootIndex(index); final LinkedListSection section = sections.get(rootIndex); section.add(index, element); for (int i = rootIndex + 1; i < sections.size(); i++) { sections.get(i).inc(); } if (section.values.size() > sectionSizeLimit) { section.split(sections, rootIndex); } entryCount++; } } private F set(int index, F element) { if (index >= entryCount || index < 0) { throw new IndexOutOfBoundsException("Index " + index + " is outside of [0 to" + entryCount + '['); } int rootIndex = guessRootIndex(index); final LinkedListSection bucket = sections.get(rootIndex); return bucket.set(index, element); } private F get(int index) { if (sections.isEmpty() || index < 0 || index > entryCount) { throw new IndexOutOfBoundsException("Index " + index + " is outside of [0 to" + entryCount + '['); } int rootIndex = guessRootIndex(index); return sections.get(rootIndex).get(index); } private void add(F value) { if (sections.isEmpty()) { final LinkedListSection section = new LinkedListSection<>(0); sections.add(section); section.values.add(value); } else { LinkedListSection last = sections.get(sections.size() - 1); if (last.values.size() < sectionSizeLimit) { last.values.add(value); } else { LinkedListSection bucket = new LinkedListSection<>( last.globalSectionStartIndex + sectionSizeLimit); bucket.values.add(value); sections.add(bucket); } } entryCount++; } private F dec(F removeResult, int currentIndex) { if (removeResult != null) { // we removed an element, so the indices behind the bucket need to be // decremented for (int j = currentIndex + 1; j < sections.size(); j++) { sections.get(j).dec(); } if (sections.get(currentIndex).values.isEmpty()) { // this bucket is now empty. we need to remove it sections.remove(currentIndex); } entryCount--; } return removeResult; } private int guessRootIndex(int index) { if (index == 0) { return 0; } final int lastBucket = sections.size() - 1; if (index > sections.get(lastBucket).globalSectionStartIndex) { return lastBucket; } int guessedIndex = index / sectionSizeLimit; if (guessedIndex > lastBucket) { guessedIndex = lastBucket; } if (guessedIndex < 0) { guessedIndex = 0; } for (;;) { LinkedListSection test = sections.get(guessedIndex); if (test.globalSectionStartIndex <= index) { if (guessedIndex == lastBucket) { // it's in/behind the last one return guessedIndex; } if (sections.get(guessedIndex + 1).globalSectionStartIndex > index) { // found return guessedIndex; } // it is maybe in the next one guessedIndex++; } else { if (guessedIndex == 0) { // somethings broken return 0; } // it is maybe in the previous one guessedIndex--; } } } int size() { return entryCount; } private void clear() { int expected = modCount; sections.clear(); entryCount = 0; if (modCount != expected) { throw new ConcurrentModificationException("The map was modified while clearing"); } modCount++; } private class LinkedListSection { private int globalSectionStartIndex; private final LinkedList values = new LinkedList<>(); public LinkedListSection(int index) { this.globalSectionStartIndex = index; } private G get(int globalIndex) { final int localIndex = globalIndex - this.globalSectionStartIndex; if (localIndex < 0 || localIndex > values.size()) { return null; } return values.get(localIndex); } private void add(int globalIndex, G element) { final int localIndex = globalIndex - this.globalSectionStartIndex; if (localIndex >= 0 && localIndex <= values.size()) { values.add(localIndex, element); } } private G set(int globalIndex, G element) { final int localIndex = globalIndex - this.globalSectionStartIndex; if (localIndex < 0 || localIndex > values.size()) { return null; } else { return values.set(localIndex, element); } } private G remove(int globalIndex) { final int localIndex = globalIndex - this.globalSectionStartIndex; if (localIndex < 0 || localIndex >= values.size()) { return null; } return values.remove(localIndex); } private void inc() { globalSectionStartIndex++; } private void dec() { globalSectionStartIndex--; } private void split(ArrayList> rootList, int splitNodeIndex) { final int nextIndex = sectionSizeLimit / 2; LinkedListSection nextNode = new LinkedListSection<>(this.globalSectionStartIndex + nextIndex); // move the end of the list to a new bucket List subList = values.subList(nextIndex, values.size()); // add lower part to the new bucket nextNode.values.addAll(subList); // delete lower part in previous bucket subList.clear(); rootList.add(splitNodeIndex + 1, nextNode); } @Override public String toString() { return "Section{" + "indices " + globalSectionStartIndex + " to " + (globalSectionStartIndex + values.size() - 1) + '}'; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy