public Sketch<?> merge(Sketch<?> left, Sketch<?> right) {
        if (left instanceof NormalSketch && right instanceof NormalSketch) {
            return mergeNormalWithNormal(asNormal(left), asNormal(right));
        } else if (left instanceof NormalSketch && right instanceof SparseSketch) {
            return mergeNormalWithSparse(asNormal(left), asSparse(right));
        } else if (left instanceof SparseSketch && right instanceof NormalSketch) {
            return mergeNormalWithSparse(asNormal(right), asSparse(left));
        } else if (left instanceof SparseSketch && right instanceof SparseSketch) {
            return mergeSparseWithSparse(asSparse(left), asSparse(right));
        } else {
            throw new IllegalArgumentException(
                    String.format("Invalid sketch types: left=%s, right=%s", right.getClass(), left.getClass()));
        }
    }
@Test
    public void requireThatMergingTwoThresholdSizeSparseSketchesReturnsNormalSketch() {
        SparseSketch s1 = SketchUtils.createSparseSketch();
        SparseSketch s2 = SketchUtils.createSparseSketch();

        // Fill sketches with disjoint data.
        for (int i = 0; i < HyperLogLog.SPARSE_SKETCH_CONVERSION_THRESHOLD; i++) {
            s1.aggregate(i);
            s2.aggregate(i + HyperLogLog.SPARSE_SKETCH_CONVERSION_THRESHOLD);
        }

        Sketch<?> result = merger.merge(s1, s2);
        assertEquals(result.getClass(), NormalSketch.class);

        List<Integer> unionOfSketchData = new ArrayList<>();
        unionOfSketchData.addAll(s1.data());
        unionOfSketchData.addAll(s2.data());
        Integer[] expectedValues = unionOfSketchData.toArray(new Integer[unionOfSketchData.size()]);
        SketchUtils.assertSketchContains(result, expectedValues);
    }