mirror of https://github.com/apache/cassandra
Fix cassandra-stress user-mode truncation of partition generation
patch by benedict; reviewed by tjake for CASSANDRA-8608
This commit is contained in:
parent
576a75f28a
commit
1435b9a87a
|
|
@ -1,4 +1,5 @@
|
||||||
2.1.3
|
2.1.3
|
||||||
|
* Fix cassandra-stress user-mode truncation of partition generation (CASSANDRA-8608)
|
||||||
* Only stream from unrepaired sstables during inc repair (CASSANDRA-8267)
|
* Only stream from unrepaired sstables during inc repair (CASSANDRA-8267)
|
||||||
* Don't allow starting multiple inc repairs on the same sstables (CASSANDRA-8316)
|
* Don't allow starting multiple inc repairs on the same sstables (CASSANDRA-8316)
|
||||||
* Invalidate prepared BATCH statements when related tables
|
* Invalidate prepared BATCH statements when related tables
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,9 @@ public abstract class Operation
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (spec.useRatio == null)
|
if (spec.useRatio == null)
|
||||||
success = partitionCache.get(i).reset(seed, spec.targetCount, this);
|
success = partitionCache.get(i).reset(seed, spec.targetCount, isWrite());
|
||||||
else
|
else
|
||||||
success = partitionCache.get(i).reset(seed, spec.useRatio.next(), this);
|
success = partitionCache.get(i).reset(seed, spec.useRatio.next(), isWrite());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
partitionCount = i;
|
partitionCount = i;
|
||||||
|
|
|
||||||
|
|
@ -50,14 +50,16 @@ import org.apache.cassandra.stress.generate.values.Generator;
|
||||||
public abstract class PartitionIterator implements Iterator<Row>
|
public abstract class PartitionIterator implements Iterator<Row>
|
||||||
{
|
{
|
||||||
|
|
||||||
// we reuse the row object to save garbage
|
abstract boolean reset(double useChance, int targetCount, boolean isWrite);
|
||||||
abstract boolean reset(double useChance, int targetCount, Operation op);
|
|
||||||
|
|
||||||
long idseed;
|
long idseed;
|
||||||
Seed seed;
|
Seed seed;
|
||||||
final Object[] partitionKey;
|
|
||||||
final PartitionGenerator generator;
|
final PartitionGenerator generator;
|
||||||
final SeedManager seedManager;
|
final SeedManager seedManager;
|
||||||
|
|
||||||
|
// we reuse these objects to save garbage
|
||||||
|
final Object[] partitionKey;
|
||||||
final Row row;
|
final Row row;
|
||||||
|
|
||||||
public static PartitionIterator get(PartitionGenerator generator, SeedManager seedManager)
|
public static PartitionIterator get(PartitionGenerator generator, SeedManager seedManager)
|
||||||
|
|
@ -93,16 +95,16 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
this.idseed = idseed;
|
this.idseed = idseed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean reset(Seed seed, double useChance, Operation op)
|
public boolean reset(Seed seed, double useChance, boolean isWrite)
|
||||||
{
|
{
|
||||||
setSeed(seed);
|
setSeed(seed);
|
||||||
return reset(useChance, 0, op);
|
return reset(useChance, 0, isWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean reset(Seed seed, int targetCount, Operation op)
|
public boolean reset(Seed seed, int targetCount, boolean isWrite)
|
||||||
{
|
{
|
||||||
setSeed(seed);
|
setSeed(seed);
|
||||||
return reset(Double.NaN, targetCount, op);
|
return reset(Double.NaN, targetCount, isWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class SingleRowIterator extends PartitionIterator
|
static class SingleRowIterator extends PartitionIterator
|
||||||
|
|
@ -115,10 +117,10 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
super(generator, seedManager);
|
super(generator, seedManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean reset(double useChance, int targetCount, Operation op)
|
boolean reset(double useChance, int targetCount, boolean isWrite)
|
||||||
{
|
{
|
||||||
done = false;
|
done = false;
|
||||||
isWrite = op.isWrite();
|
this.isWrite = isWrite;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,24 +157,22 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
// TODO : support first/last row, and constraining reads to rows we know are populated
|
// TODO : support first/last row, and constraining reads to rows we know are populated
|
||||||
static class MultiRowIterator extends PartitionIterator
|
static class MultiRowIterator extends PartitionIterator
|
||||||
{
|
{
|
||||||
|
|
||||||
// probability any single row will be generated in this iteration
|
|
||||||
double useChance;
|
|
||||||
|
|
||||||
// the seed used to generate the current values for the clustering components at each depth;
|
// the seed used to generate the current values for the clustering components at each depth;
|
||||||
// used to save recalculating it for each row, so we only need to recalc from prior row.
|
// used to save recalculating it for each row, so we only need to recalc from prior row.
|
||||||
final long[] clusteringSeeds = new long[generator.clusteringComponents.size()];
|
final long[] clusteringSeeds = new long[generator.clusteringComponents.size()];
|
||||||
// the components remaining to be visited for each level of the current stack
|
// the components remaining to be visited for each level of the current stack
|
||||||
final Deque<Object>[] clusteringComponents = new ArrayDeque[generator.clusteringComponents.size()];
|
final Deque<Object>[] clusteringComponents = new ArrayDeque[generator.clusteringComponents.size()];
|
||||||
|
|
||||||
|
// probability any single row will be generated in this iteration
|
||||||
|
double useChance;
|
||||||
// we want our chance of selection to be applied uniformly, so we compound the roll we make at each level
|
// we want our chance of selection to be applied uniformly, so we compound the roll we make at each level
|
||||||
// so that we know with what chance we reached there, and we adjust our roll at that level by that amount
|
// so that we know with what chance we reached there, and we adjust our roll at that level by that amount
|
||||||
final double[] chancemodifier = new double[generator.clusteringComponents.size()];
|
final double[] chancemodifier = new double[generator.clusteringComponents.size()];
|
||||||
final double[] rollmodifier = new double[generator.clusteringComponents.size()];
|
final double[] rollmodifier = new double[generator.clusteringComponents.size()];
|
||||||
|
|
||||||
// track where in the partition we are, and where we are limited to
|
// track where in the partition we are, and where we are limited to
|
||||||
final int[] position = new int[generator.clusteringComponents.size()];
|
final int[] currentRow = new int[generator.clusteringComponents.size()];
|
||||||
final int[] limit = new int[position.length];
|
final int[] lastRow = new int[currentRow.length];
|
||||||
boolean hasNext, isFirstWrite, isWrite;
|
boolean hasNext, isFirstWrite, isWrite;
|
||||||
|
|
||||||
// reusable collections for generating unique and sorted clustering components
|
// reusable collections for generating unique and sorted clustering components
|
||||||
|
|
@ -188,10 +188,22 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
chancemodifier[0] = generator.clusteringDescendantAverages[0];
|
chancemodifier[0] = generator.clusteringDescendantAverages[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're a write, the expected behaviour is that the requested batch count is compounded with the seed's visit
|
/**
|
||||||
// count to decide how much we should return in one iteration
|
* initialise the iterator state
|
||||||
boolean reset(double useChance, int targetCount, Operation op)
|
*
|
||||||
|
* if we're a write, the expected behaviour is that the requested
|
||||||
|
* batch count is compounded with the seed's visit count to decide
|
||||||
|
* how much we should return in one iteration
|
||||||
|
*
|
||||||
|
* @param useChance uniform chance of visiting any single row (NaN if targetCount provided)
|
||||||
|
* @param targetCount number of rows we would like to visit (0 if useChance provided)
|
||||||
|
* @param isWrite true if the action requires write semantics
|
||||||
|
*
|
||||||
|
* @return true if there is data to return, false otherwise
|
||||||
|
*/
|
||||||
|
boolean reset(double useChance, int targetCount, boolean isWrite)
|
||||||
{
|
{
|
||||||
|
this.isWrite = isWrite;
|
||||||
if (this.useChance < 1d)
|
if (this.useChance < 1d)
|
||||||
{
|
{
|
||||||
// we clear our prior roll-modifiers if the use chance was previously less-than zero
|
// we clear our prior roll-modifiers if the use chance was previously less-than zero
|
||||||
|
|
@ -207,14 +219,13 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
int expectedRowCount;
|
int expectedRowCount;
|
||||||
|
|
||||||
int position = seed.position();
|
int position = seed.position();
|
||||||
isWrite = op.isWrite();
|
|
||||||
|
|
||||||
if (isWrite)
|
if (isWrite)
|
||||||
expectedRowCount = firstComponentCount * generator.clusteringDescendantAverages[0];
|
expectedRowCount = firstComponentCount * generator.clusteringDescendantAverages[0];
|
||||||
else if (position != 0)
|
else if (position != 0)
|
||||||
expectedRowCount = setLimit(position);
|
expectedRowCount = setLastRow(position - 1);
|
||||||
else
|
else
|
||||||
expectedRowCount = setNoLimit(firstComponentCount);
|
expectedRowCount = setNoLastRow(firstComponentCount);
|
||||||
|
|
||||||
if (Double.isNaN(useChance))
|
if (Double.isNaN(useChance))
|
||||||
useChance = Math.max(0d, Math.min(1d, targetCount / (double) expectedRowCount));
|
useChance = Math.max(0d, Math.min(1d, targetCount / (double) expectedRowCount));
|
||||||
|
|
@ -222,38 +233,84 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// TODO: we could avoid repopulating these each loop, by tracking our prior position
|
// we loop in case we have picked an entirely non-existent range, in which case
|
||||||
|
// we will reset the seed's position, then try again (until we exhaust it or find
|
||||||
|
// some real range)
|
||||||
|
|
||||||
for (Queue<?> q : clusteringComponents)
|
for (Queue<?> q : clusteringComponents)
|
||||||
q.clear();
|
q.clear();
|
||||||
clusteringSeeds[0] = idseed;
|
clusteringSeeds[0] = idseed;
|
||||||
fill(clusteringComponents[0], firstComponentCount, generator.clusteringComponents.get(0));
|
fill(clusteringComponents[0], firstComponentCount, generator.clusteringComponents.get(0));
|
||||||
|
|
||||||
// we loop in case we have picked an entirely non-existent range, in which case
|
if (!isWrite)
|
||||||
// we will reset the seed's position, then try again (until we exhaust it or find
|
|
||||||
// some real range) - this only happens for writes, so we only keep this logic in the loop
|
|
||||||
|
|
||||||
if (isWrite)
|
|
||||||
{
|
{
|
||||||
position = seed.moveForwards(Math.max(1, expectedRowCount / seed.visits));
|
if (seek(0) != State.SUCCESS)
|
||||||
isFirstWrite = position == 0;
|
throw new IllegalStateException();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int count = Math.max(1, expectedRowCount / seed.visits);
|
||||||
|
position = seed.moveForwards(count);
|
||||||
|
isFirstWrite = position == 0;
|
||||||
|
setLastRow(position + count - 1);
|
||||||
|
|
||||||
// seek to our start position
|
// seek to our start position
|
||||||
switch (seek(isWrite ? position : 0))
|
switch (seek(position))
|
||||||
{
|
{
|
||||||
case END_OF_PARTITION:
|
case END_OF_PARTITION:
|
||||||
return false;
|
return false;
|
||||||
case SUCCESS:
|
case SUCCESS:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isWrite)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
|
|
||||||
// TODO: recompose our real position into the nearest scalar position, and ensure the seed position is >= this
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns expected row count
|
||||||
|
private int setNoLastRow(int firstComponentCount)
|
||||||
|
{
|
||||||
|
Arrays.fill(lastRow, Integer.MAX_VALUE);
|
||||||
|
return firstComponentCount * generator.clusteringDescendantAverages[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// sets the last row we will visit
|
||||||
|
// returns expected distance from zero
|
||||||
|
private int setLastRow(int position)
|
||||||
|
{
|
||||||
|
if (position < 0)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
|
||||||
|
decompose(position, lastRow);
|
||||||
|
int expectedRowCount = 0;
|
||||||
|
for (int i = 0 ; i < lastRow.length ; i++)
|
||||||
|
{
|
||||||
|
int l = lastRow[i];
|
||||||
|
expectedRowCount += l * generator.clusteringDescendantAverages[i];
|
||||||
|
}
|
||||||
|
return expectedRowCount + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns 0 if we are currently on the last row we are allocated to visit; 1 if it is after, -1 if it is before
|
||||||
|
// this is defined by _limit_, which is wired up from expected (mean) row counts
|
||||||
|
// the last row is where position == lastRow, except the last index is 1 less;
|
||||||
|
// OR if that row does not exist, it is the last row prior to it
|
||||||
|
private int compareToLastRow(int depth)
|
||||||
|
{
|
||||||
|
for (int i = 0 ; i <= depth ; i++)
|
||||||
|
{
|
||||||
|
int p = currentRow[i], l = lastRow[i], r = clusteringComponents[i].size();
|
||||||
|
if ((p == l) | (r == 1))
|
||||||
|
continue;
|
||||||
|
return p - l;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate the scalar position into a tiered position based on mean expected counts
|
||||||
|
* @param scalar scalar position
|
||||||
|
* @param decomposed target container
|
||||||
|
*/
|
||||||
private void decompose(int scalar, int[] decomposed)
|
private void decompose(int scalar, int[] decomposed)
|
||||||
{
|
{
|
||||||
for (int i = 0 ; i < decomposed.length ; i++)
|
for (int i = 0 ; i < decomposed.length ; i++)
|
||||||
|
|
@ -262,7 +319,7 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
decomposed[i] = scalar / avg;
|
decomposed[i] = scalar / avg;
|
||||||
scalar %= avg;
|
scalar %= avg;
|
||||||
}
|
}
|
||||||
for (int i = limit.length - 1 ; i > 0 ; i--)
|
for (int i = lastRow.length - 1 ; i > 0 ; i--)
|
||||||
{
|
{
|
||||||
int avg = generator.clusteringComponentAverages[i];
|
int avg = generator.clusteringComponentAverages[i];
|
||||||
if (decomposed[i] >= avg)
|
if (decomposed[i] >= avg)
|
||||||
|
|
@ -273,42 +330,28 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int setNoLimit(int firstComponentCount)
|
|
||||||
{
|
|
||||||
Arrays.fill(limit, Integer.MAX_VALUE);
|
|
||||||
return firstComponentCount * generator.clusteringDescendantAverages[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
private int setLimit(int position)
|
|
||||||
{
|
|
||||||
decompose(position, limit);
|
|
||||||
int expectedRowCount = 0;
|
|
||||||
for (int i = 0 ; i < limit.length ; i++)
|
|
||||||
{
|
|
||||||
int l = limit[i];
|
|
||||||
expectedRowCount += l * generator.clusteringDescendantAverages[i];
|
|
||||||
}
|
|
||||||
return expectedRowCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum State
|
static enum State
|
||||||
{
|
{
|
||||||
END_OF_PARTITION, AFTER_LIMIT, SUCCESS;
|
END_OF_PARTITION, AFTER_LIMIT, SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// seek to the provided position (or the first entry if null)
|
/**
|
||||||
|
* seek to the provided position to initialise the iterator
|
||||||
|
*
|
||||||
|
* @param scalar scalar position
|
||||||
|
* @return resultant iterator state
|
||||||
|
*/
|
||||||
private State seek(int scalar)
|
private State seek(int scalar)
|
||||||
{
|
{
|
||||||
if (scalar == 0)
|
if (scalar == 0)
|
||||||
{
|
{
|
||||||
this.position[0] = -1;
|
this.currentRow[0] = -1;
|
||||||
clusteringComponents[0].addFirst(this);
|
clusteringComponents[0].addFirst(this);
|
||||||
return setHasNext(advance(0, true));
|
return setHasNext(advance(0, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
int[] position = this.position;
|
int[] position = this.currentRow;
|
||||||
decompose(scalar, position);
|
decompose(scalar, position);
|
||||||
boolean incremented = false;
|
|
||||||
for (int i = 0 ; i < position.length ; i++)
|
for (int i = 0 ; i < position.length ; i++)
|
||||||
{
|
{
|
||||||
if (i != 0)
|
if (i != 0)
|
||||||
|
|
@ -321,39 +364,36 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
if (clusteringComponents[i].isEmpty())
|
if (clusteringComponents[i].isEmpty())
|
||||||
{
|
{
|
||||||
int j = i;
|
int j = i;
|
||||||
while (--j >= 0)
|
while (true)
|
||||||
{
|
{
|
||||||
|
// if we've exhausted the whole partition, we're done
|
||||||
|
if (--j < 0)
|
||||||
|
return setHasNext(false);
|
||||||
|
|
||||||
clusteringComponents[j].poll();
|
clusteringComponents[j].poll();
|
||||||
if (!clusteringComponents[j].isEmpty())
|
if (!clusteringComponents[j].isEmpty())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we've exhausted the whole partition, we're done
|
// we don't check here to see if we've exceeded our lastRow,
|
||||||
if (j < 0)
|
// because if we came to a non-existent position and generated a lastRow
|
||||||
return setHasNext(false);
|
|
||||||
|
|
||||||
// we don't check here to see if we've exceeded our limit,
|
|
||||||
// because if we came to a non-existent position and generated a limit
|
|
||||||
// we want to at least find the next real position, and set it on the seed
|
// we want to at least find the next real position, and set it on the seed
|
||||||
// in this case we do then yield false and select a different seed to continue with
|
// in this case we do then yield false and select a different seed to continue with
|
||||||
position[j]++;
|
position[j]++;
|
||||||
Arrays.fill(position, j + 1, position.length, 0);
|
Arrays.fill(position, j + 1, position.length, 0);
|
||||||
while (j < i)
|
while (j < i)
|
||||||
fill(++j);
|
fill(++j);
|
||||||
incremented = true;
|
|
||||||
}
|
}
|
||||||
if (clusteringComponents[i].isEmpty())
|
|
||||||
throw new IllegalStateException();
|
|
||||||
row.row[i] = clusteringComponents[i].peek();
|
row.row[i] = clusteringComponents[i].peek();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (incremented && compareToLastRow() > 0)
|
if (compareToLastRow(currentRow.length - 1) > 0)
|
||||||
return setHasNext(false);
|
return setHasNext(false);
|
||||||
|
|
||||||
position[position.length - 1]--;
|
|
||||||
// call advance so we honour any select chance
|
// call advance so we honour any select chance
|
||||||
|
position[position.length - 1]--;
|
||||||
clusteringComponents[position.length - 1].addFirst(this);
|
clusteringComponents[position.length - 1].addFirst(this);
|
||||||
|
|
||||||
return setHasNext(advance(position.length - 1, true));
|
return setHasNext(advance(position.length - 1, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,7 +424,7 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||||
// advance the leaf component
|
// advance the leaf component
|
||||||
clusteringComponents[depth].poll();
|
clusteringComponents[depth].poll();
|
||||||
position[depth]++;
|
currentRow[depth]++;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (clusteringComponents[depth].isEmpty())
|
if (clusteringComponents[depth].isEmpty())
|
||||||
|
|
@ -394,15 +434,18 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
return false;
|
return false;
|
||||||
depth--;
|
depth--;
|
||||||
clusteringComponents[depth].poll();
|
clusteringComponents[depth].poll();
|
||||||
if (++position[depth] > limit[depth])
|
if (++currentRow[depth] > lastRow[depth])
|
||||||
return false;
|
return false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int compareToLastRow = compareToLastRow();
|
int compareToLastRow = compareToLastRow(depth);
|
||||||
if (compareToLastRow > 0 && !first)
|
if (compareToLastRow > 0)
|
||||||
|
{
|
||||||
|
assert !first;
|
||||||
return false;
|
return false;
|
||||||
boolean forceReturnOne = first && compareToLastRow >= 0;
|
}
|
||||||
|
boolean forceReturnOne = first && compareToLastRow == 0;
|
||||||
|
|
||||||
// the chance of descending is the uniform usechance, multiplied by the number of children
|
// the chance of descending is the uniform usechance, multiplied by the number of children
|
||||||
// we would on average generate (so if we have a 0.1 use chance, but should generate 10 children
|
// we would on average generate (so if we have a 0.1 use chance, but should generate 10 children
|
||||||
|
|
@ -424,7 +467,7 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
rollmodifier[depth] = rollmodifier[depth - 1] / Math.min(1d, thischance);
|
rollmodifier[depth] = rollmodifier[depth - 1] / Math.min(1d, thischance);
|
||||||
chancemodifier[depth] = generator.clusteringDescendantAverages[depth] * rollmodifier[depth];
|
chancemodifier[depth] = generator.clusteringDescendantAverages[depth] * rollmodifier[depth];
|
||||||
}
|
}
|
||||||
position[depth] = 0;
|
currentRow[depth] = 0;
|
||||||
fill(depth);
|
fill(depth);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -434,34 +477,10 @@ public abstract class PartitionIterator implements Iterator<Row>
|
||||||
|
|
||||||
// if we don't descend, we remove the clustering suffix we've skipped and continue
|
// if we don't descend, we remove the clustering suffix we've skipped and continue
|
||||||
clusteringComponents[depth].poll();
|
clusteringComponents[depth].poll();
|
||||||
position[depth]++;
|
currentRow[depth]++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int compare(int[] a, int[] b)
|
|
||||||
{
|
|
||||||
for (int i = 0 ; i != a.length ; i++)
|
|
||||||
if (a[i] != b[i])
|
|
||||||
return Integer.compare(a[i], b[i]);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int compareToLastRow()
|
|
||||||
{
|
|
||||||
int c = position.length - 1;
|
|
||||||
for (int i = 0 ; i <= c ; i++)
|
|
||||||
{
|
|
||||||
int p = position[i], l = limit[i], r = clusteringComponents[i].size();
|
|
||||||
if (i == c && p == l - 1)
|
|
||||||
return 0;
|
|
||||||
if ((p < l) & (r > 1))
|
|
||||||
return -1;
|
|
||||||
if (p > l)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate the clustering components for the provided depth; requires preceding components
|
// generate the clustering components for the provided depth; requires preceding components
|
||||||
// to have been generated and their seeds populated into clusteringSeeds
|
// to have been generated and their seeds populated into clusteringSeeds
|
||||||
void fill(int depth)
|
void fill(int depth)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue