performs only one call to the underlying func with unique rowids the func
    must be some lookup function

    TODO: move this to a better place.

    CommandLine:
        python -m utool.util_list --test-unflat_unique_rowid_map:0
        python -m utool.util_list --test-unflat_unique_rowid_map:1

    Example0:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_list import *  # NOQA
        >>> import utool as ut
        >>> kwargs = {}
        >>> unflat_rowids = [[1, 2, 3], [2, 5], [1], []]
        >>> num_calls0 = [0]
        >>> num_input0 = [0]
        >>> def func0(rowids, num_calls0=num_calls0, num_input0=num_input0):
        ...     num_calls0[0] += 1
        ...     num_input0[0] += len(rowids)
        ...     return [rowid + 10 for rowid in rowids]
        >>> func = func0
        >>> unflat_vals = unflat_unique_rowid_map(func, unflat_rowids, **kwargs)
        >>> result = [arr.tolist() for arr in unflat_vals]
        >>> print(result)
        >>> ut.assert_eq(num_calls0[0], 1)
        >>> ut.assert_eq(num_input0[0], 4)
        [[11, 12, 13], [12, 15], [11], []]

    Example1:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_list import *  # NOQA
        >>> import utool as ut
        >>> import numpy as np
        >>> kwargs = {}
        >>> unflat_rowids = [[1, 2, 3], [2, 5], [1], []]
        >>> num_calls1 = [0]
        >>> num_input1 = [0]
        >>> def func1(rowids, num_calls1=num_calls1, num_input1=num_input1, np=np):
        ...     num_calls1[0] += 1
        ...     num_input1[0] += len(rowids)
        ...     return [np.array([rowid + 10, rowid, 3]) for rowid in rowids]
        >>> func = func1
        >>> unflat_vals = unflat_unique_rowid_map(func, unflat_rowids, **kwargs)
        >>> result = [arr.tolist() for arr in unflat_vals]
        >>> print(result)
        >>> ut.assert_eq(num_calls1[0], 1)
        >>> ut.assert_eq(num_input1[0], 4)
        [[[11, 1, 3], [12, 2, 3], [13, 3, 3]], [[12, 2, 3], [15, 5, 3]], [[11, 1, 3]], []]

def unflat_unique_rowid_map(func, unflat_rowids, **kwargs):
    """
    performs only one call to the underlying func with unique rowids the func
    must be some lookup function

    TODO: move this to a better place.

    CommandLine:
        python -m utool.util_list --test-unflat_unique_rowid_map:0
        python -m utool.util_list --test-unflat_unique_rowid_map:1

    Example0:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_list import *  # NOQA
        >>> import utool as ut
        >>> kwargs = {}
        >>> unflat_rowids = [[1, 2, 3], [2, 5], [1], []]
        >>> num_calls0 = [0]
        >>> num_input0 = [0]
        >>> def func0(rowids, num_calls0=num_calls0, num_input0=num_input0):
        ...     num_calls0[0] += 1
        ...     num_input0[0] += len(rowids)
        ...     return [rowid + 10 for rowid in rowids]
        >>> func = func0
        >>> unflat_vals = unflat_unique_rowid_map(func, unflat_rowids, **kwargs)
        >>> result = [arr.tolist() for arr in unflat_vals]
        >>> print(result)
        >>> ut.assert_eq(num_calls0[0], 1)
        >>> ut.assert_eq(num_input0[0], 4)
        [[11, 12, 13], [12, 15], [11], []]

    Example1:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_list import *  # NOQA
        >>> import utool as ut
        >>> import numpy as np
        >>> kwargs = {}
        >>> unflat_rowids = [[1, 2, 3], [2, 5], [1], []]
        >>> num_calls1 = [0]
        >>> num_input1 = [0]
        >>> def func1(rowids, num_calls1=num_calls1, num_input1=num_input1, np=np):
        ...     num_calls1[0] += 1
        ...     num_input1[0] += len(rowids)
        ...     return [np.array([rowid + 10, rowid, 3]) for rowid in rowids]
        >>> func = func1
        >>> unflat_vals = unflat_unique_rowid_map(func, unflat_rowids, **kwargs)
        >>> result = [arr.tolist() for arr in unflat_vals]
        >>> print(result)
        >>> ut.assert_eq(num_calls1[0], 1)
        >>> ut.assert_eq(num_input1[0], 4)
        [[[11, 1, 3], [12, 2, 3], [13, 3, 3]], [[12, 2, 3], [15, 5, 3]], [[11, 1, 3]], []]

    """
    import utool as ut
    # First flatten the list, and remember the original dimensions
    flat_rowids, reverse_list = ut.invertible_flatten2(unflat_rowids)
    # Then make the input unique
    flat_rowids_arr = np.array(flat_rowids)
    unique_flat_rowids, inverse_unique = np.unique(flat_rowids_arr, return_inverse=True)
    # Then preform the lookup / implicit mapping
    unique_flat_vals = func(unique_flat_rowids, **kwargs)
    # Then broadcast unique values back to original flat positions
    flat_vals_ = np.array(unique_flat_vals)[inverse_unique]
    #flat_vals_ = np.array(unique_flat_vals).take(inverse_unique, axis=0)
    output_shape = tuple(list(flat_rowids_arr.shape) + list(flat_vals_.shape[1:]))
    flat_vals = np.array(flat_vals_).reshape(output_shape)
    # Then _unflatten the results to the original input dimensions
    unflat_vals = ut.unflatten2(flat_vals, reverse_list)
    return unflat_vals