Calculate generalized Jaccard similarity of formulas.

    Returns the weighted similarity value or None if there is no overlap
    at all. If the union of the formulas has a weight of zero (i.e. the
    denominator in the Jaccard similarity is zero), a value of zero is
    returned.

def _jaccard_similarity(f1, f2, weight_func):
    """Calculate generalized Jaccard similarity of formulas.

    Returns the weighted similarity value or None if there is no overlap
    at all. If the union of the formulas has a weight of zero (i.e. the
    denominator in the Jaccard similarity is zero), a value of zero is
    returned.
    """
    elements = set(f1)
    elements.update(f2)

    count, w_count, w_total = 0, 0, 0
    for element in elements:
        mi = min(f1.get(element, 0), f2.get(element, 0))
        mx = max(f1.get(element, 0), f2.get(element, 0))
        count += mi
        w = weight_func(element)
        w_count += w * mi
        w_total += w * mx

    if count == 0:
        return None

    return 0.0 if w_total == 0.0 else w_count / w_total