kopia lustrzana https://github.com/onthegomap/planetiler
247 wiersze
7.2 KiB
Java
247 wiersze
7.2 KiB
Java
/*
|
|
* Licensed to GraphHopper GmbH under one or more contributor
|
|
* license agreements. See the NOTICE file distributed with this work for
|
|
* additional information regarding copyright ownership.
|
|
*
|
|
* GraphHopper GmbH licenses this file to you under the Apache License,
|
|
* Version 2.0 (the "License"); you may not use this file except in
|
|
* compliance with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.onthegomap.planetiler.collection;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.function.IntBinaryOperator;
|
|
|
|
/**
|
|
* A min-heap stored in an array where each element has 4 children.
|
|
* <p>
|
|
* This is about 5-10% faster than the standard binary min-heap for the case of merging sorted lists.
|
|
* <p>
|
|
* Ported from <a href=
|
|
* "https://github.com/graphhopper/graphhopper/blob/master/core/src/main/java/com/graphhopper/coll/MinHeapWithUpdate.java">GraphHopper</a>
|
|
* and:
|
|
* <ul>
|
|
* <li>modified to use {@code long} values instead of {@code float}</li>
|
|
* <li>extracted a common interface for subclass implementations</li>
|
|
* <li>modified so that each element has 4 children instead of 2 (improves performance by 5-10%)</li>
|
|
* <li>performance improvements to minimize array lookups</li>
|
|
* </ul>
|
|
*
|
|
* @see <a href="https://en.wikipedia.org/wiki/D-ary_heap">d-ary heap (wikipedia)</a>
|
|
*/
|
|
class ArrayLongMinHeap implements LongMinHeap {
|
|
protected static final int NOT_PRESENT = -1;
|
|
protected final int[] posToId;
|
|
protected final int[] idToPos;
|
|
protected final long[] posToValue;
|
|
protected final int max;
|
|
protected int size;
|
|
private final IntBinaryOperator tieBreaker;
|
|
|
|
/**
|
|
* @param elements the number of elements that can be stored in this heap. Currently the heap cannot be resized or
|
|
* shrunk/trimmed after initial creation. elements-1 is the maximum id that can be stored in this heap
|
|
*/
|
|
ArrayLongMinHeap(int elements, IntBinaryOperator tieBreaker) {
|
|
// we use an offset of one to make the arithmetic a bit simpler/more efficient, the 0th elements are not used!
|
|
posToId = new int[elements + 1];
|
|
idToPos = new int[elements + 1];
|
|
Arrays.fill(idToPos, NOT_PRESENT);
|
|
posToValue = new long[elements + 1];
|
|
posToValue[0] = Long.MIN_VALUE;
|
|
this.max = elements;
|
|
this.tieBreaker = tieBreaker;
|
|
}
|
|
|
|
private static int firstChild(int index) {
|
|
return (index << 2) - 2;
|
|
}
|
|
|
|
private static int parent(int index) {
|
|
return (index + 2) >> 2;
|
|
}
|
|
|
|
@Override
|
|
public int size() {
|
|
return size;
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return size == 0;
|
|
}
|
|
|
|
@Override
|
|
public void push(int id, long value) {
|
|
checkIdInRange(id);
|
|
if (size == max) {
|
|
throw new IllegalStateException("Cannot push anymore, the heap is already full. size: " + size);
|
|
}
|
|
if (contains(id)) {
|
|
throw new IllegalStateException("Element with id: " + id +
|
|
" was pushed already, you need to use the update method if you want to change its value");
|
|
}
|
|
size++;
|
|
posToId[size] = id;
|
|
idToPos[id] = size;
|
|
posToValue[size] = value;
|
|
percolateUp(size);
|
|
}
|
|
|
|
@Override
|
|
public boolean contains(int id) {
|
|
checkIdInRange(id);
|
|
return idToPos[id] != NOT_PRESENT;
|
|
}
|
|
|
|
@Override
|
|
public void update(int id, long value) {
|
|
checkIdInRange(id);
|
|
int pos = idToPos[id];
|
|
if (pos < 0) {
|
|
throw new IllegalStateException(
|
|
"The heap does not contain: " + id + ". Use the contains method to check this before calling update");
|
|
}
|
|
long prev = posToValue[pos];
|
|
posToValue[pos] = value;
|
|
int cmp = compareIdPos(value, prev, id, pos);
|
|
if (cmp > 0) {
|
|
percolateDown(pos);
|
|
} else if (cmp < 0) {
|
|
percolateUp(pos);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void updateHead(long value) {
|
|
posToValue[1] = value;
|
|
percolateDown(1);
|
|
}
|
|
|
|
@Override
|
|
public int peekId() {
|
|
return posToId[1];
|
|
}
|
|
|
|
@Override
|
|
public long peekValue() {
|
|
return posToValue[1];
|
|
}
|
|
|
|
@Override
|
|
public int poll() {
|
|
int id = peekId();
|
|
posToId[1] = posToId[size];
|
|
posToValue[1] = posToValue[size];
|
|
idToPos[posToId[1]] = 1;
|
|
idToPos[id] = NOT_PRESENT;
|
|
size--;
|
|
percolateDown(1);
|
|
return id;
|
|
}
|
|
|
|
@Override
|
|
public void clear() {
|
|
for (int i = 1; i <= size; i++) {
|
|
idToPos[posToId[i]] = NOT_PRESENT;
|
|
}
|
|
size = 0;
|
|
}
|
|
|
|
private void percolateUp(int pos) {
|
|
assert pos != 0;
|
|
if (pos == 1) {
|
|
return;
|
|
}
|
|
final int id = posToId[pos];
|
|
final long val = posToValue[pos];
|
|
// the finish condition (index==0) is covered here automatically because we set vals[0]=-inf
|
|
int parent;
|
|
long parentValue;
|
|
while (compareIdPos(val, parentValue = posToValue[parent = parent(pos)], id, parent) < 0) {
|
|
posToValue[pos] = parentValue;
|
|
idToPos[posToId[pos] = posToId[parent]] = pos;
|
|
pos = parent;
|
|
}
|
|
posToId[pos] = id;
|
|
posToValue[pos] = val;
|
|
idToPos[posToId[pos]] = pos;
|
|
}
|
|
|
|
private void checkIdInRange(int id) {
|
|
if (id < 0 || id >= max) {
|
|
throw new IllegalArgumentException("Illegal id: " + id + ", legal range: [0, " + max + "[");
|
|
}
|
|
}
|
|
|
|
private void percolateDown(int pos) {
|
|
if (size == 0) {
|
|
return;
|
|
}
|
|
assert pos > 0;
|
|
assert pos <= size;
|
|
final int id = posToId[pos];
|
|
final long value = posToValue[pos];
|
|
int child;
|
|
while ((child = firstChild(pos)) <= size) {
|
|
// optimization: this is a very hot code path for performance of k-way merging,
|
|
// so manually-unroll the loop over the 4 child elements to find the minimum value
|
|
int minChild = child;
|
|
long minValue = posToValue[child], childValue;
|
|
if (++child <= size) {
|
|
if (comparePosPos(childValue = posToValue[child], minValue, child, minChild) < 0) {
|
|
minChild = child;
|
|
minValue = childValue;
|
|
}
|
|
if (++child <= size) {
|
|
if (comparePosPos(childValue = posToValue[child], minValue, child, minChild) < 0) {
|
|
minChild = child;
|
|
minValue = childValue;
|
|
}
|
|
if (++child <= size &&
|
|
comparePosPos(childValue = posToValue[child], minValue, child, minChild) < 0) {
|
|
minChild = child;
|
|
minValue = childValue;
|
|
}
|
|
}
|
|
}
|
|
if (compareIdPos(value, minValue, id, minChild) <= 0) {
|
|
break;
|
|
}
|
|
posToValue[pos] = minValue;
|
|
idToPos[posToId[pos] = posToId[minChild]] = pos;
|
|
pos = minChild;
|
|
}
|
|
posToId[pos] = id;
|
|
posToValue[pos] = value;
|
|
idToPos[id] = pos;
|
|
}
|
|
|
|
private int comparePosPos(long val1, long val2, int pos1, int pos2) {
|
|
if (val1 < val2) {
|
|
return -1;
|
|
} else if (val1 == val2 && val1 != Long.MIN_VALUE) {
|
|
return tieBreaker.applyAsInt(posToId[pos1], posToId[pos2]);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
private int compareIdPos(long val1, long val2, int id1, int pos2) {
|
|
if (val1 < val2) {
|
|
return -1;
|
|
} else if (val1 == val2 && val1 != Long.MIN_VALUE) {
|
|
return tieBreaker.applyAsInt(id1, posToId[pos2]);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
}
|