Skip to content

Instantly share code, notes, and snippets.

@aryszka
Last active November 16, 2021 12:10
Show Gist options
  • Save aryszka/e9208fa8013facbdf697f543ea2733f3 to your computer and use it in GitHub Desktop.
Save aryszka/e9208fa8013facbdf697f543ea2733f3 to your computer and use it in GitHub Desktop.
min transfers in Go, and without a for loop in JS and MML
package main
import (
"fmt"
"log"
"math"
"os"
"strconv"
"strings"
)
func splitArg(arg string) (string, float64, error) {
s := strings.Split(arg, "=")
if len(s) != 2 {
return "", 0, fmt.Errorf("invalid arg: %s", arg)
}
f, err := strconv.ParseFloat(s[1], 64)
return s[0], f, err
}
func parseArgs(args []string) (map[string]float64, error) {
standings := make(map[string]float64)
for _, arg := range args {
name, surplus, err := splitArg(arg)
if err != nil {
return nil, err
}
standings[name] = surplus
}
return standings, nil
}
func verifyStandings(standings map[string]float64) error {
var sum float64
for _, surplus := range standings {
sum += surplus
}
if sum != 0 {
return fmt.Errorf("sum is not 0; sum=%f", sum)
}
return nil
}
func max(standings map[string]float64, sign float64) (string, float64) {
var (
name string
max float64
)
for current, surplus := range standings {
surplus *= sign
if surplus > max {
name = current
max = surplus
}
}
return name, max
}
func calculateTransfers(standings map[string]float64) map[string]map[string]float64 {
transfers := make(map[string]map[string]float64)
for {
biggestSurplus, surplus := max(standings, 1)
biggestDebt, debt := max(standings, -1)
transfer := math.Min(surplus, debt)
if transfer == 0 {
return transfers
}
standings[biggestSurplus] -= transfer
standings[biggestDebt] += transfer
if _, ok := transfers[biggestDebt]; !ok {
transfers[biggestDebt] = make(map[string]float64)
}
transfers[biggestDebt][biggestSurplus] = transfer
}
}
func main() {
standings, err := parseArgs(os.Args[1:])
if err != nil {
log.Fatalln(err)
}
if err := verifyStandings(standings); err != nil {
log.Fatalln(err)
}
transfers := calculateTransfers(standings)
fmt.Println(transfers)
}
// merge two entries, but as a plain function
function mergeEntry(all, next) {
return {...all, ...next}
}
// object indexing as a plain function
function value(standings, key) {
return standings[key]
}
// function binding as a plain function
function bind(f, ...args) {
return f.bind(null, ...args)
}
function splitArg(arg) {
let s = arg.split("=")
if (s.length != 2) {
throw new Error("invalid arg: " + arg)
}
return {[s[0]]: Number.parseFloat(s[1])}
}
function parseArgs(args) {
return args.map(splitArg).reduce(mergeEntry)
}
function addStanding(standings, sum, nextKey) {
return sum + standings[nextKey]
}
function verifyStandings(standings) {
let sum = Object.keys(standings).reduce(bind(addStanding, standings), 0)
if (sum !== 0) {
throw new Error("sum is not 0; sum=" + sum)
}
}
function maxOf(standings, sign, previousKey, nextKey) {
return standings[nextKey] * sign <= standings[previousKey] * sign ? previousKey : nextKey
}
function max(standings, sign) {
return Object.keys(standings).reduce(bind(maxOf, standings, sign), "")
}
function updateStandings(standings, transfer, from, to) {
return {
...standings,
[to]: standings[to] - transfer,
[from]: standings[from] + transfer
}
}
function updateTransfers(transfers, transfer, from, to) {
return {
...transfers,
[from]: {
...transfers[from],
[to]: transfer
}
}
}
function calculateTransfers(standings, transfers) {
let biggestSurplus = max(standings, 1)
let biggestDebt = max(standings, -1)
let surplus = standings[biggestSurplus]
let debt = -standings[biggestDebt]
let transfer = Math.min(surplus, debt)
if (transfer === 0) {
return transfers
}
standings = updateStandings(standings, transfer, biggestDebt, biggestSurplus)
transfers = updateTransfers(transfers, transfer, biggestDebt, biggestSurplus)
return calculateTransfers(standings, transfers)
}
try {
let standings = parseArgs(Deno.args)
verifyStandings(standings)
console.log(calculateTransfers(standings, {}))
} catch (err) {
console.log(err.message)
}
use (
"errors"
"ints"
"io"
"lists"
"os"
"strings"
"structs"
)
fn splitArg(arg) {
let s strings.split("=", arg)
if len(s) != 2 {
return error("invalid arg", arg)
}
let i ints.parse(s[1])
check i
return {[s[0]]: i}
}
fn (
parseArgs(args) -> lists.map(splitArg) -> errors.pass(lists.fold(structs.merge, {}))
verifyStandings(standings) errors.when(
"the sum of standings must be 0"
structs.keys(standings) -> lists.map(addStanding) != 0
)
maxOf(standings, sign, key1, key2) standings[key1] * sign <= standings[key2] * sign ? key2 : key1
max(standings, sign) structs.keys(standings) -> lists.fold(maxOf(standings, sign), "")
)
fn updateStandings(standings, transfer, from, to) {
standings...
[from]: standings[from] + transfer
[to]: standings[to] - transfer
}
fn updateTransfers(transfers, transfer, from, to) {
transfers...
[from]: {structs.ensure(from, {}, transfers)..., [to]: transfer}
}
fn calculateTransfers(standings, transfers) {
let (
biggestDebt max(standings, -1)
biggestSurplus max(standings, 1)
debt standings[biggestDebt]
surplus standings[biggestSurplus]
transfer math.min(debt, surplus)
)
return transfer == 0 ? transfers : calculateTransfers(
updateStandings(standings, transfer, biggestDebt, biggestSurplus)
updateTransfers(transfers, transfer, biggestDebt, biggestSurplus)
)
}
let standings parseArgs(os.args[1:])
check standings
check verifyStandings(standings)
let transfers calculateTransfers(standings, {})
check io.println(transfers)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment