Attribution
Conversions after paths of touches from four channels, credited by first touch, last touch, linear and position models with the same total and different leaders, and last touch decided by its lookback window.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
Conversions, each worth 60, each after a path of touches from search, social, email and affiliate, and four ways to share out the credit.
main.eml- the menu, the conversions, the four models side by side with each channel's rank, last touch by lookback window, and adding or removing conversionscredit.eml- the paths, each model's shares, the credit per channel, last touch within a window, the leader and the ranks
How each part works:
- A touch is a channel and the days before the conversion, listed from the oldest. The models: - first: all 60 to the first touch; last: all 60 to the last; - linear: an equal share to every touch; - position: 40% to the first and 40% to the last, 20% split among the ones between (one touch takes all, two take half each). Paths have 1 to 6 touches, so every share is a whole number.
- As in the corpus case
last-touch-vs-first-touch, every model gives out exactly 60 a conversion: a check that the credit adds up to the conversions passes for all four and says nothing about the split. For the eight default conversions the leader is social under first, email under last, and a tie of the two under linear and position. - As in the corpus case
attribution-window-picks-the-winner, last touch with a lookback window counts only touches within the window, and a conversion with none goes to nobody. With a window of 1, 3 or 7 days search leads; with 14 or 30, email does.
What is checked: menu choices 1 to 6; a path of 1 to 6 touches written channel:days, a known channel, days from 0 to 90 that only go down; at most 12 conversions. An empty answer cancels.
Sessions: sessions/basic.in gives: - the conversions, the models and the windows; - affiliate:40 email:35 added, dropped at every window, then the models.
sessions/bad-input.in gives: - menu choices 0 and x; - paths: a cancel, tv:3, search alone, search:x, search:91, search:5 email:9 (days going up) and seven touches; - four conversions of search:1 to reach 12, and one more; - every conversion removed, then the conversions, models and windows of none, and one more removal.
Built on the verified corpus cases last-touch-vs-first-touch (four models with the same total and different winners) and attribution-window-picks-the-winner (one model, and the lookback window decides).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Attribution ==
Conversions after paths of touches from four channels, and four ways to share out the credit.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 0
Pick a number from 1 to 6.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> x
Pick a number from 1 to 6.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path>
Cancelled.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path> tv:3
Each touch is search, social, email or affiliate, a colon, and the days.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path> search
Days are a whole number from 0 to 90.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path> search:x
Days are a whole number from 0 to 90.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path> search:91
Days are a whole number from 0 to 90.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path> search:5 email:9
List the touches from the oldest: the days can only go down.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path> search:9 search:8 search:7 search:6 search:5 search:4 search:3
A path has 1 to 6 touches.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path> search:1
Added conversion 9: search (1d)
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path> search:1
Added conversion 10: search (1d)
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path> search:1
Added conversion 11: search (1d)
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path> search:1
Added conversion 12: search (1d)
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
12 conversions is the most.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 11 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 10 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 9 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 8 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 7 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 6 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 5 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 4 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 3 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 2 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 1 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
Removed the last conversion; 0 left.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 1
No conversions.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 2
No conversions.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 3
No conversions.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 5
No conversions.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 6
Bye.
What was typed (42 lines)
0
x
4
4
tv:3
4
search
4
search:x
4
search:91
4
search:5 email:9
4
search:9 search:8 search:7 search:6 search:5 search:4 search:3
4
search:1
4
search:1
4
search:1
4
search:1
4
5
5
5
5
5
5
5
5
5
5
5
5
1
2
3
5
6
basic
interpreter: byte-equal== Attribution ==
Conversions after paths of touches from four channels, and four ways to share out the credit.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 1
Each conversion is worth 60; each touch is a channel and the days before the conversion.
1 social (20d) -> search (1d)
2 social (15d) -> email (6d)
3 affiliate (12d) -> email (9d)
4 social (25d) -> email (12d)
5 search (3d)
6 social (30d) -> affiliate (10d) -> email (13d)
7 affiliate (8d) -> social (3d)
8 email (18d) -> search (2d)
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 2
model search social email affiliate total leader
first 60 240 60 120 480 social
last 180 60 240 0 480 email
linear 120 140 140 80 480 social and email
position 120 144 144 72 480 social and email
Ranks, 1 for the most credit:
channel first last linear position
search 3 2 3 3
social 1 3 1 1
email 3 1 1 1
affiliate 2 4 4 4
Every model gives out 480, all there is: a check that the credit adds up to the conversions passes for all four, and says nothing about the split.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 3
Last touch, counting only touches within the lookback window:
window search social email affiliate attributed dropped leader
1 days 60 0 0 0 1 7 search
3 days 180 60 0 0 4 4 search
7 days 180 60 60 0 5 3 search
14 days 180 60 240 0 8 0 email
30 days 180 60 240 0 8 0 email
A conversion with no touch inside the window goes to nobody; attributed plus dropped is every conversion at every window.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 4
A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).
path> affiliate:40 email:35
Added conversion 9: affiliate (40d) -> email (35d)
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 3
Last touch, counting only touches within the lookback window:
window search social email affiliate attributed dropped leader
1 days 60 0 0 0 1 8 search
3 days 180 60 0 0 4 5 search
7 days 180 60 60 0 5 4 search
14 days 180 60 240 0 8 1 email
30 days 180 60 240 0 8 1 email
A conversion with no touch inside the window goes to nobody; attributed plus dropped is every conversion at every window.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 2
model search social email affiliate total leader
first 60 240 60 180 540 social
last 180 60 300 0 540 email
linear 120 140 170 110 540 email
position 120 144 174 102 540 email
Ranks, 1 for the most credit:
channel first last linear position
search 3 2 3 3
social 1 3 2 2
email 3 1 1 1
affiliate 2 4 4 4
Every model gives out 540, all there is: a check that the credit adds up to the conversions passes for all four, and says nothing about the split.
1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit
choice> 6
Bye.
What was typed (8 lines)
1
2
3
4
affiliate:40 email:35
3
2
6
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P136 attribution: the conversions and their paths, four models side by
# side, last touch by lookback window, and adding or removing conversions.
import credit
def plain(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def pad(s, n):
s => out
while len(out) < n:
out + " " => out
return out
def lpad(s, n):
s => out
while len(out) < n:
" " + out => out
return out
def path_text(p):
"" => out
for t in p:
if out != "":
out + " -> " => out
out + t[0] + " (" + str(t[1]) + "d)" => out
return out
def show(paths):
"Each conversion is worth 60; each touch is a channel and the days before the conversion." ^0
for k in [0:len(paths) - 1]:
(" " + lpad(str(k + 1), 2) + " " + path_text(paths[k])) ^0
def header(first):
" " + pad(first, 10) => h
for ch in credit.channels():
h + lpad(ch, 10) => h
return h
def models(paths):
(header("model") + " total leader") ^0
[] => cs
for m in ["first", "last", "linear", "position"]:
credit.credit(paths, m) => c
cs + [c] => cs
" " + pad(m, 10) => line
0 => total
for ch in credit.channels():
line + lpad(str(c[ch]), 10) => line
total + c[ch] => total
(line + lpad(str(total), 10) + " " + credit.leader(c)) ^0
"Ranks, 1 for the most credit:" ^0
" " + pad("channel", 10) => h
for m in ["first", "last", "linear", "position"]:
h + lpad(m, 10) => h
h ^0
for ch in credit.channels():
" " + pad(ch, 10) => line
for c in cs:
line + lpad(str(credit.rank(c, ch)), 10) => line
line ^0
("Every model gives out " + str(60 * len(paths)) + ", all there is: a check that the credit adds up to the conversions passes for all four, and says nothing about the split.") ^0
def windows(paths):
"Last touch, counting only touches within the lookback window:" ^0
(header("window") + " attributed dropped leader") ^0
for w in [1, 3, 7, 14, 30]:
credit.last_in_window(paths, w) => r
" " + pad(str(w) + " days", 10) => line
for ch in credit.channels():
line + lpad(str(r[0][ch]), 10) => line
"-" => lead
if r[1] > 0:
credit.leader(r[0]) => lead
(line + lpad(str(r[1]), 12) + lpad(str(r[2]), 9) + " " + lead) ^0
"A conversion with no touch inside the window goes to nobody; attributed plus dropped is every conversion at every window." ^0
def add(paths):
if len(paths) >= 12:
"12 conversions is the most." ^0
return paths
"A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0)." ^0
plain(input("path> ")) => t
if t == "":
"Cancelled." ^0
return paths
[] => p
"" => w
for c in t + " ":
if c == " ":
if w != "":
"" => ch
"" => d
False => colon
for x in w:
if x == ":" and not colon:
True => colon
elif colon:
d + x => d
else:
ch + x => ch
False => known
for k in credit.channels():
if k == ch:
True => known
if not known:
"Each touch is search, social, email or affiliate, a colon, and the days." ^0
return paths
if len(d) == 0 or len(d) > 2:
"Days are a whole number from 0 to 90." ^0
return paths
for x in d:
if x < "0" or x > "9":
"Days are a whole number from 0 to 90." ^0
return paths
if int(d) > 90:
"Days are a whole number from 0 to 90." ^0
return paths
p + [[ch, int(d)]] => p
"" => w
else:
w + c => w
if len(p) == 0 or len(p) > 6:
"A path has 1 to 6 touches." ^0
return paths
for k in [1:len(p) - 1]:
if p[k][1] > p[k - 1][1]:
"List the touches from the oldest: the days can only go down." ^0
return paths
("Added conversion " + str(len(paths) + 1) + ": " + path_text(p)) ^0
return paths + [p]
credit.default_paths() => paths
"== Attribution ==" ^0
"Conversions after paths of touches from four channels, and four ways to share out the credit." ^0
True => running
while running:
"" ^0
"1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
if len(paths) == 0:
"No conversions." ^0
else:
show(paths)
elif choice == "2":
if len(paths) == 0:
"No conversions." ^0
else:
models(paths)
elif choice == "3":
if len(paths) == 0:
"No conversions." ^0
else:
windows(paths)
elif choice == "4":
add(paths) => paths
elif choice == "5":
if len(paths) == 0:
"No conversions." ^0
else:
paths[0:len(paths) - 1] => paths
("Removed the last conversion; " + str(len(paths)) + " left.") ^0
elif choice == "6":
False => running
else:
"Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import credit
def plain(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def pad(s, n):
out = s
while len(out) < n:
out = out + " "
return out
def lpad(s, n):
out = s
while len(out) < n:
out = " " + out
return out
def path_text(p):
out = ""
for t in p:
if out != "":
out = out + " -> "
out = out + t[0] + " (" + str(t[1]) + "d)"
return out
def show(paths):
print("Each conversion is worth 60; each touch is a channel and the days before the conversion.")
for k in range(0, len(paths)):
print(" " + lpad(str(k + 1), 2) + " " + path_text(paths[k]))
def header(first):
h = " " + pad(first, 10)
for ch in credit.channels():
h = h + lpad(ch, 10)
return h
def models(paths):
print(header("model") + " total leader")
cs = []
for m in ["first", "last", "linear", "position"]:
c = credit.credit(paths, m)
cs = cs + [c]
line = " " + pad(m, 10)
total = 0
for ch in credit.channels():
line = line + lpad(str(c[ch]), 10)
total = total + c[ch]
print(line + lpad(str(total), 10) + " " + credit.leader(c))
print("Ranks, 1 for the most credit:")
h = " " + pad("channel", 10)
for m in ["first", "last", "linear", "position"]:
h = h + lpad(m, 10)
print(h)
for ch in credit.channels():
line = " " + pad(ch, 10)
for c in cs:
line = line + lpad(str(credit.rank(c, ch)), 10)
print(line)
print("Every model gives out " + str(60 * len(paths)) + ", all there is: a check that the credit adds up to the conversions passes for all four, and says nothing about the split.")
def windows(paths):
print("Last touch, counting only touches within the lookback window:")
print(header("window") + " attributed dropped leader")
for w in [1, 3, 7, 14, 30]:
r = credit.last_in_window(paths, w)
line = " " + pad(str(w) + " days", 10)
for ch in credit.channels():
line = line + lpad(str(r[0][ch]), 10)
lead = "-"
if r[1] > 0:
lead = credit.leader(r[0])
print(line + lpad(str(r[1]), 12) + lpad(str(r[2]), 9) + " " + lead)
print("A conversion with no touch inside the window goes to nobody; attributed plus dropped is every conversion at every window.")
def add(paths):
if len(paths) >= 12:
print("12 conversions is the most.")
return paths
print("A path from the oldest touch: channel:days, such as social:20 email:5 search:1 (1 to 6 touches, days from 90 down to 0).")
t = plain(input("path> "))
if t == "":
print("Cancelled.")
return paths
p = []
w = ""
for c in t + " ":
if c == " ":
if w != "":
ch = ""
d = ""
colon = False
for x in w:
if x == ":" and not colon:
colon = True
elif colon:
d = d + x
else:
ch = ch + x
known = False
for k in credit.channels():
if k == ch:
known = True
if not known:
print("Each touch is search, social, email or affiliate, a colon, and the days.")
return paths
if len(d) == 0 or len(d) > 2:
print("Days are a whole number from 0 to 90.")
return paths
for x in d:
if x < "0" or x > "9":
print("Days are a whole number from 0 to 90.")
return paths
if int(d) > 90:
print("Days are a whole number from 0 to 90.")
return paths
p = p + [[ch, int(d)]]
w = ""
else:
w = w + c
if len(p) == 0 or len(p) > 6:
print("A path has 1 to 6 touches.")
return paths
for k in range(1, len(p)):
if p[k][1] > p[k - 1][1]:
print("List the touches from the oldest: the days can only go down.")
return paths
print("Added conversion " + str(len(paths) + 1) + ": " + path_text(p))
return paths + [p]
paths = credit.default_paths()
print("== Attribution ==")
print("Conversions after paths of touches from four channels, and four ways to share out the credit.")
running = True
while running:
print("")
print("1) conversions 2) models 3) last touch by window 4) add a conversion 5) remove the last 6) quit")
choice = plain(input("choice> "))
if choice == "1":
if len(paths) == 0:
print("No conversions.")
else:
show(paths)
elif choice == "2":
if len(paths) == 0:
print("No conversions.")
else:
models(paths)
elif choice == "3":
if len(paths) == 0:
print("No conversions.")
else:
windows(paths)
elif choice == "4":
paths = add(paths)
elif choice == "5":
if len(paths) == 0:
print("No conversions.")
else:
paths = paths[0:len(paths) - 1]
print("Removed the last conversion; " + str(len(paths)) + " left.")
elif choice == "6":
running = False
else:
print("Pick a number from 1 to 6.")
print("Bye.")
credit.eml
eml# P136 attribution - conversions, each worth 60, each after a path of
# touches from marketing channels, and four ways to give each channel its
# share. As in the corpus case last-touch-vs-first-touch, every model hands
# out exactly the same total and the winner still changes. As in
# attribution-window-picks-the-winner, last touch with a lookback window
# drops the touches older than the window, and the window alone can pick
# the winner.
#
# A touch is [channel, days before the conversion]; a path lists its
# touches from the oldest. Paths have 1 to 6 touches, so every share of 60
# is a whole number.
def channels():
return ["search", "social", "email", "affiliate"]
def default_paths():
return [[["social", 20], ["search", 1]], [["social", 15], ["email", 6]], [["affiliate", 12], ["email", 9]], [["social", 25], ["email", 12]], [["search", 3]], [["social", 30], ["affiliate", 10], ["email", 13]], [["affiliate", 8], ["social", 3]], [["email", 18], ["search", 2]]]
def shares(path, model):
# [channel, share of 60] for each touch of the path
len(path) => n
[] => out
for k in [0:n - 1]:
0 => s
if model == "first":
if k == 0:
60 => s
elif model == "last":
if k == n - 1:
60 => s
elif model == "linear":
int(60 / n) => s
else:
# position: 40% to the first touch, 40% to the last, 20% split
# among the ones between; one touch takes all, two take half each
if n == 1:
60 => s
elif n == 2:
30 => s
elif k == 0 or k == n - 1:
24 => s
else:
int(12 / (n - 2)) => s
out + [[path[k][0], s]] => out
return out
def credit(paths, model):
# {channel: credit}
{} => c
for ch in channels():
0 => c[ch]
for p in paths:
for t in shares(p, model):
c[t[0]] + t[1] => c[t[0]]
return c
def last_in_window(paths, window):
# [{channel: credit}, conversions attributed, dropped]: the last touch no
# more than window days before the conversion takes all 60
{} => c
for ch in channels():
0 => c[ch]
0 => got
0 => dropped
for p in paths:
"" => winner
for t in p:
if t[1] <= window:
t[0] => winner
if winner == "":
dropped + 1 => dropped
else:
c[winner] + 60 => c[winner]
got + 1 => got
return [c, got, dropped]
def leader(c):
# the channels with the most credit, joined by "and" when they tie
0 - 1 => most
for ch in channels():
if c[ch] > most:
c[ch] => most
"" => out
for ch in channels():
if c[ch] == most:
if out != "":
out + " and " => out
out + ch => out
return out
def rank(c, ch):
1 => r
for other in channels():
if c[other] > c[ch]:
r + 1 => r
return r
Python projection (credit.py)
def channels():
return ["search", "social", "email", "affiliate"]
def default_paths():
return [[["social", 20], ["search", 1]], [["social", 15], ["email", 6]], [["affiliate", 12], ["email", 9]], [["social", 25], ["email", 12]], [["search", 3]], [["social", 30], ["affiliate", 10], ["email", 13]], [["affiliate", 8], ["social", 3]], [["email", 18], ["search", 2]]]
def shares(path, model):
n = len(path)
out = []
for k in range(0, n):
s = 0
if model == "first":
if k == 0:
s = 60
elif model == "last":
if k == n - 1:
s = 60
elif model == "linear":
s = int(60 / n)
elif n == 1:
s = 60
elif n == 2:
s = 30
elif k == 0 or k == n - 1:
s = 24
else:
s = int(12 / (n - 2))
out = out + [[path[k][0], s]]
return out
def credit(paths, model):
c = {}
for ch in channels():
c[ch] = 0
for p in paths:
for t in shares(p, model):
c[t[0]] = c[t[0]] + t[1]
return c
def last_in_window(paths, window):
c = {}
for ch in channels():
c[ch] = 0
got = 0
dropped = 0
for p in paths:
winner = ""
for t in p:
if t[1] <= window:
winner = t[0]
if winner == "":
dropped = dropped + 1
else:
c[winner] = c[winner] + 60
got = got + 1
return [c, got, dropped]
def leader(c):
most = 0 - 1
for ch in channels():
if c[ch] > most:
most = c[ch]
out = ""
for ch in channels():
if c[ch] == most:
if out != "":
out = out + " and "
out = out + ch
return out
def rank(c, ch):
r = 1
for other in channels():
if c[other] > c[ch]:
r = r + 1
return r