use crate::problem::traits::Problem;
use crate::solution::Solution;
use crate::utils::random::Random;
use std::collections::HashMap;
const PENALTY: f64 = 0.5;
#[derive(Clone)]
pub struct KnapsackProblem {
description: String,
number_of_items: usize,
capacity: f64,
weights: Vec<f64>,
values: Vec<f64>,
}
impl KnapsackProblem {
pub fn with_data(
capacity: f64,
weights: Vec<f64>,
values: Vec<f64>,
random_seed: Option<u64>,
) -> Self {
assert_eq!(
weights.len(),
values.len(),
"Weights and values must have the same length"
);
let number_of_items = weights.len();
let _ = random_seed;
KnapsackProblem {
description: format!(
"Knapsack Problem with {} items and capacity {}",
number_of_items, capacity
),
number_of_items,
capacity,
weights,
values,
}
}
fn calculate_weight(&self, solution: &Solution<bool>) -> f64 {
let mut total_weight = 0.0;
for i in 0..self.number_of_items {
if let Some(&selected) = solution.get_variable(i) {
if selected {
total_weight += self.weights[i];
}
}
}
total_weight
}
fn calculate_value(&self, solution: &Solution<bool>) -> f64 {
let mut total_value = 0.0;
for i in 0..self.number_of_items {
if let Some(&selected) = solution.get_variable(i) {
if selected {
total_value += self.values[i];
}
}
}
total_value
}
}
impl Problem<bool> for KnapsackProblem {
fn new() -> Self {
KnapsackProblem {
description: "Knapsack Problem".to_string(),
number_of_items: 0,
capacity: 0.0,
weights: vec![],
values: vec![],
}
}
fn evaluate(&self, solution: &mut Solution<bool>) {
let weight = self.calculate_weight(solution);
let value = self.calculate_value(solution);
let _fitness = if weight > self.capacity {
-(weight - self.capacity) * PENALTY } else {
value };
solution.set_quality(_fitness);
}
fn dominates(&self, solution_a: &Solution<bool, f64>, solution_b: &Solution<bool, f64>) -> bool {
let fitness_a = solution_a.quality().copied().unwrap_or(f64::NEG_INFINITY);
let fitness_b = solution_b.quality().copied().unwrap_or(f64::NEG_INFINITY);
fitness_a > fitness_b
}
fn create_solution(&self, _rng: &mut Random) -> Solution<bool> {
let mut variables: Vec<bool> = vec![];
for _ in 0..self.number_of_items {
variables.push(_rng.coin_flip());
}
Solution::new(variables)
}
fn set_problem_description(&mut self, description: String) {
self.description = description;
}
fn get_problem_description(&self) -> String {
self.description.clone()
}
fn better_fitness_fn(&self) -> fn(f64, f64) -> bool {
crate::solution::traits::evaluator::maximizing_fitness
}
fn format_solution(&self, solution: &Solution<bool>) -> String {
let selected_items = solution
.variables()
.iter()
.filter(|&&selected| selected)
.count();
let total_weight = self.calculate_weight(solution);
let total_value = self.calculate_value(solution);
let feasible = total_weight <= self.capacity;
let quality_text = solution
.try_quality_value()
.map(|value| format!("{:.3}", value))
.unwrap_or_else(|| "not evaluated".to_string());
format!(
"selected={}/{}, weight={:.3}/{:.3}, value={:.3}, feasible={}, quality={}",
selected_items,
self.number_of_items,
total_weight,
self.capacity,
total_value,
feasible,
quality_text
)
}
fn get_problem_parameters_payload(&self) -> String {
format!(
"capacity={:.3}, items=[{}]",
self.capacity,
self.weights
.iter()
.zip(self.values.iter())
.map(|(w, v)| format!("(weight={:.3}, value={:.3})", w, v))
.collect::<Vec<String>>()
.join(", ")
)
}
}
pub struct KnapsackBuilder {
capacity: f64,
weights: Vec<f64>,
values: Vec<f64>,
}
impl KnapsackBuilder {
pub fn new() -> Self {
Self {
capacity: 100.0,
weights: vec![],
values: vec![],
}
}
pub fn with_capacity(mut self, capacity: f64) -> Self {
self.capacity = capacity;
self
}
pub fn add_item(mut self, weight: f64, value: f64) -> Self {
self.weights.push(weight);
self.values.push(value);
self
}
pub fn add_items(mut self, items: Vec<(f64, f64)>) -> Self {
for (weight, value) in items {
self.weights.push(weight);
self.values.push(value);
}
self
}
pub fn build(self) -> KnapsackProblem {
KnapsackProblem::with_data(self.capacity, self.weights, self.values, None)
}
}
impl Default for KnapsackBuilder {
fn default() -> Self {
Self::new()
}
}
pub fn build_knapsack_from_records(
records: &[HashMap<String, String>],
capacity: f64,
row_limit: usize,
weight_key: &str,
value_key: &str,
) -> Result<(KnapsackProblem, usize), String> {
if records.is_empty() {
return Err("Input data has no records".to_string());
}
let mut builder = KnapsackBuilder::new().with_capacity(capacity);
let mut loaded_items = 0usize;
for record in records.iter().take(row_limit) {
let Some(weight_text) = record.get(weight_key) else {
continue;
};
let Some(value_text) = record.get(value_key) else {
continue;
};
let Ok(weight) = weight_text.parse::<f64>() else {
continue;
};
let Ok(value) = value_text.parse::<f64>() else {
continue;
};
builder = builder.add_item(weight, value);
loaded_items += 1;
}
if loaded_items == 0 {
return Err(format!(
"No valid records found. Ensure keys '{}' and '{}' exist and are numeric",
weight_key, value_key
));
}
Ok((builder.build(), loaded_items))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn problem_description_test() {
let mut knapsack_problem = KnapsackProblem::new();
let description = "Test Problem".to_string();
knapsack_problem.set_problem_description(description.clone());
assert_eq!(knapsack_problem.description, description);
}
#[test]
fn test_knapsack_creation_with_data() {
let weights = vec![10.0, 20.0, 30.0];
let values = vec![100.0, 200.0, 300.0];
let capacity = 50.0;
let problem = KnapsackProblem::with_data(capacity, weights, values, Some(100));
let solution = problem.create_solution(&mut Random::new(10));
assert_eq!(solution.num_variables(), 3);
}
#[test]
fn test_knapsack_builder() {
let problem = KnapsackBuilder::new()
.with_capacity(50.0)
.add_item(10.0, 100.0)
.add_item(20.0, 200.0)
.build();
let solution = problem.create_solution(&mut Random::new(10));
assert_eq!(solution.num_variables(), 2);
}
#[test]
fn test_knapsack_with_builder() {
let problem = KnapsackBuilder::new()
.with_capacity(100.0)
.add_item(10.0, 50.0)
.add_item(20.0, 100.0)
.add_item(30.0, 150.0)
.build();
let sol = problem.create_solution(&mut Random::new(10));
assert_eq!(sol.num_variables(), 3);
let mut variables: Vec<bool> = vec![];
for _ in 0..3 {
variables.push(true);
}
let mut solution = Solution::new(variables);
problem.evaluate(&mut solution);
assert!(solution.quality().copied().unwrap() > 0.0);
}
#[test]
fn knapsack_uses_maximizing_fitness() {
let problem = KnapsackBuilder::new().build();
assert!(problem.is_better_fitness(10.0, 5.0));
assert!(!problem.is_better_fitness(5.0, 10.0));
}
#[test]
fn format_solution_reports_domain_summary() {
let problem = KnapsackBuilder::new()
.with_capacity(50.0)
.add_item(10.0, 100.0)
.add_item(20.0, 200.0)
.add_item(30.0, 300.0)
.build();
let mut solution = Solution::new(vec![true, false, true]);
problem.evaluate(&mut solution);
let formatted = problem.format_solution(&solution);
assert!(formatted.contains("selected=2/3"));
assert!(formatted.contains("weight=40.000/50.000"));
assert!(formatted.contains("value=400.000"));
assert!(formatted.contains("feasible=true"));
assert!(formatted.contains("quality=400.000"));
}
}