Yields `num` items from the cartesian product of items in a random order.

    Args:
        items (list of sequences): items to get caresian product of
            packed in a list or tuple.
            (note this deviates from api of it.product)

    Example:
        import utool as ut
        items = [(1, 2, 3), (4, 5, 6, 7)]
        rng = 0
        list(ut.random_product(items, rng=0))
        list(ut.random_product(items, num=3, rng=0))

def random_product(items, num=None, rng=None):
    """
    Yields `num` items from the cartesian product of items in a random order.

    Args:
        items (list of sequences): items to get caresian product of
            packed in a list or tuple.
            (note this deviates from api of it.product)

    Example:
        import utool as ut
        items = [(1, 2, 3), (4, 5, 6, 7)]
        rng = 0
        list(ut.random_product(items, rng=0))
        list(ut.random_product(items, num=3, rng=0))
    """
    import utool as ut
    rng = ut.ensure_rng(rng, 'python')
    seen = set()
    items = [list(g) for g in items]
    max_num = ut.prod(map(len, items))
    if num is None:
        num = max_num
    if num > max_num:
        raise ValueError('num exceedes maximum number of products')

    # TODO: make this more efficient when num is large
    if num > max_num // 2:
        for prod in ut.shuffle(list(it.product(*items)), rng=rng):
            yield prod
    else:
        while len(seen) < num:
            # combo = tuple(sorted(rng.choice(items, size, replace=False)))
            idxs = tuple(rng.randint(0, len(g) - 1) for g in items)
            if idxs not in seen:
                seen.add(idxs)
                prod = tuple(g[x] for g, x in zip(items, idxs))
                yield prod