Repeating a Calculation
Run a calculation again and again, one row each time, a set number of times or until an output says stop.
After reading this guide, you will know:
- How
repeatdiffers from a run over a list. - How to run a set number of times with
times. - How to stop when something happens with
untilandlimit. - How to work out odds over many seeded runs.
- What
checkasks of a repeat.
Rows That Make Themselves
A run over a list has one row per item of the list. A repeat has no list: it runs the calculation
again and again, each time as one more row, until it has run times rows or a row’s output says stop.
It takes position, carry, start, earlier and with exactly as a run over a list does (see
Running over Lists). Without carry, every row would see the same
values, so a repeat nearly always carries something.
Until Something Happens
Months of saving until a goal is reached:
calculations:
goal:
inputs:
balance: {type: number}
deposit: {type: number}
monthly_rate: {type: number}
target: {type: number}
steps:
- name: months
repeat: {calculation: month, position: month, carry: {balance: closing}, until: reached, limit: 120}
- name: taken
formula: COUNT(months)
- name: reached_it
formula: 'ANY(months, m, m.reached)'
- name: final
formula: 'REDUCE(months, last, m, m.closing, balance)'
outputs: [taken, reached_it, final]
month:
inputs:
month: {type: number}
balance: {type: number}
deposit: {type: number}
monthly_rate: {type: number}
target: {type: number}
steps:
- name: interest
formula: 'ROUND(balance * monthly_rate, 2)'
- name: closing
formula: balance + interest + deposit
- name: reached
formula: closing >= target
outputs: [month, interest, closing, reached]
$ echo '{"balance": 1000, "deposit": 500, "monthly_rate": 0.01, "target": 2000}' | rmt run savings.yml --trace
taken 2
reached_it true
final 2025.1
How it was worked out
months 2 rows
row 1
interest 10 ROUND(balance * monthly_rate, 2)
closing 1510 balance + interest + deposit
reached false closing >= target
row 2
interest 15.1 ROUND(balance * monthly_rate, 2)
closing 2025.1 balance + interest + deposit
reached true closing >= target
taken 2 COUNT(months)
reached_it true ANY(months, m, m.reached)
final 2025.1 REDUCE(months, last, m, m.closing, balance)
until: reached stops after the first row whose reached is true, and that row is kept. limit is
required with until, so a repeat always ends. A goal out of reach stops at the limit:
- name: a goal out of reach stops at the limit
facts: {balance: 0, deposit: 1, monthly_rate: 0, target: 1000000}
expect: {taken: 120, reached_it: false, final: 120}
Check whether the stop was reached, as reached_it does, rather than assuming it was.
A Set Number of Times
times runs a set number of rows. It can be a number or a formula, such as an input with a default:
schedule:
inputs:
balance: {type: number}
deposit: {type: number}
monthly_rate: {type: number}
target: {type: number}
months: {type: number, default: 3}
steps:
- name: rows
repeat: {calculation: month, times: months, position: month, carry: {balance: closing}}
outputs: [rows]
$ echo '{"balance": 1000, "deposit": 500, "monthly_rate": 0.01, "target": 0, "months": 4}' | rmt run savings.yml --calculation schedule
rows
month interest closing reached
1 10 1510 true
2 15.1 2025.1 true
3 20.25 2545.35 true
4 25.45 3070.8 true
The shipped electricity bill charges supply this way: repeat: {calculation: supply_day, times: days,
position: day}, one row per day of the billing period, each priced on its own date.
Odds
Roll with ROLL, keyed by a seed, and repeat with the seed as the position. Each row is a different
contest, and the same seed always gives the same one, so the odds can be pinned in a case:
calculations:
odds:
inputs:
fights: {type: number, default: 200}
steps:
- name: played
repeat: {calculation: contest, times: fights, position: seed}
- name: wins
formula: 'COUNT(FILTER(played, p, p.won))'
- name: chance
formula: 'ROUND(wins / fights, 2)'
outputs: [wins, chance]
contest:
inputs:
seed: {type: number}
steps:
- {name: ours, formula: 'ROLL("1D8+1", seed, "ours")'}
- {name: theirs, formula: 'ROLL("2D4", seed, "theirs")'}
- {name: won, formula: ours > theirs}
outputs: [ours, theirs, won]
$ echo '{}' | rmt run odds.yml
wins 100
chance 0.5
$ echo '{"fights": 2000}' | rmt run odds.yml
wins 1023
chance 0.51
- name: the same seed always gives the same contest
calculation: contest
facts: {seed: 7}
expect: {ours: 7, theirs: 4, won: true}
- name: two hundred contests, pinned by their seeds
facts: {}
expect: {wins: 100, chance: 0.5}
The shipped coc_7e_duel plays a whole Call of Cthulhu fight this way, round by round until one side is
down, and then the odds across 200 fights. See Dates and Dice
for how ROLL keys work.
What check Asks
$ rmt check broken_repeat.yml
✗ 4 problems
• a: step r repeats b with until but no limit; give it a limit, or times instead
• a: step s repeats b with times and until; give it times, or until and a limit
• a: step t repeats b without times or until; give it times, or until and a limit
• a: step u stops on finished, but b only outputs done
A repeat is a list of rows to later steps, as a run over a list is: read it with sum, COUNT,
FILTER, ANY or REDUCE, not as name.output.
Next Steps
- Dates and Dice - Days between dates, weekdays and repeatable rolls.
- Shipped Blueprints -
coc_7e_duelandcoc_7e_chaserepeat until something happens.