CountThenEstimate implements IRichCardinality, Externalizable { @Override public IRichCardinality merge(IRichCardinality... estimators) throws CardinalityMergeException { if (estimators == null) { return mergeEstimators(this); } CountThenEstimate[] all = Arrays.copyOf(estimators, estimators.length + 1, CountThenEstimate[].class); all[all.length - 1] = this; return mergeEstimators(all); } CountThenEstimate();  CountThenEstimate(int tippingPoint, IBuilder<IRichCardinality> builder);  CountThenEstimate(byte[] bytes); @Override long cardinality(); @Override boolean offerHashed(long hashedLong); @Override boolean offerHashed(int hashedInt); @Override boolean offer(Object o); @Override int sizeof(); boolean tipped(); @Override byte[] getBytes(); @SuppressWarnings("unchecked") @Override void readExternal(ObjectInput in); @Override void writeExternal(ObjectOutput out); @Override IRichCardinality merge(IRichCardinality... estimators); static CountThenEstimate mergeEstimators(CountThenEstimate... estimators);  }
@Test public void testMerge() throws CardinalityMergeException { int numToMerge = 10; int tippingPoint = 100; int cardinality = 1000; CountThenEstimate[] ctes = new CountThenEstimate[numToMerge]; for (int i = 0; i < numToMerge; i++) { ctes[i] = new CountThenEstimate(tippingPoint, AdaptiveCounting.Builder.obyCount(100000)); for (int j = 0; j < tippingPoint - 1; j++) { ctes[i].offer(Math.random()); } } int expectedCardinality = numToMerge * (tippingPoint - 1); long mergedEstimate = CountThenEstimate.mergeEstimators(ctes).cardinality(); double error = Math.abs(mergedEstimate - expectedCardinality) / (double) expectedCardinality; assertEquals(0.01, error, 0.01); for (int i = 0; i < numToMerge / 2; i++) { for (int j = tippingPoint - 1; j < cardinality; j++) { ctes[i].offer(Math.random()); } } expectedCardinality = (numToMerge / 2) * (cardinality + tippingPoint - 1); mergedEstimate = CountThenEstimate.mergeEstimators(ctes).cardinality(); error = Math.abs(mergedEstimate - expectedCardinality) / (double) expectedCardinality; assertEquals(0.01, error, 0.01); for (int i = numToMerge / 2; i < numToMerge; i++) { for (int j = tippingPoint - 1; j < cardinality; j++) { ctes[i].offer(Math.random()); } } expectedCardinality = numToMerge * cardinality; mergedEstimate = CountThenEstimate.mergeEstimators(ctes).cardinality(); error = Math.abs(mergedEstimate - expectedCardinality) / (double) expectedCardinality; assertEquals(0.01, error, 0.01); }