docs: public FrequentItemsetsResult run(Database db, final Relation<BitVector> relation) {
      // TODO: implement with resizable array, to not need dim.
      final int dim = RelationUtil.dimensionality(relation);
      final VectorFieldTypeInformation<BitVector> meta = RelationUtil.assumeVectorField(relation);
      // Compute absolute minsupport
      final int minsupp = getMinimumSupport(relation.size());
  
      LOG.verbose("Finding item frequencies for ordering.");
      final int[] counts = countItemSupport(relation, dim);
      // Forward and backward indexes
      int[] iidx = new int[dim];
      final int[] idx = buildIndex(counts, iidx, minsupp);
      final int items = idx.length;
  
      LOG.statistics(new LongStatistic(STAT + "raw-items", dim));
      LOG.statistics(new LongStatistic(STAT + "raw-transactions", relation.size()));
      LOG.statistics(new DoubleStatistic(STAT + "minsupp-relative", minsupp / (double) relation.size()));
      LOG.statistics(new LongStatistic(STAT + "minsupp-absolute", minsupp));
  
      LOG.verbose("Building FP-Tree.");
      Duration ctime = LOG.newDuration(STAT + "fp-tree.construction.time").begin();
      FPTree tree = buildFPTree(relation, iidx, items);
      if(LOG.isStatistics()) {
        tree.logStatistics();
      }
      if(LOG.isDebuggingFinest()) {
        StringBuilder buf = new StringBuilder(10000).append("FP-tree:\n");
        tree.appendTo(buf, new FPNode.Translator() {
          @Override
          public StringBuilder appendTo(StringBuilder buf, int i) {
            String l = meta.getLabel(idx[i]);
            return (l != null) ? buf.append(l) : buf.append(i);
          }
        });
        LOG.debugFinest(buf.toString());
      }
      // Reduce memory usage:
      tree.reduceMemory();
      LOG.statistics(ctime.end());
  
      LOG.verbose("Extracting frequent patterns.");
      Duration etime = LOG.newDuration(STAT + "fp-growth.extraction.time").begin();
      final IndefiniteProgress itemp = LOG.isVerbose() ? new IndefiniteProgress("Frequent itemsets", LOG) : null;
      final List<Itemset> solution = new ArrayList<>();
      // Start extraction with the least frequent items
      tree.extract(minsupp, minlength, maxlength, true, new FPTree.Collector() {
        @Override
        public void collect(int support, int[] data, int start, int plen) {
          // Always translate the indexes back to the original values via 'idx'!
          if(plen - start == 1) {
            solution.add(new OneItemset(idx[data[start]], support));
            LOG.incrementProcessed(itemp);
            return;
          }
          // Copy from buffer to a permanent storage
          int[] indices = new int[plen - start];
          for(int i = start, j = 0; i < plen; i++) {
            indices[j++] = idx[data[i]]; // Translate to original items
          }
          Arrays.sort(indices);
          solution.add(new SparseItemset(indices, support));
          LOG.incrementProcessed(itemp);
        }
      });
      LOG.setCompleted(itemp);
      Collections.sort(solution);
      LOG.statistics(etime.end());
      LOG.statistics(new LongStatistic(STAT + "frequent-itemsets", solution.size()));
  
      return new FrequentItemsetsResult("FP-Growth", "fp-growth", solution, meta, relation.size());
    }