Last active
November 16, 2021 12:10
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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