Compute the value of a composite distance function on two dictionaries,
    typically SFrame rows.

    Parameters
    ----------
    distance : list[list]
        A composite distance function. Composite distance functions are a
        weighted sum of standard distance functions, each of which applies to
        its own subset of features. Composite distance functions are specified
        as a list of distance components, each of which is itself a list
        containing three items:

          1. list or tuple of feature names (strings)

          2. standard distance name (string)

          3. scaling factor (int or float)

    x, y : dict
        Individual observations, typically rows of an SFrame, in dictionary
        form. Must include the features specified by `distance`.

    Returns
    -------
    out : float
        The distance between `x` and `y`, as specified by `distance`.

    Examples
    --------
    >>> sf = turicreate.SFrame({'X1': [0.98, 0.62, 0.11],
    ...                       'X2': [0.69, 0.58, 0.36],
    ...                       'species': ['cat', 'dog', 'fossa']})
    ...
    >>> dist_spec = [[('X1', 'X2'), 'euclidean', 2],
    ...              [('species',), 'levenshtein', 0.4]]
    ...
    >>> d = turicreate.distances.compute_composite_distance(dist_spec, sf[0], sf[1])
    >>> print d
    1.95286120899

def compute_composite_distance(distance, x, y):
    """
    Compute the value of a composite distance function on two dictionaries,
    typically SFrame rows.

    Parameters
    ----------
    distance : list[list]
        A composite distance function. Composite distance functions are a
        weighted sum of standard distance functions, each of which applies to
        its own subset of features. Composite distance functions are specified
        as a list of distance components, each of which is itself a list
        containing three items:

          1. list or tuple of feature names (strings)

          2. standard distance name (string)

          3. scaling factor (int or float)

    x, y : dict
        Individual observations, typically rows of an SFrame, in dictionary
        form. Must include the features specified by `distance`.

    Returns
    -------
    out : float
        The distance between `x` and `y`, as specified by `distance`.

    Examples
    --------
    >>> sf = turicreate.SFrame({'X1': [0.98, 0.62, 0.11],
    ...                       'X2': [0.69, 0.58, 0.36],
    ...                       'species': ['cat', 'dog', 'fossa']})
    ...
    >>> dist_spec = [[('X1', 'X2'), 'euclidean', 2],
    ...              [('species',), 'levenshtein', 0.4]]
    ...
    >>> d = turicreate.distances.compute_composite_distance(dist_spec, sf[0], sf[1])
    >>> print d
    1.95286120899
    """

    ## Validate inputs
    _validate_composite_distance(distance)
    distance = _convert_distance_names_to_functions(distance)

    if not isinstance(x, dict) or not isinstance(y, dict):
        raise TypeError("Inputs 'x' and 'y' must be in dictionary form. " +
                        "Selecting individual rows of an SFrame yields the " +
                        "correct format.")

    ans = 0.

    for d in distance:
        ftrs, dist, weight = d

        ## Special check for multiple columns with levenshtein distance.
        if dist == _tc.distances.levenshtein and len(ftrs) > 1:
            raise ValueError("levenshtein distance cannot be used with multiple" +
                             "columns. Please concatenate strings into a single " +
                             "column before computing the distance.")

        ## Extract values for specified features.
        a = {}
        b = {}

        for ftr in ftrs:
            if type(x[ftr]) != type(y[ftr]):
                if not isinstance(x[ftr], (int, float)) or not isinstance(y[ftr], (int, float)):
                    raise ValueError("Input data has different types.")

            if isinstance(x[ftr], (int, float, str)):
                a[ftr] = x[ftr]
                b[ftr] = y[ftr]

            elif isinstance(x[ftr], dict):
                for key, val in _six.iteritems(x[ftr]):
                    a['{}.{}'.format(ftr, key)] = val

                for key, val in _six.iteritems(y[ftr]):
                    b['{}.{}'.format(ftr, key)] = val

            elif isinstance(x[ftr], (list, _array.array)):
                for i, val in enumerate(x[ftr]):
                    a[i] = val

                for i, val in enumerate(y[ftr]):
                    b[i] = val

            else:
                raise TypeError("Type of feature '{}' not understood.".format(ftr))


        ## Pull out the raw values for levenshtein
        if dist == _tc.distances.levenshtein:
            a = list(a.values())[0]
            b = list(b.values())[0]

        ## Compute component distance and add to the total distance.
        ans += weight * dist(a, b)

    return ans