Functional Weave
Code in Rust

money.allocate@1.0.1

impl/rust.rs

2,108 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::money_amount::{money, money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune

/// Split `amount` across `ratios` so the parts add back up to the whole.
///
/// Splitting 10.00 three ways gives 3.34, 3.33, 3.33 - never 3.33 three times
/// with a penny quietly lost. The leftover goes to the largest remainders,
/// ties broken by position, so the split is stable and reproducible.
///
/// # Panics
/// Panics if `ratios` is empty or sums to zero.
pub fn allocate(amount: &Money, ratios: &[i64]) -> Vec<Money> {
    if ratios.is_empty() {
        panic!("allocate needs at least one ratio");
    }
    let total: i64 = ratios.iter().sum();
    if total == 0 {
        panic!("ratios must not sum to zero");
    }

    let mut bases: Vec<i64> = Vec::with_capacity(ratios.len());
    let mut remainders: Vec<i64> = Vec::with_capacity(ratios.len());
    let total128 = total as i128;
    for ratio in ratios {
        // Euclidean division keeps the remainder non-negative, which is the
        // invariant the largest-remainder pass below relies on, and matches
        // floor division in the TypeScript and Python implementations.
        let numerator = (amount.minor as i128) * (*ratio as i128);
        let base = numerator.div_euclid(total128);
        bases.push(base as i64);
        remainders.push(numerator.rem_euclid(total128) as i64);
    }

    let mut leftover = amount.minor - bases.iter().sum::<i64>();

    let mut order: Vec<usize> = (0..ratios.len()).collect();
    order.sort_by(|a, b| remainders[*b].cmp(&remainders[*a]).then(a.cmp(b)));

    for index in order {
        if leftover <= 0 {
            break;
        }
        bases[index] += 1;
        leftover -= 1;
    }

    bases
        .iter()
        .map(|base| money(*base, &amount.currency))
        .collect()
}

pub fn fune_vector(args: &[Value]) -> Value {
    let ratios: Vec<i64> = args[1].as_arr().iter().map(|v| v.as_i64()).collect();
    Value::Arr(
        allocate(&money_from_value(&args[0]), &ratios)
            .iter()
            .map(money_to_value)
            .collect(),
    )
}