Skip to content

Commit

Permalink
Fix #40 -- a buggy "delayed removals" implementation during bulk remo…
Browse files Browse the repository at this point in the history
…ving operations
  • Loading branch information
leventov committed Apr 27, 2015
1 parent 6f21824 commit 1bb53e8
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private void innerGenerate(boolean replace) {
if (isLHash(cxt) && permissions.contains(REMOVE)) {
lines("int capacityMask = " + capacityMask(cxt) + ";");
lines("int firstDelayedRemoved = -1;");
if (cxt.isPrimitiveKey()) {
if (cxt.isIntegralKey()) {
String delayedValue = ((PrimitiveType) cxt.keyOption()).bitsType().formatValue("0");
lines(cxt.keyUnwrappedRawType() + " delayedRemoved = " + delayedValue + ";");
}
Expand Down Expand Up @@ -124,7 +124,7 @@ private void innerGenerate(boolean replace) {

if (isLHash(cxt) && permissions.contains(REMOVE)) {
ifBlock("firstDelayedRemoved >= 0"); {
String addArg = cxt.isPrimitiveKey() ? ", delayedRemoved" : "";
String addArg = cxt.isIntegralKey() ? ", delayedRemoved" : "";
lines("closeDelayedRemoved(firstDelayedRemoved" + addArg + ");");
} blockEnd();
}
Expand Down Expand Up @@ -332,8 +332,8 @@ public BulkMethodGenerator remove() {
lHashShiftRemove();
} else {
tombstoneRemove();
lines("postRemoveHook();");
}
lines("postRemoveHook();");
permissions.add(REMOVE);
return this;
}
Expand Down Expand Up @@ -361,9 +361,11 @@ void generate() {
ifBlock("firstDelayedRemoved < 0"); {
// "simple" mode
closeDeletion();
postRemoveHook();
} elseBlock(); {
// "delayed removed" mode
writeKey(g, cxt, "i", (cxt.isPrimitiveKey() ? "delayedRemoved" : "REMOVED"));
writeKey(g, cxt, "i", (cxt.isIntegralKey() ? "delayedRemoved" :
(cxt.isFloatingKey() ? "REMOVED_BITS" : "REMOVED")));
} blockEnd();
}

Expand All @@ -386,13 +388,16 @@ void beforeShift() {
// of close deletion loop, and `i - 1` otherwise, but we just use `i`
// to simplify the code, because anyway it is a very rare branch
lines("firstDelayedRemoved = i;");
if (cxt.isPrimitiveKey()) {
// when keys are primitives, use the first key, that should be
// removed, as tombstone
lines("delayedRemoved = " + readKeyOnly(cxt, "indexToRemove") + ";");
String delayedRemoved;
if (cxt.isIntegralKey()) {
lines("delayedRemoved = " + key() + ";");
delayedRemoved = key();
} else if (cxt.isFloatingKey()) {
delayedRemoved = "REMOVED_BITS";
} else {
writeKey(g, cxt, "indexToRemove", "REMOVED");
delayedRemoved = "REMOVED";
}
writeKey(g, cxt, "indexToRemove", delayedRemoved);
lines("break closeDeletion;");
} blockEnd();
ifBlock("indexToRemove == i"); {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,21 @@ protected void generateLines(Method method) {
lines("int capacityMask = " + capacityMask(cxt) + ";");
declareEntry(this, cxt);
forLoop(this, cxt, "firstDelayedRemoved", "i", true); {
String removed = cxt.isPrimitiveKey() ? "delayedRemoved" : "REMOVED";
String removed = cxt.isIntegralKey() ? "delayedRemoved" :
(cxt.isFloatingKey() ? "REMOVED_BITS" : "REMOVED");
ifBlock(readKeyOrEntry(cxt, "i") + " == " + removed); {
new LHashShiftRemove(this, cxt, "i", "tab", "vals") {
@Override
void generate() {
closeDeletion();
postRemoveHook();
}

@Override
String additionalShiftPrecondition() {
return "keyToShift != " + removed;
}
}.closeDeletion();
}.generate();
} blockEnd();
} blockEnd();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ private boolean retainAll(@Nonnull HashByteSet/*<>*/ thisC,
/* if LHash hash */
@Override
void closeDelayedRemoved(int firstDelayedRemoved
/* if !(obj key) */, /* bits */byte delayedRemoved/* endif */) {
/* if !(obj|float|double key) */, /* bits */byte delayedRemoved/* endif */) {
/* template LHashCloseDelayedRemoved */ throw new NotGenerated(); /* endtemplate */
}
/* endif */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ private boolean retainAll(@Nonnull HashByteSet/*<>*/ thisC,

/* if Mutable mutability LHash hash */
void closeDelayedRemoved(int firstDelayedRemoved
/* if !(obj elem) */, /* bits */byte delayedRemoved/* endif */) {
/* if !(obj|float|double elem) */, /* bits */byte delayedRemoved/* endif */) {
/* template LHashCloseDelayedRemoved */ throw new NotGenerated(); /* endtemplate */
}
/* endif */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed 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 net.openhft.koloboke.collect.impl.hash;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.BitSet;

import net.openhft.koloboke.collect.map.hash.HashLongObjMap;
import net.openhft.koloboke.collect.map.hash.HashLongObjMaps;
import net.openhft.koloboke.collect.set.hash.HashLongSet;
import net.openhft.koloboke.collect.set.hash.HashLongSets;
import org.junit.Test;


public class Issue40 {

@Test
public void test() {
main();
}

// The primes 3, 7, 109, and 673, are quite remarkable. By taking any two
// primes and concatenating them in any order the result will always be prime.
// For example, taking 7 and 109, both 7109 and 1097 are prime. The sum of
// these four primes, 792, represents the lowest sum for a set of four primes
// with this property.
//
// Find the lowest sum for a set of five primes for which any two primes
// concatenate to produce another prime.

// Analysis:
// 2 cannot be a part of the resulting set, since 2 is the only prime ending
// in 2. Same for 5.
// Use sets to intersect valid pairs until a set of five remains

private static final long[] primes = generatePrimesWithESieve(100000);
private static HashLongSet primeSet = HashLongSets.newUpdatableSet(primes);
private static long maxSum = 100000;
private static final int MAX_INDEX = Arrays.binarySearch(primes, BigInteger.valueOf(25000).nextProbablePrime().longValue());
private static final long MAX_PRIME = primes[primes.length - 1];
private static HashLongObjMap<HashLongSet> primePairMap = HashLongObjMaps.getDefaultFactory().<HashLongSet> newUpdatableMap(MAX_INDEX);

public static void main() {
long time = System.currentTimeMillis();
for (int i = 1; i < MAX_INDEX; i++) {
HashLongSet primarySet = getPairSet(primes[i], i);
long[] secArr = primarySet.toLongArray();
Arrays.sort(secArr);
for (int j = 0; j < secArr.length; j++) {
if (primes[i] + secArr[j] * 4 > maxSum) break;
HashLongSet secondarySet = HashLongSets.getDefaultFactory().newMutableSet(primarySet);
secondarySet.retainAll(getPairSet(secArr[j]));
if (secondarySet.size() < 3) continue;
long[] tertArr = secondarySet.toLongArray();
Arrays.sort(tertArr);
for (int k = 0; k < tertArr.length; k++) {
if (primes[i] + secArr[j] + tertArr[k] * 3 > maxSum) break;
HashLongSet tertiarySet = HashLongSets.getDefaultFactory().newMutableSet(secondarySet);
if (tertiarySet.size() < 2) continue;
tertiarySet.retainAll(getPairSet(tertArr[k]));
long[] quatArr = tertiarySet.toLongArray();
Arrays.sort(quatArr);
for (int l = 0; l < quatArr.length; l++) {
if (primes[i] + secArr[j] + tertArr[k] + quatArr[l] * 2 > maxSum) break;
HashLongSet quaternarySet = HashLongSets.getDefaultFactory().newMutableSet(tertiarySet);
if (quaternarySet.size() < 1) continue;
quaternarySet.retainAll(getPairSet(quatArr[l]));
long[] quinArr = quaternarySet.toLongArray();
Arrays.sort(quinArr);
for (int m = 0; m < quinArr.length; m++) {
if (primes[i] + secArr[j] + tertArr[k] + quatArr[l] + quinArr[m] > maxSum) break;
System.out.format("{ %1$d, %2$d, %3$d, %4$d, %5$d } Sum = %6$d", primes[i], secArr[j], tertArr[k], quatArr[l], quinArr[m], primes[i]
+ secArr[j] + tertArr[k] + quatArr[l] + quinArr[m]);
System.out.println();
long sum = primes[i] + secArr[j] + tertArr[k] + quatArr[l] + quinArr[m];
if (sum < maxSum) {
maxSum = sum;
}
}
}
}
}
}

System.out.println("Result = " + maxSum);
System.out.println("Done.");
System.out.println("Solution took " + (System.currentTimeMillis() - time) + " ms.");
}

private static HashLongSet getPairSet(long prime) {
return getPairSet(prime, Arrays.binarySearch(primes, prime));
}

private static HashLongSet getPairSet(long prime, int primeIndex) {
HashLongSet ret = primePairMap.get(prime);
if (ret == null) {
primePairMap.put(prime, (ret = getPairs(prime, primeIndex)));
}
return ret;
}

private static HashLongSet getPairs(long prime, int primeIndex) {
HashLongSet pairSet = HashLongSets.newUpdatableSet();
long primeDigitSum = digitSum(prime);
for (int j = primeIndex + 1; j < MAX_INDEX; j++) {
if ((primeDigitSum + digitSum(primes[j])) % 3 == 0) continue;
if (isPrime(concat(prime, primes[j])) && isPrime(concat(primes[j], prime))) pairSet.add(primes[j]);
}
return pairSet;
}

private static boolean isPrime(long num) {
if (num > MAX_PRIME) { return BigInteger.valueOf(num).isProbablePrime(20); }
return primeSet.contains(num);
}

public static long[] generatePrimesWithESieve(long upperLimit) {
// No even numbers in sieve BitSet
// BitSet starts from 0..sieveBound
// p = 2i+3 => i = (p-3)/2
// jStart = 3p
// jStep = 2p

// i = 0 => p = 3 => jStart = 9, jStep = 6
// i = 0, j = 3, 6, 9
// i = 1 => p = 5 => jStart = 15, jStep = 10
// i = 1, j = 6, 11, 16
// i = 2 => p = 7 => jStart = 21, jStep = 14
// i = 2, j = 9, 16, 23

int intUpperLimit = Math.toIntExact(upperLimit);
int sieveBound = (intUpperLimit - 1) / 2;
int upperSqrt = ((int) Math.sqrt(upperLimit) - 1) / 2;

BitSet set = new BitSet(sieveBound + 1);
set.set(0, sieveBound + 1);

for (int i = 0; i <= upperSqrt; i++) {
if (set.get(i)) {
for (int j = 3 * i + 3; j <= sieveBound; j += 2 * i + 3) {
set.clear(j);
}
}
}

int i = 0;
// Number of primes can be approximated with pi(x) = x/log(x - 1)
LongArray list = new LongArray((int) (upperLimit / (Math.log(upperLimit) - 1.08366)));
list.setSizeIncreaseFactor(1.2d);
list.add(2); // sieve starts at 3
do {
list.add(2 * i + 3);
} while ((i = set.nextSetBit(i + 1)) != -1);

return list.getArray();
}

public static long concat(long a, long b) {
for (long c = b; c > 0;) {
a *= 10;
c /= 10;
}
return a + b;
}

public static long digitSum(long num) {
long sum = 0;
while (num > 0) {
sum += num - ((num / 10) * 10);
num /= 10;
}
return sum;
}

public static class LongArray {
private static final int DEFAULT_SIZE = 16;
private double sizeIncreaseFactor = 1.5d;
private long[] arr;
private int size = 0;

public LongArray() {
arr = new long[DEFAULT_SIZE];
}

public LongArray(int initialSize) {
arr = new long[initialSize];
}

public void add(long value) {
if (size == arr.length) {
increaseSize();
}
arr[size++] = value;
}

public long[] getArray() {
trim();
return arr;
}

public long get(int i) {
return arr[i];
}

public void trim() {
if (size < arr.length) {
long[] newArr = new long[size];
System.arraycopy(arr, 0, newArr, 0, size);
arr = newArr;
}
}

private void increaseSize() {
long[] newArr = new long[(int) (arr.length * sizeIncreaseFactor)];
System.arraycopy(arr, 0, newArr, 0, arr.length);
arr = newArr;
}

public double getSizeIncreaseFactor() {
return sizeIncreaseFactor;
}

public void setSizeIncreaseFactor(double sizeIncreaseFactor) {
this.sizeIncreaseFactor = sizeIncreaseFactor;
}
}
}

0 comments on commit 1bb53e8

Please sign in to comment.