LogLog implements IRichCardinality { @Override public IRichCardinality merge(IRichCardinality... estimators) throws LogLogMergeException { if (estimators == null) { return new LogLog(M); } byte[] mergedBytes = Arrays.copyOf(this.M, this.M.length); for (IRichCardinality estimator : estimators) { if (!(this.getClass().isInstance(estimator))) { throw new LogLogMergeException("Cannot merge estimators of different class"); } if (estimator.sizeof() != this.sizeof()) { throw new LogLogMergeException("Cannot merge estimators of different sizes"); } LogLog ll = (LogLog) estimator; for (int i = 0; i < mergedBytes.length; ++i) { mergedBytes[i] = (byte) Math.max(mergedBytes[i], ll.M[i]); } } return new LogLog(mergedBytes); } LogLog(int k);  LogLog(byte[] M); @Override byte[] getBytes(); int sizeof(); @Override long cardinality(); @Override boolean offerHashed(long hashedLong); @Override boolean offerHashed(int hashedInt); @Override boolean offer(Object o); @Override IRichCardinality merge(IRichCardinality... estimators); static LogLog mergeEstimators(LogLog... estimators);  }
@Test public void testMerge() throws CardinalityMergeException { int numToMerge = 5; int bits = 16; int cardinality = 1000000; LogLog[] loglogs = new LogLog[numToMerge]; LogLog baseline = new LogLog(bits); for (int i = 0; i < numToMerge; i++) { loglogs[i] = new LogLog(bits); for (int j = 0; j < cardinality; j++) { double val = Math.random(); loglogs[i].offer(val); baseline.offer(val); } } LogLog hll = loglogs[0]; loglogs = Arrays.asList(loglogs).subList(1, loglogs.length).toArray(new LogLog[0]); long mergedEstimate = hll.merge(loglogs).cardinality(); long baselineEstimate = baseline.cardinality(); System.out.println("Baseline estimate: " + baselineEstimate); assertEquals(mergedEstimate, baselineEstimate); }