feat(dosages): added library function to calculate optimal combination of medicines for desired dosage
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
/target
|
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
#
|
||||||
|
# already existing elements were commented out
|
||||||
|
|
||||||
|
#/target
|
||||||
Generated
+7
@@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lsdp"
|
||||||
|
version = "0.1.0"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "lsdp"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
use std::{cmp::max, collections::HashMap, ops::Index};
|
||||||
|
|
||||||
|
pub struct ComboNode {
|
||||||
|
combo: Vec<u32>,
|
||||||
|
weight: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for ComboNode {
|
||||||
|
type Output = u32;
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
self.combo.index(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComboNode {
|
||||||
|
pub fn weight(&self) -> u32 {
|
||||||
|
self.weight
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(combo: Vec<u32>) -> Self {
|
||||||
|
let sum = combo.iter().sum();
|
||||||
|
Self { combo, weight: sum }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_neighbours(&self) -> impl Iterator<Item = ComboNode> {
|
||||||
|
(0..self.combo.len()).map(|i| {
|
||||||
|
let mut combo = self.combo.clone();
|
||||||
|
combo[i] += 1;
|
||||||
|
Self {
|
||||||
|
combo,
|
||||||
|
weight: self.weight + 1,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ComboGraph {
|
||||||
|
nodes: HashMap<u32, ComboNode>,
|
||||||
|
values: Vec<u32>,
|
||||||
|
target: u32,
|
||||||
|
queue: Vec<ComboNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComboGraph {
|
||||||
|
pub fn new(mut values: Vec<u32>, target: u32) -> Self {
|
||||||
|
// ensure values is sorted
|
||||||
|
values.sort_unstable();
|
||||||
|
let num_values = values.len();
|
||||||
|
Self {
|
||||||
|
nodes: HashMap::new(),
|
||||||
|
values,
|
||||||
|
target,
|
||||||
|
queue: vec![ComboNode::new(vec![0; num_values])],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn closest_node_target(&self) -> Option<&ComboNode> {
|
||||||
|
let closest = self
|
||||||
|
.nodes
|
||||||
|
.iter()
|
||||||
|
// Treat a zero difference as a difference of 1 to accomodate for rounding
|
||||||
|
.map(|(pos, combo)| (max(self.target.abs_diff(*pos), 1), combo.weight(), pos))
|
||||||
|
.min()?;
|
||||||
|
|
||||||
|
self.nodes.get(&closest.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visit a combo and calculate its position and weight
|
||||||
|
pub fn visit(&mut self, node: ComboNode) -> bool {
|
||||||
|
let mut pos = 0;
|
||||||
|
|
||||||
|
for i in 0..self.values.len() {
|
||||||
|
pos += self.values[i] * node[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if pos already found with less weight
|
||||||
|
let current = self.nodes.get(&pos);
|
||||||
|
if let Some(combo) = current {
|
||||||
|
if combo.weight() > node.weight() {
|
||||||
|
// should never happen
|
||||||
|
panic!("Found better path second");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else if pos == self.target {
|
||||||
|
self.nodes.insert(pos, node);
|
||||||
|
return true;
|
||||||
|
} else if pos < self.target {
|
||||||
|
node.get_neighbours().for_each(|n| {
|
||||||
|
self.queue.push(n);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.nodes.insert(pos, node);
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// grows the graph to find the optimal path
|
||||||
|
pub fn grow(&mut self) -> bool {
|
||||||
|
let mut next = self.queue.pop();
|
||||||
|
while let Some(node) = next {
|
||||||
|
if self.visit(node) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
next = self.queue.pop();
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target_combo(&self) -> Option<Vec<u32>> {
|
||||||
|
let combo = self.nodes.get(&self.target)?;
|
||||||
|
Some(
|
||||||
|
(0..self.values.len())
|
||||||
|
.map(|i| vec![self.values[i]; combo[i] as usize])
|
||||||
|
.flatten()
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn closest_combo(&self) -> Option<Vec<u32>> {
|
||||||
|
let combo = self.closest_node_target()?;
|
||||||
|
Some(
|
||||||
|
(0..self.values.len())
|
||||||
|
.map(|i| vec![self.values[i]; combo[i] as usize])
|
||||||
|
.flatten()
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_optimal_combo(strengths: Vec<u32>, target: u32, rounded: bool) -> Vec<u32> {
|
||||||
|
let mut graph = ComboGraph::new(strengths, target);
|
||||||
|
if graph.grow() && !rounded {
|
||||||
|
graph.target_combo().expect("grow signalled target hit")
|
||||||
|
} else {
|
||||||
|
graph.closest_combo().expect("non empty graph")
|
||||||
|
}
|
||||||
|
}
|
||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
/// calculate greatest common factor
|
||||||
|
/// requires a > b
|
||||||
|
fn gcf(mut a: u32, mut b: u32) -> u32 {
|
||||||
|
let mut rem = a % b;
|
||||||
|
|
||||||
|
while rem != 0 {
|
||||||
|
a = b;
|
||||||
|
b = rem;
|
||||||
|
rem = a % b;
|
||||||
|
}
|
||||||
|
b
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate gcf of vector
|
||||||
|
fn gcfv(v: &Vec<u32>) -> u32 {
|
||||||
|
let mut i = 1;
|
||||||
|
let mut cgcf = v[0]; // start with first item
|
||||||
|
let mut a;
|
||||||
|
let mut b;
|
||||||
|
while i < v.len() {
|
||||||
|
if cgcf >= v[i] {
|
||||||
|
a = cgcf;
|
||||||
|
b = v[i];
|
||||||
|
} else {
|
||||||
|
a = v[i];
|
||||||
|
b = cgcf;
|
||||||
|
}
|
||||||
|
cgcf = gcf(a, b);
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cgcf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn factorise(strengths: &mut Vec<u32>) -> u32 {
|
||||||
|
let factor = gcfv(strengths);
|
||||||
|
if factor == 1 {
|
||||||
|
return factor;
|
||||||
|
}
|
||||||
|
for s in strengths.iter_mut() {
|
||||||
|
*s = *s / factor;
|
||||||
|
}
|
||||||
|
factor
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gcf_base_case() {
|
||||||
|
assert_eq!(gcf(1, 1), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gcf_standard_case() {
|
||||||
|
assert_eq!(gcf(1071, 462), 21)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gcf_coprime_case() {
|
||||||
|
assert_eq!(gcf(1071, 461), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gcfv_base_case() {
|
||||||
|
assert_eq!(gcfv(&vec![1]), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gcfv_standard_case() {
|
||||||
|
assert_eq!(gcfv(&vec![1071, 462, 42, 105]), 21)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gcfv_coprime_case() {
|
||||||
|
assert_eq!(gcfv(&vec![1071, 105, 42, 461]), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn factorise_base_case() {
|
||||||
|
let mut s = vec![1];
|
||||||
|
assert_eq!(factorise(&mut s), 1);
|
||||||
|
assert_eq!(s, vec![1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn factorise_standard_case() {
|
||||||
|
let mut s = vec![1071, 462, 42, 105];
|
||||||
|
assert_eq!(factorise(&mut s), 21);
|
||||||
|
assert_eq!(s, vec![51, 22, 2, 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn factorise_coprime_case() {
|
||||||
|
let mut s = vec![1071, 105, 42, 461];
|
||||||
|
assert_eq!(factorise(&mut s), 1);
|
||||||
|
assert_eq!(s, vec![1071, 105, 42, 461]);
|
||||||
|
}
|
||||||
|
}
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
mod combo_graph;
|
||||||
|
mod factor;
|
||||||
|
|
||||||
|
use crate::combo_graph::find_optimal_combo;
|
||||||
|
use crate::factor::factorise;
|
||||||
|
|
||||||
|
/// Algorithm:
|
||||||
|
/// 1. factorise strengths by GCF
|
||||||
|
/// 2. Perform DFS search on strength combos starting from full largest strength
|
||||||
|
/// 3. use efficiencies from dynamic programming to prune cases already calculated
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use lsdp::calculate_combo;
|
||||||
|
/// let combo = calculate_combo(173, vec![20, 57]);
|
||||||
|
/// assert_eq!(combo, vec![20, 20, 20, 57, 57]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn calculate_combo(mut target: u32, mut strengths: Vec<u32>) -> Vec<u32> {
|
||||||
|
if strengths.len() == 0 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
let factor = factorise(&mut strengths);
|
||||||
|
let mut rounded = false;
|
||||||
|
|
||||||
|
if factor != 1 {
|
||||||
|
if target % factor != 0 {
|
||||||
|
let f_target: f64 = target as f64 / factor as f64;
|
||||||
|
target = f_target.round() as u32;
|
||||||
|
rounded = true;
|
||||||
|
} else {
|
||||||
|
target /= factor;
|
||||||
|
rounded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let combo = find_optimal_combo(strengths, target, rounded);
|
||||||
|
|
||||||
|
combo.iter().map(|s| s * factor).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn base_case() {
|
||||||
|
assert_eq!(calculate_combo(1, vec![1]), vec![1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic() {
|
||||||
|
assert_eq!(
|
||||||
|
calculate_combo(245, vec![50, 20, 5]),
|
||||||
|
vec![5, 20, 20, 50, 50, 50, 50]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_close() {
|
||||||
|
assert_eq!(
|
||||||
|
calculate_combo(245, vec![50, 20, 4]),
|
||||||
|
vec![4, 20, 20, 50, 50, 50, 50]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_exact() {
|
||||||
|
assert_eq!(calculate_combo(173, vec![20, 57]), vec![20, 20, 20, 57, 57]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user