I think I got it:
dt <- cc[rep(1:.N, installments)][, Indx := 1:.N, by = .(period, customer, installments)]
dt[, period := period + Indx - 1]
dt[, inst_pay := purchase/installments]
dt[, spending := sum(inst_pay), by = .(period, customer)]
setorder(dt, customer, period)
print(dt)
period customer purchase installments Indx inst_pay spending
1: 1 1 90 3 1 30 30
2: 2 1 90 3 2 30 40
3: 2 1 20 2 1 10 40
4: 3 1 90 3 3 30 50
5: 3 1 20 2 2 10 50
6: 3 1 10 1 1 10 50
7: 1 2 50 2 1 25 25
8: 2 2 50 2 2 25 55
9: 2 2 60 2 1 30 55
10: 3 2 60 2 2 30 40
11: 3 2 10 1 1 10 40
Just out of curiosity I've taken a look at what happens under the hood, and I've used [dtruss/strace][1] on each test.
C++
./a.out < in
Saw 6512403 lines in 8 seconds. Crunch speed: 814050
syscalls `sudo dtruss -c ./a.out < in`
CALL COUNT
__mac_syscall 1
<snip>
open 6
pread 8
mprotect 17
mmap 22
stat64 30
read_nocancel 25958
Python
./a.py < in
Read 6512402 lines in 1 seconds. LPS: 6512402
syscalls `sudo dtruss -c ./a.py < in`
CALL COUNT
__mac_syscall 1
<snip>
open 5
pread 8
mprotect 17
mmap 21
stat64 29
[1]: http://en.wikipedia.org/wiki/Strace