seq-len-balance 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- seq_len_balance-0.1.0/.gitignore +30 -0
- seq_len_balance-0.1.0/.python-version +1 -0
- seq_len_balance-0.1.0/Cargo.lock +158 -0
- seq_len_balance-0.1.0/Cargo.toml +17 -0
- seq_len_balance-0.1.0/Makefile +40 -0
- seq_len_balance-0.1.0/PKG-INFO +5 -0
- seq_len_balance-0.1.0/README.md +48 -0
- seq_len_balance-0.1.0/benchmark/kk_benchmark.png +0 -0
- seq_len_balance-0.1.0/pyproject.toml +26 -0
- seq_len_balance-0.1.0/src/bin.rs +173 -0
- seq_len_balance-0.1.0/src/heapq.rs +116 -0
- seq_len_balance-0.1.0/src/kk.rs +56 -0
- seq_len_balance-0.1.0/src/lib.rs +59 -0
- seq_len_balance-0.1.0/src/main.rs +51 -0
- seq_len_balance-0.1.0/test_kk.py +397 -0
- seq_len_balance-0.1.0/uv.lock +603 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Generated by Cargo
|
|
2
|
+
# will have compiled files and executables
|
|
3
|
+
debug
|
|
4
|
+
target
|
|
5
|
+
|
|
6
|
+
# These are backup files generated by rustfmt
|
|
7
|
+
**/*.rs.bk
|
|
8
|
+
|
|
9
|
+
# MSVC Windows builds of rustc generate these, which store debugging information
|
|
10
|
+
*.pdb
|
|
11
|
+
|
|
12
|
+
# Generated by cargo mutants
|
|
13
|
+
# Contains mutation testing data
|
|
14
|
+
**/mutants.out*/
|
|
15
|
+
|
|
16
|
+
# RustRover
|
|
17
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
18
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
19
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
20
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
21
|
+
#.idea/
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Added by cargo
|
|
25
|
+
|
|
26
|
+
/target
|
|
27
|
+
|
|
28
|
+
.ipynb_checkpoints/
|
|
29
|
+
|
|
30
|
+
__pycache__
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11.3
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "autocfg"
|
|
7
|
+
version = "1.5.0"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "heck"
|
|
13
|
+
version = "0.5.0"
|
|
14
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "libc"
|
|
19
|
+
version = "0.2.182"
|
|
20
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
21
|
+
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
|
22
|
+
|
|
23
|
+
[[package]]
|
|
24
|
+
name = "num-traits"
|
|
25
|
+
version = "0.2.19"
|
|
26
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
27
|
+
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
28
|
+
dependencies = [
|
|
29
|
+
"autocfg",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[[package]]
|
|
33
|
+
name = "once_cell"
|
|
34
|
+
version = "1.21.3"
|
|
35
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
36
|
+
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
|
37
|
+
|
|
38
|
+
[[package]]
|
|
39
|
+
name = "ordered-float"
|
|
40
|
+
version = "5.1.0"
|
|
41
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
42
|
+
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
|
|
43
|
+
dependencies = [
|
|
44
|
+
"num-traits",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[[package]]
|
|
48
|
+
name = "portable-atomic"
|
|
49
|
+
version = "1.13.1"
|
|
50
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
51
|
+
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
|
52
|
+
|
|
53
|
+
[[package]]
|
|
54
|
+
name = "proc-macro2"
|
|
55
|
+
version = "1.0.106"
|
|
56
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
57
|
+
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
58
|
+
dependencies = [
|
|
59
|
+
"unicode-ident",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[[package]]
|
|
63
|
+
name = "pyo3"
|
|
64
|
+
version = "0.28.2"
|
|
65
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
66
|
+
checksum = "cf85e27e86080aafd5a22eae58a162e133a589551542b3e5cee4beb27e54f8e1"
|
|
67
|
+
dependencies = [
|
|
68
|
+
"libc",
|
|
69
|
+
"once_cell",
|
|
70
|
+
"portable-atomic",
|
|
71
|
+
"pyo3-build-config",
|
|
72
|
+
"pyo3-ffi",
|
|
73
|
+
"pyo3-macros",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "pyo3-build-config"
|
|
78
|
+
version = "0.28.2"
|
|
79
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
80
|
+
checksum = "8bf94ee265674bf76c09fa430b0e99c26e319c945d96ca0d5a8215f31bf81cf7"
|
|
81
|
+
dependencies = [
|
|
82
|
+
"target-lexicon",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[[package]]
|
|
86
|
+
name = "pyo3-ffi"
|
|
87
|
+
version = "0.28.2"
|
|
88
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
89
|
+
checksum = "491aa5fc66d8059dd44a75f4580a2962c1862a1c2945359db36f6c2818b748dc"
|
|
90
|
+
dependencies = [
|
|
91
|
+
"libc",
|
|
92
|
+
"pyo3-build-config",
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[[package]]
|
|
96
|
+
name = "pyo3-macros"
|
|
97
|
+
version = "0.28.2"
|
|
98
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
99
|
+
checksum = "f5d671734e9d7a43449f8480f8b38115df67bef8d21f76837fa75ee7aaa5e52e"
|
|
100
|
+
dependencies = [
|
|
101
|
+
"proc-macro2",
|
|
102
|
+
"pyo3-macros-backend",
|
|
103
|
+
"quote",
|
|
104
|
+
"syn",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[[package]]
|
|
108
|
+
name = "pyo3-macros-backend"
|
|
109
|
+
version = "0.28.2"
|
|
110
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
111
|
+
checksum = "22faaa1ce6c430a1f71658760497291065e6450d7b5dc2bcf254d49f66ee700a"
|
|
112
|
+
dependencies = [
|
|
113
|
+
"heck",
|
|
114
|
+
"proc-macro2",
|
|
115
|
+
"pyo3-build-config",
|
|
116
|
+
"quote",
|
|
117
|
+
"syn",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
[[package]]
|
|
121
|
+
name = "quote"
|
|
122
|
+
version = "1.0.45"
|
|
123
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
124
|
+
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
|
125
|
+
dependencies = [
|
|
126
|
+
"proc-macro2",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
[[package]]
|
|
130
|
+
name = "seq-len-balance-rs"
|
|
131
|
+
version = "0.1.0"
|
|
132
|
+
dependencies = [
|
|
133
|
+
"ordered-float",
|
|
134
|
+
"pyo3",
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
[[package]]
|
|
138
|
+
name = "syn"
|
|
139
|
+
version = "2.0.117"
|
|
140
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
141
|
+
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
|
142
|
+
dependencies = [
|
|
143
|
+
"proc-macro2",
|
|
144
|
+
"quote",
|
|
145
|
+
"unicode-ident",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
[[package]]
|
|
149
|
+
name = "target-lexicon"
|
|
150
|
+
version = "0.13.5"
|
|
151
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
152
|
+
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
153
|
+
|
|
154
|
+
[[package]]
|
|
155
|
+
name = "unicode-ident"
|
|
156
|
+
version = "1.0.24"
|
|
157
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
158
|
+
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "seq-len-balance-rs"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
name = "seq_len_balance"
|
|
9
|
+
crate-type = ["cdylib", "rlib"]
|
|
10
|
+
|
|
11
|
+
[[bin]]
|
|
12
|
+
name = "seq-len-balance-rs"
|
|
13
|
+
path = "src/main.rs"
|
|
14
|
+
|
|
15
|
+
[dependencies]
|
|
16
|
+
pyo3 = { version = "0.28", features = ["extension-module"] }
|
|
17
|
+
ordered-float = "5"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# ---- Project metadata ----
|
|
2
|
+
PACKAGE_NAME := seq-len-balance-rs
|
|
3
|
+
PYTHON ?= python3
|
|
4
|
+
BUILD_DIR := target/wheels
|
|
5
|
+
|
|
6
|
+
# ---- Commands ----
|
|
7
|
+
|
|
8
|
+
.PHONY: help build develop publish clean
|
|
9
|
+
|
|
10
|
+
help:
|
|
11
|
+
@echo "Available commands:"
|
|
12
|
+
@echo " make build - Build release wheels (and sdist)"
|
|
13
|
+
@echo " make develop - Install locally in dev mode"
|
|
14
|
+
@echo " make publish - Publish to PyPI using maturin"
|
|
15
|
+
@echo " make clean - Remove build artifacts"
|
|
16
|
+
|
|
17
|
+
build:
|
|
18
|
+
@echo "🚀 Building release wheels..."
|
|
19
|
+
maturin build --release --sdist
|
|
20
|
+
@echo "✅ Wheels generated in $(BUILD_DIR)/"
|
|
21
|
+
|
|
22
|
+
develop:
|
|
23
|
+
@echo "💡 Installing in development mode..."
|
|
24
|
+
maturin develop
|
|
25
|
+
|
|
26
|
+
publish: build
|
|
27
|
+
@echo "📦 Publishing $(PACKAGE_NAME) to PyPI..."
|
|
28
|
+
@if [ -z "$$MATURIN_PASSWORD" ]; then \
|
|
29
|
+
echo "❌ Error: MATURIN_PASSWORD (your PyPI token) is not set."; \
|
|
30
|
+
echo " export MATURIN_USERNAME=__token__"; \
|
|
31
|
+
echo " export MATURIN_PASSWORD=pypi-xxxxxx"; \
|
|
32
|
+
exit 1; \
|
|
33
|
+
fi
|
|
34
|
+
maturin publish --skip-existing
|
|
35
|
+
@echo "✅ Published successfully!"
|
|
36
|
+
|
|
37
|
+
clean:
|
|
38
|
+
@echo "🧹 Cleaning build artifacts..."
|
|
39
|
+
cargo clean
|
|
40
|
+
rm -rf $(BUILD_DIR) dist build *.egg-info
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# seq-len-balance-rs
|
|
2
|
+
|
|
3
|
+
Fast sequence-length balancing: KK partition, FFD, and BFD bin packing (Rust + Python).
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv sync
|
|
9
|
+
pip install maturin
|
|
10
|
+
maturin develop
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from seq_len_balance import kk, ffd, bfd
|
|
17
|
+
|
|
18
|
+
print(kk([8, 7, 6, 5, 4], 2)) # -> [[8, 5], [7, 4], [6]]
|
|
19
|
+
print(ffd([6, 3, 4, 5, 2, 7, 1], 10)) # -> [[7, 3], [6, 4], [5, 2, 1]]
|
|
20
|
+
print(bfd([6, 3, 4, 5, 2, 7, 1], 10)) # -> [[7, 2, 1], [6, 4], [5, 3]]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Benchmark
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv sync --group benchmark
|
|
27
|
+
uv run python test_kk.py
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
=================================================================
|
|
32
|
+
KK (greedy LPT) benchmark k=4 repeats=3
|
|
33
|
+
=================================================================
|
|
34
|
+
n= 10,000 Python: 2.4 ms Rust: 0.4 ms
|
|
35
|
+
n= 100,000 Python: 25.5 ms Rust: 2.9 ms
|
|
36
|
+
n= 500,000 Python: 131.4 ms Rust: 16.3 ms
|
|
37
|
+
n=1,000,000 Python: 294.7 ms Rust: 33.1 ms
|
|
38
|
+
n=2,000,000 Python: 663.9 ms Rust: 69.6 ms
|
|
39
|
+
n=4,000,000 Python: 1405.9 ms Rust: 144.9 ms
|
|
40
|
+
n=10,000,000 Python: 3577.8 ms Rust: 377.6 ms
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Rust in Jupyter (optional)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cargo install evcxr_jupyter
|
|
47
|
+
evcxr_jupyter --install
|
|
48
|
+
```
|
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["maturin>=1.0,<2.0"]
|
|
3
|
+
build-backend = "maturin"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "seq-len-balance"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
description = "Fast sequence-length balancing: KK partition, FFD, and BFD bin packing"
|
|
10
|
+
dependencies = []
|
|
11
|
+
|
|
12
|
+
[tool.maturin]
|
|
13
|
+
features = ["pyo3/extension-module"]
|
|
14
|
+
|
|
15
|
+
[dependency-groups]
|
|
16
|
+
benchmark = [
|
|
17
|
+
"matplotlib>=3.10.8",
|
|
18
|
+
"pytest>=9.0.2",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[tool.uv]
|
|
22
|
+
dev-dependencies = [
|
|
23
|
+
"matplotlib>=3.10.8",
|
|
24
|
+
"maturin>=1.0,<2.0",
|
|
25
|
+
"numpy>=2.4.2",
|
|
26
|
+
]
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
use std::fmt;
|
|
2
|
+
use std::ops::{Add, Sub};
|
|
3
|
+
|
|
4
|
+
/// Unified result of a bin packing or partitioning algorithm.
|
|
5
|
+
///
|
|
6
|
+
/// - `capacity`: `Some(limit)` for bin packing (FFD/BFD), `None` for unconstrained partitioning (KK).
|
|
7
|
+
/// - `remaining[i]`: unused space in bin i; zero for partitioning results.
|
|
8
|
+
/// - `sums[i]`: total item weight assigned to bin/group i.
|
|
9
|
+
#[derive(Debug)]
|
|
10
|
+
pub struct BinPacking<T> {
|
|
11
|
+
pub bins: Vec<Vec<T>>,
|
|
12
|
+
pub sums: Vec<T>,
|
|
13
|
+
pub remaining: Vec<T>,
|
|
14
|
+
pub capacity: Option<T>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
impl<T> BinPacking<T>
|
|
18
|
+
where
|
|
19
|
+
T: Ord + Clone + Sub<Output = T>,
|
|
20
|
+
{
|
|
21
|
+
/// Number of bins / groups used.
|
|
22
|
+
pub fn num_bins(&self) -> usize {
|
|
23
|
+
self.bins.len()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Difference between the largest and smallest bin sum.
|
|
27
|
+
pub fn imbalance(&self) -> T {
|
|
28
|
+
let max = self.sums.iter().max().unwrap().clone();
|
|
29
|
+
let min = self.sums.iter().min().unwrap().clone();
|
|
30
|
+
max - min
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Total wasted space across all bins (sum of `remaining`).
|
|
34
|
+
/// For partitioning results this is always zero.
|
|
35
|
+
pub fn waste(&self) -> T
|
|
36
|
+
where
|
|
37
|
+
T: Default + Add<Output = T>,
|
|
38
|
+
{
|
|
39
|
+
self.remaining
|
|
40
|
+
.iter()
|
|
41
|
+
.cloned()
|
|
42
|
+
.fold(T::default(), |acc, r| acc + r)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl<T: fmt::Display> fmt::Display for BinPacking<T> {
|
|
47
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
48
|
+
for (i, ((bin, sum), rem)) in self
|
|
49
|
+
.bins
|
|
50
|
+
.iter()
|
|
51
|
+
.zip(self.sums.iter())
|
|
52
|
+
.zip(self.remaining.iter())
|
|
53
|
+
.enumerate()
|
|
54
|
+
{
|
|
55
|
+
match &self.capacity {
|
|
56
|
+
Some(_) => write!(f, "Bin {i} (sum={sum}, remaining={rem}): [")?,
|
|
57
|
+
None => write!(f, "Bin {i} (sum={sum}): [")?,
|
|
58
|
+
}
|
|
59
|
+
for (j, item) in bin.iter().enumerate() {
|
|
60
|
+
if j > 0 {
|
|
61
|
+
write!(f, ", ")?;
|
|
62
|
+
}
|
|
63
|
+
write!(f, "{item}")?;
|
|
64
|
+
}
|
|
65
|
+
writeln!(f, "]")?;
|
|
66
|
+
}
|
|
67
|
+
Ok(())
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// First Fit Decreasing bin packing.
|
|
72
|
+
///
|
|
73
|
+
/// Sorts items descending, then for each item tries bins in order (0, 1, 2, ...)
|
|
74
|
+
/// and places it in the **first** bin with enough remaining capacity.
|
|
75
|
+
/// Opens a new bin only when no existing bin fits.
|
|
76
|
+
/// Capacity defaults to the sum of all items when not provided.
|
|
77
|
+
pub fn first_fit_decreasing<T>(mut items: Vec<T>, capacity: Option<T>) -> BinPacking<T>
|
|
78
|
+
where
|
|
79
|
+
T: Ord + Add<Output = T> + Sub<Output = T> + Default + Clone,
|
|
80
|
+
{
|
|
81
|
+
let capacity = capacity
|
|
82
|
+
.unwrap_or_else(|| items.iter().cloned().fold(T::default(), |acc, x| acc + x));
|
|
83
|
+
|
|
84
|
+
items.sort_unstable_by(|a, b| b.cmp(a));
|
|
85
|
+
|
|
86
|
+
let mut bins: Vec<Vec<T>> = Vec::new();
|
|
87
|
+
let mut sums: Vec<T> = Vec::new();
|
|
88
|
+
let mut remaining: Vec<T> = Vec::new();
|
|
89
|
+
|
|
90
|
+
for item in items {
|
|
91
|
+
assert!(item <= capacity, "item exceeds bin capacity");
|
|
92
|
+
|
|
93
|
+
// First bin with enough room.
|
|
94
|
+
let target = bins
|
|
95
|
+
.iter()
|
|
96
|
+
.enumerate()
|
|
97
|
+
.find(|(i, _)| remaining[*i] >= item)
|
|
98
|
+
.map(|(i, _)| i);
|
|
99
|
+
|
|
100
|
+
match target {
|
|
101
|
+
Some(i) => {
|
|
102
|
+
remaining[i] = remaining[i].clone() - item.clone();
|
|
103
|
+
sums[i] = sums[i].clone() + item.clone();
|
|
104
|
+
bins[i].push(item);
|
|
105
|
+
}
|
|
106
|
+
None => {
|
|
107
|
+
remaining.push(capacity.clone() - item.clone());
|
|
108
|
+
sums.push(item.clone());
|
|
109
|
+
bins.push(vec![item]);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
BinPacking {
|
|
115
|
+
bins,
|
|
116
|
+
sums,
|
|
117
|
+
remaining,
|
|
118
|
+
capacity: Some(capacity),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Best Fit Decreasing bin packing.
|
|
123
|
+
///
|
|
124
|
+
/// Sorts items descending, then for each item finds the bin with the
|
|
125
|
+
/// **minimum remaining capacity** that still fits the item (tightest fit).
|
|
126
|
+
/// This leaves larger gaps available for larger future items.
|
|
127
|
+
/// Opens a new bin only when no existing bin fits.
|
|
128
|
+
/// Capacity defaults to the sum of all items when not provided.
|
|
129
|
+
pub fn best_fit_decreasing<T>(mut items: Vec<T>, capacity: Option<T>) -> BinPacking<T>
|
|
130
|
+
where
|
|
131
|
+
T: Ord + Add<Output = T> + Sub<Output = T> + Default + Clone,
|
|
132
|
+
{
|
|
133
|
+
let capacity = capacity
|
|
134
|
+
.unwrap_or_else(|| items.iter().cloned().fold(T::default(), |acc, x| acc + x));
|
|
135
|
+
|
|
136
|
+
items.sort_unstable_by(|a, b| b.cmp(a));
|
|
137
|
+
|
|
138
|
+
let mut bins: Vec<Vec<T>> = Vec::new();
|
|
139
|
+
let mut sums: Vec<T> = Vec::new();
|
|
140
|
+
let mut remaining: Vec<T> = Vec::new();
|
|
141
|
+
|
|
142
|
+
for item in items {
|
|
143
|
+
assert!(item <= capacity, "item exceeds bin capacity");
|
|
144
|
+
|
|
145
|
+
// Bin with the smallest remaining space that still fits (tightest fit).
|
|
146
|
+
let best_idx = remaining
|
|
147
|
+
.iter()
|
|
148
|
+
.enumerate()
|
|
149
|
+
.filter(|(_, r)| *r >= &item)
|
|
150
|
+
.min_by(|(_, a), (_, b)| a.cmp(b))
|
|
151
|
+
.map(|(i, _)| i);
|
|
152
|
+
|
|
153
|
+
match best_idx {
|
|
154
|
+
Some(i) => {
|
|
155
|
+
remaining[i] = remaining[i].clone() - item.clone();
|
|
156
|
+
sums[i] = sums[i].clone() + item.clone();
|
|
157
|
+
bins[i].push(item);
|
|
158
|
+
}
|
|
159
|
+
None => {
|
|
160
|
+
remaining.push(capacity.clone() - item.clone());
|
|
161
|
+
sums.push(item.clone());
|
|
162
|
+
bins.push(vec![item]);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
BinPacking {
|
|
168
|
+
bins,
|
|
169
|
+
sums,
|
|
170
|
+
remaining,
|
|
171
|
+
capacity: Some(capacity),
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#[derive(Debug, Default)]
|
|
2
|
+
pub struct MinHeap<T: Ord> {
|
|
3
|
+
data: Vec<T>,
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
impl<T: Ord> MinHeap<T> {
|
|
7
|
+
pub fn len(&self) -> usize {
|
|
8
|
+
self.data.len()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
pub fn is_empty(&self) -> bool {
|
|
12
|
+
self.data.is_empty()
|
|
13
|
+
}
|
|
14
|
+
// used after inserting a new element
|
|
15
|
+
pub fn sift_up(&mut self, mut i: usize) {
|
|
16
|
+
while i > 0 {
|
|
17
|
+
let parent = (i - 1) / 2;
|
|
18
|
+
|
|
19
|
+
if self.data[parent] <= self.data[i] {
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
self.data.swap(parent, i);
|
|
23
|
+
i = parent;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// after deleting an element
|
|
27
|
+
pub fn sift_down(&mut self, mut i: usize) {
|
|
28
|
+
let n = self.data.len();
|
|
29
|
+
|
|
30
|
+
loop {
|
|
31
|
+
// parent(i) = (i - 1) / 2
|
|
32
|
+
// left(i) = 2*i + 1
|
|
33
|
+
// right(i) = 2*i + 2
|
|
34
|
+
let left = 2 * i + 1;
|
|
35
|
+
let right = 2 * i + 2;
|
|
36
|
+
|
|
37
|
+
if left >= n {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
let mut smallest = left;
|
|
41
|
+
if right < n && self.data[right] < self.data[left] {
|
|
42
|
+
smallest = right;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if self.data[i] <= self.data[smallest] {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
self.data.swap(i, smallest);
|
|
49
|
+
i = smallest;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub fn push(&mut self, val: T) {
|
|
54
|
+
self.data.push(val);
|
|
55
|
+
let i = self.data.len() - 1;
|
|
56
|
+
self.sift_up(i);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pub fn pop(&mut self) -> Option<T> {
|
|
60
|
+
if self.data.is_empty() {
|
|
61
|
+
return None;
|
|
62
|
+
}
|
|
63
|
+
let last = self.data.len() - 1;
|
|
64
|
+
self.data.swap(0, last);
|
|
65
|
+
let top = self.data.pop();
|
|
66
|
+
if !self.data.is_empty() {
|
|
67
|
+
self.sift_down(0);
|
|
68
|
+
}
|
|
69
|
+
top
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub fn peek(&self) -> Option<&T> {
|
|
73
|
+
self.data.first()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pub fn from_vec(data: Vec<T>) -> Self {
|
|
77
|
+
let mut h = Self { data };
|
|
78
|
+
let n = h.data.len();
|
|
79
|
+
for i in (0..n / 2).rev() {
|
|
80
|
+
h.sift_down(i);
|
|
81
|
+
}
|
|
82
|
+
h
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
pub fn with_capacity(cap: usize) -> Self {
|
|
86
|
+
Self {
|
|
87
|
+
data: Vec::with_capacity(cap),
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl<T: Ord + std::fmt::Display> MinHeap<T> {
|
|
93
|
+
pub fn print_tree(&self) {
|
|
94
|
+
if self.data.is_empty() {
|
|
95
|
+
println!("(empty)");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let n = self.data.len();
|
|
100
|
+
let mut level_start = 0;
|
|
101
|
+
let mut level_size = 1;
|
|
102
|
+
|
|
103
|
+
while level_start < n {
|
|
104
|
+
let level_end = (level_start + level_size).min(n);
|
|
105
|
+
for i in level_start..level_end {
|
|
106
|
+
if i > level_start {
|
|
107
|
+
print!(" ");
|
|
108
|
+
}
|
|
109
|
+
print!("{}", self.data[i]);
|
|
110
|
+
}
|
|
111
|
+
println!();
|
|
112
|
+
level_start += level_size;
|
|
113
|
+
level_size *= 2;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
use crate::bin::BinPacking;
|
|
2
|
+
use std::cmp::Reverse;
|
|
3
|
+
use std::collections::BinaryHeap;
|
|
4
|
+
use std::ops::Add;
|
|
5
|
+
|
|
6
|
+
// [8,7,6,5,4]
|
|
7
|
+
// [0 | 0 | 8] -> sort ascending (first element)
|
|
8
|
+
// [7 | 0 | 0] -> sort descending element
|
|
9
|
+
// [7 | 0 | 8] -> sort -> [0 | 7 | 8]
|
|
10
|
+
|
|
11
|
+
// [0 | 7 | 8] -> sorted ascending
|
|
12
|
+
// [6 | 0 | 0] -> sorted descending
|
|
13
|
+
// [6 | 7 | 8] -> sort ascending -> [6 | 7 | 8]
|
|
14
|
+
|
|
15
|
+
// [6 | 7 | 8]
|
|
16
|
+
// [5 | 0 | 0]
|
|
17
|
+
// [(6,5) | 7 | 8] -> sort ascending -> [7 | 8 | 11]
|
|
18
|
+
|
|
19
|
+
// [7 | 8 | 11]
|
|
20
|
+
// [5 | 0 | 0]
|
|
21
|
+
// [(7,5) | 8 | (6,5)] -> [8 | 11 | 12]
|
|
22
|
+
|
|
23
|
+
// [8 | 11 | 12]
|
|
24
|
+
// [4 | 0 | 0]
|
|
25
|
+
// [(8,4) | 11 | 12] -> [(8,4) | (6,5) | (7,5) ]
|
|
26
|
+
|
|
27
|
+
pub fn kk_partition<T>(mut items: Vec<T>, k: usize) -> BinPacking<T>
|
|
28
|
+
where
|
|
29
|
+
T: Ord + Add<Output = T> + Default + Clone,
|
|
30
|
+
{
|
|
31
|
+
assert!(k > 0, "k must be at least 1");
|
|
32
|
+
// placing large items first improves balancing.
|
|
33
|
+
items.sort_unstable_by(|a, b| b.cmp(a));
|
|
34
|
+
|
|
35
|
+
let mut bins: Vec<Vec<T>> = vec![vec![]; k];
|
|
36
|
+
let mut sums: Vec<T> = vec![T::default(); k];
|
|
37
|
+
// BinaryHeap<Reverse<...>> gives min-heap behaviour.
|
|
38
|
+
let mut min_heap: BinaryHeap<Reverse<(T, usize)>> =
|
|
39
|
+
(0..k).map(|i| Reverse((T::default(), i))).collect();
|
|
40
|
+
|
|
41
|
+
for item in items {
|
|
42
|
+
let Reverse((min_sum, idx)) = min_heap.pop().unwrap();
|
|
43
|
+
let new_sum = min_sum + item.clone();
|
|
44
|
+
bins[idx].push(item);
|
|
45
|
+
sums[idx] = new_sum.clone();
|
|
46
|
+
min_heap.push(Reverse((new_sum, idx)));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let remaining = vec![T::default(); k];
|
|
50
|
+
BinPacking {
|
|
51
|
+
bins,
|
|
52
|
+
sums,
|
|
53
|
+
remaining,
|
|
54
|
+
capacity: None,
|
|
55
|
+
}
|
|
56
|
+
}
|