import numpy as np

def generate_combos(n, k, z):
    full_locs = np.arange(k + 1, dtype=np.uint)
    full_locs[k] = n                      # makes partial vectorization easier
    locs = full_locs[:k]                  # bit indices
    counts = np.zeros(n, dtype=np.uint)   # counter buckets
    values = np.arange(n, dtype=np.uint)  # values
    min_index = 0                         # index of lowest non-exhausted bin

    for _ in range((n * z) // k):
        counts[locs] += 1
        yield values[locs]

        if counts[min_index] == z:
            # if lowest bin filled, shift and reset
            min_index += np.argmax(counts[min_index:] < z)
            locs[:] = min_index + np.arange(k)
        else:
            # otherwise, increment highest available counter
            i = np.flatnonzero(np.diff(full_locs))[-1]
            locs[i] += 1
            # reset the remainder
            locs[i + 1:] = locs[i] + np.arange(1, k - i)

for i, v in enumerate(generate_combos(256, 3, 10)):
    print(v)
    if i == 99:
        break

