# https://stackoverflow.com/a/75409015/1468366
import collections.abc
import itertools
import math
import typing
T = typing.TypeVar('T')
def combination_batches(
seq: collections.abc.Sequence[T],
r: int,
max_batch_size: int,
prefix: tuple[T, ...] = ()
) -> collections.abc.Iterator[collections.abc.Iterator[tuple[T, ...]]]:
"""Compute batches of combinations.
Each yielded value is itself a generator over some of the combinations.
Taken together they produce all the combinations.
Args:
seq: The sequence of elements to choose from.
r: The number of elements to include in each combination.
max_batch_size: How many elements each returned iterator
is allowed to iterate over.
prefix: Used during recursive calls, prepended to each returned tuple.
Yields: generators which each generate a subset of all the combinations,
in a way that generators together yield every combination exactly once.
"""
if math.comb(len(seq), r) > max_batch_size:
# One option: first element taken.
yield from combination_batches(
seq[1:], r - 1, max_batch_size, prefix + (seq[0],))
# Other option: first element not taken.
yield from combination_batches(
seq[1:], r, max_batch_size, prefix)
return
yield (prefix + i for i in itertools.combinations(seq, r))
for i in combination_batches("abcdefg", 3, 5):
print("---")
for j in i:
print("".join(j))
IyBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL2EvNzU0MDkwMTUvMTQ2ODM2NgoKaW1wb3J0IGNvbGxlY3Rpb25zLmFiYwppbXBvcnQgaXRlcnRvb2xzCmltcG9ydCBtYXRoCmltcG9ydCB0eXBpbmcKClQgPSB0eXBpbmcuVHlwZVZhcignVCcpCmRlZiBjb21iaW5hdGlvbl9iYXRjaGVzKAogICAgICAgIHNlcTogY29sbGVjdGlvbnMuYWJjLlNlcXVlbmNlW1RdLAogICAgICAgIHI6IGludCwKICAgICAgICBtYXhfYmF0Y2hfc2l6ZTogaW50LAogICAgICAgIHByZWZpeDogdHVwbGVbVCwgLi4uXSA9ICgpCiAgICApIC0+IGNvbGxlY3Rpb25zLmFiYy5JdGVyYXRvcltjb2xsZWN0aW9ucy5hYmMuSXRlcmF0b3JbdHVwbGVbVCwgLi4uXV1dOgogICAgIiIiQ29tcHV0ZSBiYXRjaGVzIG9mIGNvbWJpbmF0aW9ucy4KCiAgICBFYWNoIHlpZWxkZWQgdmFsdWUgaXMgaXRzZWxmIGEgZ2VuZXJhdG9yIG92ZXIgc29tZSBvZiB0aGUgY29tYmluYXRpb25zLgogICAgVGFrZW4gdG9nZXRoZXIgdGhleSBwcm9kdWNlIGFsbCB0aGUgY29tYmluYXRpb25zLgoKICAgIEFyZ3M6CiAgICAgIHNlcTogVGhlIHNlcXVlbmNlIG9mIGVsZW1lbnRzIHRvIGNob29zZSBmcm9tLgogICAgICByOiBUaGUgbnVtYmVyIG9mIGVsZW1lbnRzIHRvIGluY2x1ZGUgaW4gZWFjaCBjb21iaW5hdGlvbi4KICAgICAgbWF4X2JhdGNoX3NpemU6IEhvdyBtYW55IGVsZW1lbnRzIGVhY2ggcmV0dXJuZWQgaXRlcmF0b3IKICAgICAgICBpcyBhbGxvd2VkIHRvIGl0ZXJhdGUgb3Zlci4KICAgICAgcHJlZml4OiBVc2VkIGR1cmluZyByZWN1cnNpdmUgY2FsbHMsIHByZXBlbmRlZCB0byBlYWNoIHJldHVybmVkIHR1cGxlLgogICAgWWllbGRzOiBnZW5lcmF0b3JzIHdoaWNoIGVhY2ggZ2VuZXJhdGUgYSBzdWJzZXQgb2YgYWxsIHRoZSBjb21iaW5hdGlvbnMsCiAgICAgIGluIGEgd2F5IHRoYXQgZ2VuZXJhdG9ycyB0b2dldGhlciB5aWVsZCBldmVyeSBjb21iaW5hdGlvbiBleGFjdGx5IG9uY2UuCiAgICAiIiIKICAgIGlmIG1hdGguY29tYihsZW4oc2VxKSwgcikgPiBtYXhfYmF0Y2hfc2l6ZToKICAgICAgICAjIE9uZSBvcHRpb246IGZpcnN0IGVsZW1lbnQgdGFrZW4uCiAgICAgICAgeWllbGQgZnJvbSBjb21iaW5hdGlvbl9iYXRjaGVzKAogICAgICAgICAgICBzZXFbMTpdLCByIC0gMSwgbWF4X2JhdGNoX3NpemUsIHByZWZpeCArIChzZXFbMF0sKSkKICAgICAgICAjIE90aGVyIG9wdGlvbjogZmlyc3QgZWxlbWVudCBub3QgdGFrZW4uCiAgICAgICAgeWllbGQgZnJvbSBjb21iaW5hdGlvbl9iYXRjaGVzKAogICAgICAgICAgICBzZXFbMTpdLCByLCBtYXhfYmF0Y2hfc2l6ZSwgcHJlZml4KQogICAgICAgIHJldHVybgogICAgeWllbGQgKHByZWZpeCArIGkgZm9yIGkgaW4gaXRlcnRvb2xzLmNvbWJpbmF0aW9ucyhzZXEsIHIpKQoKZm9yIGkgaW4gY29tYmluYXRpb25fYmF0Y2hlcygiYWJjZGVmZyIsIDMsIDUpOgogICAgcHJpbnQoIi0tLSIpCiAgICBmb3IgaiBpbiBpOgogICAgICAgIHByaW50KCIiLmpvaW4oaikpCg==