1414
1515# =========================== Problem setup: Knapsack ===========================
1616
17- KNAPSACK_N_ITEMS : int = 42 # Number of items in the knapsack problem
18- KNAPSACK_VALUE_RANGE : tuple [int , int ] = (10 , 100 ) # Range of item values
19- KNAPSACK_WEIGHT_RANGE : tuple [int , int ] = (5 , 50 ) # Range of item weights
20- KNAPSACK_CAPACITY_RATIO : float = 0.5 # Capacity as a fraction of total weight
17+ KNAPSACK_N_ITEMS : int = 42 # Number of items in the knapsack problem
18+ KNAPSACK_VALUE_RANGE : tuple [int , int ] = (10 , 100 ) # Range of item values
19+ KNAPSACK_WEIGHT_RANGE : tuple [int , int ] = (5 , 50 ) # Range of item weights
20+ KNAPSACK_CAPACITY_RATIO : float = 0.5 # Capacity as a fraction of total weight
21+
2122
2223@dataclass
2324class Item :
2425 value : int
2526 weight : int
2627
28+
2729def generate_knapsack_instance (
2830 n_items : int ,
2931 value_range : tuple [int , int ],
3032 weight_range : tuple [int , int ],
31- capacity_ratio : float
33+ capacity_ratio : float ,
3234) -> tuple [list [Item ], int ]:
3335 """
3436 Generates a random knapsack problem instance.
@@ -63,12 +65,13 @@ def generate_knapsack_instance(
6365 capacity = int (sum (it .weight for it in items ) * capacity_ratio )
6466 return items , capacity
6567
68+
6669# Example instance (guarded by __main__ below for printing)
6770items , capacity = generate_knapsack_instance (
6871 n_items = KNAPSACK_N_ITEMS ,
6972 value_range = KNAPSACK_VALUE_RANGE ,
7073 weight_range = KNAPSACK_WEIGHT_RANGE ,
71- capacity_ratio = KNAPSACK_CAPACITY_RATIO
74+ capacity_ratio = KNAPSACK_CAPACITY_RATIO ,
7275)
7376
7477# ============================== GA Representation ==============================
@@ -86,6 +89,7 @@ def generate_knapsack_instance(
8689
8790Genome = list [int ] # An index list where 1 means item is included, 0 means excluded
8891
92+
8993def evaluate (genome : Genome , items : list [Item ], capacity : int ) -> tuple [int , int ]:
9094 """
9195 Calculates fitness (value) and weight of a candidate solution. If overweight,
@@ -117,7 +121,7 @@ def evaluate(genome: Genome, items: list[Item], capacity: int) -> tuple[int, int
117121 total_value += item .value
118122 total_weight += item .weight
119123 if total_weight > capacity :
120- overflow = ( total_weight - capacity )
124+ overflow = total_weight - capacity
121125 total_value = max (0 , total_value - overflow * OVERWEIGHT_PENALTY_FACTOR )
122126 return total_value , total_weight
123127
@@ -137,6 +141,7 @@ def random_genome(n: int) -> Genome:
137141 """
138142 return [random .randint (0 , 1 ) for _ in range (n )]
139143
144+
140145def selection (population : list [Genome ], fitnesses : list [int ], k : int ) -> Genome :
141146 """
142147 Performs tournament selection to choose a genome from the population.
@@ -191,6 +196,7 @@ def crossover(a: Genome, b: Genome, p_crossover: float) -> tuple[Genome, Genome]
191196 cutoff_point = random .randint (1 , min_length - 1 )
192197 return a [:cutoff_point ] + b [cutoff_point :], b [:cutoff_point ] + a [cutoff_point :]
193198
199+
194200def mutation (g : Genome , p_mutation : float ) -> Genome :
195201 """
196202 Performs bit-flip mutation on a genome. Each bit flips with probability p_mutation.
@@ -301,14 +307,17 @@ def run_ga(
301307 "avg_history" : avg_history ,
302308 }
303309
310+
304311# ================================ Script entry =================================
305312
306313if __name__ == "__main__" :
307314 result = run_ga (items , capacity )
308315 best_items = [items [i ] for i , bit in enumerate (result ["best_genome" ]) if bit == 1 ]
309316
310317 print (f"Knapsack capacity: { result ['capacity' ]} " )
311- print (f"Best solution: value = { result ['best_value' ]} , weight = { result ['best_weight' ]} " )
318+ print (
319+ f"Best solution: value = { result ['best_value' ]} , weight = { result ['best_weight' ]} "
320+ )
312321 # Uncomment to inspect chosen items:
313322 # print("Items included in the best solution:", best_items)
314323
0 commit comments