1+ """
2+ In this problem, we want to determine all possible combinations of k
3+ numbers out of 1 ... n. We use backtracking to solve this problem.
4+
5+ Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!))),
6+ """
7+
8+ from __future__ import annotations
9+
10+ from itertools import combinations
11+
12+
13+ def combination_lists (n : int , k : int ) -> list [list [int ]]:
14+ """
15+ Generates all possible combinations of k numbers out of 1 ... n using itertools.
16+
17+ >>> combination_lists(n=4, k=2)
18+ [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
19+ """
20+ return [list (x ) for x in combinations (range (1 , n + 1 ), k )]
21+
22+
23+ def generate_all_combinations (n : int , k : int ) -> list [list [int ]]:
24+ """
25+ Generates all possible combinations of k numbers out of 1 ... n using backtracking.
26+
27+ >>> generate_all_combinations(n=4, k=2)
28+ [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
29+ >>> generate_all_combinations(n=0, k=0)
30+ [[]]
31+ >>> generate_all_combinations(n=10, k=-1)
32+ Traceback (most recent call last):
33+ ...
34+ ValueError: k must not be negative
35+ >>> generate_all_combinations(n=-1, k=10)
36+ Traceback (most recent call last):
37+ ...
38+ ValueError: n must not be negative
39+ >>> generate_all_combinations(n=5, k=4)
40+ [[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]]
41+ >>> generate_all_combinations(n=3, k=3)
42+ [[1, 2, 3]]
43+ >>> generate_all_combinations(n=3, k=1)
44+ [[1], [2], [3]]
45+ >>> generate_all_combinations(n=1, k=0)
46+ [[]]
47+ >>> generate_all_combinations(n=1, k=1)
48+ [[1]]
49+ >>> from itertools import combinations
50+ >>> all(generate_all_combinations(n, k) == combination_lists(n, k)
51+ ... for n in range(1, 6) for k in range(1, 6))
52+ True
53+ """
54+ if k < 0 :
55+ raise ValueError ("k must not be negative" )
56+ if n < 0 :
57+ raise ValueError ("n must not be negative" )
58+
59+ result : list [list [int ]] = []
60+ create_all_state (1 , n , k , [], result )
61+ return result
62+
63+
64+ def create_all_state (
65+ increment : int ,
66+ total_number : int ,
67+ level : int ,
68+ current_list : list [int ],
69+ total_list : list [list [int ]],
70+ ) -> None :
71+ """
72+ Helper function to recursively build all combinations.
73+
74+ >>> create_all_state(1, 4, 2, [], result := [])
75+ >>> result
76+ [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
77+ >>> create_all_state(1, 3, 3, [], result := [])
78+ >>> result
79+ [[1, 2, 3]]
80+ >>> create_all_state(2, 2, 1, [1], result := [])
81+ >>> result
82+ [[1, 2]]
83+ >>> create_all_state(1, 0, 0, [], result := [])
84+ >>> result
85+ [[]]
86+ >>> create_all_state(1, 4, 0, [1, 2], result := [])
87+ >>> result
88+ [[1, 2]]
89+ >>> create_all_state(5, 4, 2, [1, 2], result := [])
90+ >>> result
91+ []
92+ """
93+ if level == 0 :
94+ total_list .append (current_list [:])
95+ return
96+
97+ for i in range (increment , total_number - level + 2 ):
98+ current_list .append (i )
99+ create_all_state (i + 1 , total_number , level - 1 , current_list , total_list )
100+ current_list .pop ()
101+
102+
103+ if __name__ == "__main__" :
104+ from doctest import testmod
105+
106+ testmod ()
107+ print (generate_all_combinations (n = 4 , k = 2 ))
108+ tests = ((n , k ) for n in range (1 , 5 ) for k in range (1 , 5 ))
109+ for n , k in tests :
110+ print (n , k , generate_all_combinations (n , k ) == combination_lists (n , k ))
111+
112+ print ("Benchmark:" )
113+ from timeit import timeit
114+
115+ for func in ("combination_lists" , "generate_all_combinations" ):
116+ print (f"{ func :>25} (): { timeit (f'{ func } (n=4, k = 2)' , globals = globals ())} " )
0 commit comments