Commit 84dab23b authored by tleydxdy's avatar tleydxdy

rough CGA implementation

parents
/target
**/*.rs.bk
Cargo.lock
This diff is collapsed.
[package]
name = "mnp-rs"
version = "0.1.0"
authors = ["tleydxdy <shironeko@waifu.club>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
/*
* A Multi-Way Number Partitioning library
* Copyright 2019 Yunxiang Li
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
pub mod mnp {
use std::collections::BTreeSet;
// perfect partitions are either all the same, or the difference are 1
fn is_perfect(k: usize, total: usize, full: &BTreeSet<(usize, Vec<usize>, usize)>) -> bool {
let cost = full.iter().next_back().unwrap().0;
cost * k <= total + k
}
fn _cga(
k: usize,
mut input: Vec<usize>,
total: usize,
mut part: BTreeSet<(usize, Vec<usize>, usize)>,
mut best: usize,
) -> Option<BTreeSet<(usize, Vec<usize>, usize)>> {
match input.pop() {
Some(n) => {
let mut result = None;
// Create k-1 non-zero bins, first one is created by the wrapper.
if part.len() < k {
let mut new_part = part.clone();
new_part.insert((n, vec![n], part.len()));
match _cga(k, input.clone(), total, new_part, best) {
Some(sol) => {
if is_perfect(k, total, &sol) {
return Some(sol);
}
best = sol.iter().next_back().unwrap().0;
result = Some(sol);
}
None => (),
}
}
let mut first_pair = part.iter().next().cloned().unwrap();
let cur_min = first_pair.0;
let cur_max = part.iter().next_back().unwrap().0;
// If all the bins are the same, just put the next number in the first one.
if cur_min == cur_max && cur_min + n < best {
part.remove(&first_pair);
first_pair.1.push(n);
part.insert((cur_min + n, first_pair.1, first_pair.2));
return _cga(k, input.clone(), total, part, best);
}
// If even if we evenly distribute (total - cur_max) among the rest of the k-1 bins
// we still won't lower the best, there's no need to search further.
// Use ((a - 1) / b) + 1 for always round up division.
if ((total - cur_max - 1) / k) + 1 < best {
// Try putting the next number in all the bins one by one
for pair in part.iter() {
let cost = pair.0;
// If improving the best is possible
if cost + n < best {
let mut new_part = part.clone();
let mut new_pair = new_part.take(pair).unwrap();
new_pair.1.push(n);
new_part.insert((cost + n, new_pair.1, new_pair.2));
match _cga(k, input.clone(), total, new_part, best) {
Some(sol) => {
if is_perfect(k, total, &sol) {
return Some(sol);
}
best = sol.iter().next_back().unwrap().0;
result = Some(sol);
}
None => (),
}
}
}
}
// Return the best result we found in the sub-tree.
return result;
}
// Leaf node, we are done.
None => {
return Some(part);
}
}
}
pub fn cga(k: usize, mut input: Vec<usize>) -> Vec<Vec<usize>> {
// Deal with dumb inputs.
let total = input.iter().sum();
if k == 1 || total == 0 {
return vec![input];
} else if input.len() <= k {
return input.iter().map(|x| vec![*x]).collect();
}
// Sort by increasing order, so pop() gives decreasing number.
input.sort_unstable();
// First number always goes in the "first" bin, so don't need to search a tree.
let mut part = BTreeSet::new();
let n = input.pop().unwrap();
part.insert((n, vec![n], 0));
_cga(k, input, total, part, total)
.unwrap()
.iter()
.map(|pair| pair.1.clone())
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cga() {
assert_eq!(mnp::cga(1, vec![1, 3, 2]), vec![vec![1, 3, 2]]);
assert_eq!(mnp::cga(2, vec![1, 3, 2]), vec![vec![2, 1], vec![3]]);
assert_eq!(mnp::cga(3, vec![1, 3, 2]), vec![vec![1], vec![3], vec![2]]);
assert_eq!(mnp::cga(4, vec![1, 3, 2]), vec![vec![1], vec![3], vec![2]]);
assert_eq!(
mnp::cga(3, vec![8, 4, 7, 5, 6]),
vec![vec![8], vec![6, 5], vec![7, 4]]
);
assert_eq!(
mnp::cga(4, vec![8, 4, 7, 5, 6]),
vec![vec![6], vec![7], vec![8], vec![5, 4]]
);
}
#[test]
fn test_cga_big() {
// random big numbers, to avoid perfect partition
assert_eq!(
mnp::cga(
8,
vec![
32525, 17303, 7084, 24185, 2233, 23788, 20717, 25841, 14545, 14807, 30030,
23001, 310, 10096, 27283, 8125, 28625, 21273, 19109, 25831, 3628, 25627, 15169,
26692,
]
),
vec![
vec![25841, 15169, 14807],
vec![26692, 19109, 10096],
vec![25831, 23001, 7084],
vec![30030, 25627, 310],
vec![32525, 21273, 2233],
vec![24185, 17303, 14545],
vec![28625, 23788, 3628],
vec![27283, 20717, 8125]
]
);
// random small numbers, realistic in some applications
assert_eq!(
mnp::cga(
8,
vec![
13, 151, 172, 121, 185, 236, 237, 241, 209, 215, 78, 217, 54, 112, 147, 189,
209, 25, 165, 231, 44, 27, 65, 68, 150, 127, 241, 129, 235, 85, 49, 248, 236,
220, 112, 164, 199, 92, 148, 152, 50, 225, 112, 103, 80, 2, 36, 32, 26, 200, 6,
70, 227, 71, 137, 120, 197, 122, 248, 175, 206, 40, 166, 185, 3, 22, 92, 201,
113, 239, 96, 163, 208, 207, 9, 31, 208, 44, 63, 234, 244, 68, 47, 214, 138,
183, 77, 78, 48, 68, 253, 254, 107, 162, 182, 109, 183, 18, 53, 40,
]
),
vec![
vec![241, 236, 215, 199, 172, 162, 113, 112, 78, 68, 40, 25],
vec![248, 234, 208, 200, 175, 152, 129, 107, 78, 54, 49, 27],
vec![254, 225, 209, 201, 183, 147, 127, 103, 85, 53, 48, 26],
vec![239, 237, 220, 185, 185, 151, 137, 96, 80, 65, 36, 31],
vec![241, 236, 217, 189, 183, 150, 138, 92, 92, 50, 40, 32, 2],
vec![244, 235, 214, 197, 182, 148, 122, 112, 71, 68, 47, 13, 9],
vec![248, 231, 209, 207, 165, 164, 120, 112, 70, 68, 44, 18, 6],
vec![253, 227, 208, 206, 166, 163, 121, 109, 77, 63, 44, 22, 3]
]
);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment