Survey analyzer
A small survey: each question's exact mean, median and spread; the Net Promoter Score and the scores that would move it; the top respondents under strict, dense and exclude policies for ties; and a split by satisfaction.
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
A small survey: how likely each person is to recommend us (0 to 10), and three questions on a scale of 1 to 5. The program shows the responses, each question's average and spread, the Net Promoter Score and what moves it, the top respondents when scores tie, and a split by satisfaction.
main.eml- the menu, the tables and the reports, and adding a responsesurvey.eml- exact fractions, medians, the promoter groups, totals, the ranking and the three top-N policies
How each part works:
- Averages are exact: the mean is shown as a fraction and to two decimals. The median of an even count is the mean of the middle two. "4 or 5" counts the top two answers.
- The Net Promoter Score is the share of promoters (9 or 10) minus the share of detractors (0 to 6), from -100 to +100. A recommend score counts only when it crosses from 6 to 7 or from 8 to 9. The program names who would change the score with one point more, and how many others would move only the mean.
- The top respondents by the total of q1 to q3, as in the corpus case
top-n-with-ties: - strict: exactly N, the first N of a ranking that keeps equal totals in their order; - dense: everyone with a total at least the N-th's; - exclude: when the N-th is tied with the next one, only those above the tie.
Strict depends on the order of the responses, so the program also shows the reverse order whenever that gives other people. - Satisfied or not, as in the corpus case survey-score-analyzer: a recommend score of 7 or more, against 6 or less.
What is checked: menu choices 1 to 7; a top-N count from 1 to the number of responses; a new response as a name of up to 6 lower-case letters, not yet in the survey, a recommend score from 0 to 10 and three answers from 1 to 5; at most 30 responses. An empty answer cancels.
Sessions: sessions/basic.in gives: - the 12 responses, each question, and the Net Promoter Score of 16.67; - the top 3, then the top 4 (strict takes bo, or kai in reverse order; dense keeps both; exclude drops both), then the top 2 (strict takes ana, or fay in reverse order); - the split by satisfaction; - zed added as a detractor with top answers: the score falls to 7.69, and the top 4 again.
sessions/bad-input.in gives: - menu choices 0 and x; - top-N counts: a cancel, 0, 13 and x; - new responses: a cancel, three numbers, a name already in, upper case, a name too long, a recommend score of 11, an answer of 6, and a letter for a number.
Built on the verified corpus cases survey-score-analyzer (respondents split by a threshold and averaged) and top-n-with-ties (the top N is not defined until a policy for ties is).
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== Survey analyzer ==
A survey: how likely each person is to recommend us (0 to 10), and three questions on a scale of 1 to 5.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 0
Pick a number from 1 to 7.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> x
Pick a number from 1 to 7.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 4
how many at the top (1 to 12)>
Cancelled.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 4
how many at the top (1 to 12)> 0
Type a whole number from 1 to 12.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 4
how many at the top (1 to 12)> 13
Type a whole number from 1 to 12.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 4
how many at the top (1 to 12)> x
Type a whole number from 1 to 12.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 6
A response as name, recommend (0 to 10) and q1 to q3 (1 to 5), such as zed 8 4 4 3.
response>
Cancelled.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 6
A response as name, recommend (0 to 10) and q1 to q3 (1 to 5), such as zed 8 4 4 3.
response> zed 8 4 4
Type a name and four numbers.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 6
A response as name, recommend (0 to 10) and q1 to q3 (1 to 5), such as zed 8 4 4 3.
response> ana 8 4 4 4
ana has answered already.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 6
A response as name, recommend (0 to 10) and q1 to q3 (1 to 5), such as zed 8 4 4 3.
response> Zed 8 4 4 4
A name is lower-case letters.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 6
A response as name, recommend (0 to 10) and q1 to q3 (1 to 5), such as zed 8 4 4 3.
response> zedzedz 8 4 4 4
Keep a name to 6 letters.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 6
A response as name, recommend (0 to 10) and q1 to q3 (1 to 5), such as zed 8 4 4 3.
response> zed 11 4 4 4
The recommend score is a whole number from 0 to 10.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 6
A response as name, recommend (0 to 10) and q1 to q3 (1 to 5), such as zed 8 4 4 3.
response> zed 8 6 4 4
q1 to q3 are whole numbers from 1 to 5.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 6
A response as name, recommend (0 to 10) and q1 to q3 (1 to 5), such as zed 8 4 4 3.
response> zed x 4 4 4
The recommend score is a whole number from 0 to 10.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 7
Bye.
What was typed (27 lines)
0
x
4
4
0
4
13
4
x
6
6
zed 8 4 4
6
ana 8 4 4 4
6
Zed 8 4 4 4
6
zedzedz 8 4 4 4
6
zed 11 4 4 4
6
zed 8 6 4 4
6
zed x 4 4 4
7
basic
interpreter: byte-equal== Survey analyzer ==
A survey: how likely each person is to recommend us (0 to 10), and three questions on a scale of 1 to 5.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 1
name recommend group q1 q2 q3 total
ana 10 promoter 5 4 5 14
bo 9 promoter 4 4 4 12
cy 8 passive 4 3 4 11
dee 7 passive 3 4 3 10
eli 6 detractor 3 3 2 8
fay 9 promoter 5 5 4 14
gus 3 detractor 2 1 2 5
hal 10 promoter 5 5 5 15
ivy 8 passive 4 4 3 11
jo 5 detractor 2 3 3 8
kai 9 promoter 4 4 4 12
lu 7 passive 4 3 4 11
12 responses.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 2
recommend mean 7.58 (91/12), median 8 3:1 5:1 6:1 7:2 8:2 9:3 10:2
q1 mean 3.75 (15/4), median 4 2:2 3:2 4:5 5:3
answered 4 or 5: 8 of 12, 66.67%
q2 mean 3.58 (43/12), median 4 1:1 3:4 4:5 5:2
answered 4 or 5: 7 of 12, 58.33%
q3 mean 3.58 (43/12), median 4 2:2 3:3 4:5 5:2
answered 4 or 5: 7 of 12, 58.33%
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 3
Promoters (9-10): 5, passives (7-8): 4, detractors (0-6): 3, of 12.
Net Promoter Score: 16.67 (50/3), on a scale from -100 to +100.
One point more would change the score only for cy 8 to 9, eli 6 to 7, ivy 8 to 9: a recommend score counts only when it crosses 6 to 7 or 8 to 9.
7 others would raise the mean recommend score and leave the Net Promoter Score as it is.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 4
how many at the top (1 to 12)> 3
Top 3 by total of q1 to q3:
strict 3: hal 15, ana 14, fay 14
dense 3: hal 15, ana 14, fay 14
exclude 3: hal 15, ana 14, fay 14
Strict gives exactly the number asked for, but when the cut falls inside a tie it keeps whoever came first. Dense keeps the whole tie, exclude drops it.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 4
how many at the top (1 to 12)> 4
Top 4 by total of q1 to q3:
strict 4: hal 15, ana 14, fay 14, bo 12
with the responses in the reverse order: hal 15, fay 14, ana 14, kai 12
dense 5: hal 15, ana 14, fay 14, bo 12, kai 12
exclude 3: hal 15, ana 14, fay 14
Strict gives exactly the number asked for, but when the cut falls inside a tie it keeps whoever came first. Dense keeps the whole tie, exclude drops it.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 4
how many at the top (1 to 12)> 2
Top 2 by total of q1 to q3:
strict 2: hal 15, ana 14
with the responses in the reverse order: hal 15, fay 14
dense 3: hal 15, ana 14, fay 14
exclude 1: hal 15
Strict gives exactly the number asked for, but when the cut falls inside a tie it keeps whoever came first. Dense keeps the whole tie, exclude drops it.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 5
recommend 7 or more 9, mean total of q1 to q3 12.22
recommend 6 or less 3, mean total of q1 to q3 7.00
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 6
A response as name, recommend (0 to 10) and q1 to q3 (1 to 5), such as zed 8 4 4 3.
response> zed 6 5 5 5
zed added.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 3
Promoters (9-10): 5, passives (7-8): 4, detractors (0-6): 4, of 13.
Net Promoter Score: 7.69 (100/13), on a scale from -100 to +100.
One point more would change the score only for cy 8 to 9, eli 6 to 7, ivy 8 to 9, zed 6 to 7: a recommend score counts only when it crosses 6 to 7 or 8 to 9.
7 others would raise the mean recommend score and leave the Net Promoter Score as it is.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 4
how many at the top (1 to 13)> 4
Top 4 by total of q1 to q3:
strict 4: hal 15, zed 15, ana 14, fay 14
dense 4: hal 15, zed 15, ana 14, fay 14
exclude 4: hal 15, zed 15, ana 14, fay 14
Strict gives exactly the number asked for, but when the cut falls inside a tie it keeps whoever came first. Dense keeps the whole tie, exclude drops it.
1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit
choice> 7
Bye.
What was typed (16 lines)
1
2
3
4
3
4
4
4
2
5
6
zed 6 5 5 5
3
4
4
7
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# P112 survey analyzer: responses, each question's average and spread,
# the Net Promoter Score and what moves it, and the top respondents when
# scores tie.
import survey
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 number(s, most):
if len(s) == 0 or len(s) > most:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
return n
def pad(s, n):
while len(s) < n:
s + " " => s
return s
def lpad(s, n):
while len(s) < n:
" " + s => s
return s
def names(rows):
"" => out
for r in rows:
if out != "":
out + ", " => out
out + r[0] + " " + str(survey.total(r)) => out
if out == "":
return "(nobody)"
return out
def show_rows(rows):
" name recommend group q1 q2 q3 total" ^0
for r in rows:
(" " + pad(r[0], 7) + lpad(str(r[1]), 9) + " " + pad(survey.group(r[1]), 10) + lpad(str(r[2]), 2) + lpad(str(r[3]), 3) + lpad(str(r[4]), 3) + lpad(str(survey.total(r)), 7)) ^0
(str(len(rows)) + " responses.") ^0
def questions(rows):
["recommend", "q1", "q2", "q3"] => labels
for k in [1:4]:
survey.column(rows, k) => xs
0 => s
for x in xs:
s + x => s
survey.median_twice(xs) => m2
str(int(m2 / 2)) => med
if m2 % 2 == 1:
str(int((m2 - 1) / 2)) + ".5" => med
0 => low
10 => high
if k > 1:
1 => low
5 => high
"" => dist
for v in [low:high]:
0 => c
for x in xs:
if x == v:
c + 1 => c
if c > 0:
if dist != "":
dist + " " => dist
dist + str(v) + ":" + str(c) => dist
(" " + pad(labels[k - 1], 10) + "mean " + survey.two_places(s, len(xs)) + " (" + survey.fraction_text(s, len(xs)) + "), median " + med + " " + dist) ^0
if k > 1:
0 => box
for x in xs:
if x >= 4:
box + 1 => box
(" " + pad("", 10) + "answered 4 or 5: " + str(box) + " of " + str(len(xs)) + ", " + survey.two_places(box * 100, len(xs)) + "%") ^0
def show_nps(rows):
survey.nps(rows) => g
len(rows) => n
("Promoters (9-10): " + str(g[0]) + ", passives (7-8): " + str(g[1]) + ", detractors (0-6): " + str(g[2]) + ", of " + str(n) + ".") ^0
("Net Promoter Score: " + survey.two_places((g[0] - g[2]) * 100, n) + " (" + survey.fraction_text((g[0] - g[2]) * 100, n) + "), on a scale from -100 to +100.") ^0
[] => moves
[] => still
for r in rows:
if r[1] == 6 or r[1] == 8:
moves + [r[0] + " " + str(r[1]) + " to " + str(r[1] + 1)] => moves
elif r[1] < 10:
still + [r[0]] => still
"" => a
for x in moves:
if a != "":
a + ", " => a
a + x => a
if a == "":
"nobody" => a
("One point more would change the score only for " + a + ": a recommend score counts only when it crosses 6 to 7 or 8 to 9.") ^0
(str(len(still)) + " others would raise the mean recommend score and leave the Net Promoter Score as it is.") ^0
def show_top(rows):
plain(input("how many at the top (1 to " + str(len(rows)) + ")> ")) => t
if t == "":
"Cancelled." ^0
return
number(t, 2) => n
if n < 1 or n > len(rows):
("Type a whole number from 1 to " + str(len(rows)) + ".") ^0
return
("Top " + str(n) + " by total of q1 to q3:") ^0
for p in ["strict", "dense", "exclude"]:
survey.top(rows, n, p) => a
survey.top(survey.reversed_rows(rows), n, p) => b
(" " + pad(p, 8) + str(len(a)) + ": " + names(a)) ^0
True => same
if len(a) != len(b):
False => same
else:
for x in a:
False => found
for y in b:
if y[0] == x[0]:
True => found
if not found:
False => same
if not same:
(" " + pad("", 8) + "with the responses in the reverse order: " + names(b)) ^0
"Strict gives exactly the number asked for, but when the cut falls inside a tie it keeps whoever came first. Dense keeps the whole tie, exclude drops it." ^0
def split(rows):
[] => happy
[] => unhappy
for r in rows:
if r[1] >= 7:
happy + [r] => happy
else:
unhappy + [r] => unhappy
for part in [["recommend 7 or more", happy], ["recommend 6 or less", unhappy]]:
part[1] => g
if len(g) == 0:
(" " + pad(part[0], 21) + "nobody") ^0
else:
0 => s
for r in g:
s + survey.total(r) => s
(" " + pad(part[0], 21) + str(len(g)) + ", mean total of q1 to q3 " + survey.two_places(s, len(g))) ^0
def add(rows):
"A response as name, recommend (0 to 10) and q1 to q3 (1 to 5), such as zed 8 4 4 3." ^0
plain(input("response> ")) => t
if t == "":
"Cancelled." ^0
return rows
[] => w
"" => cur
for c in t + " ":
if c == " ":
if cur != "":
w + [cur] => w
"" => cur
else:
cur + c => cur
if len(w) != 5:
"Type a name and four numbers." ^0
return rows
for r in rows:
if r[0] == w[0]:
(w[0] + " has answered already.") ^0
return rows
if len(w[0]) > 6:
"Keep a name to 6 letters." ^0
return rows
for c in w[0]:
if not (c >= "a" and c <= "z"):
"A name is lower-case letters." ^0
return rows
number(w[1], 2) => rec
if rec < 0 or rec > 10:
"The recommend score is a whole number from 0 to 10." ^0
return rows
[] => q
for k in [2:4]:
number(w[k], 1) => v
if v < 1 or v > 5:
"q1 to q3 are whole numbers from 1 to 5." ^0
return rows
q + [v] => q
if len(rows) >= 30:
"The survey holds 30 responses." ^0
return rows
(w[0] + " added.") ^0
return rows + [[w[0], rec, q[0], q[1], q[2]]]
[["ana", 10, 5, 4, 5], ["bo", 9, 4, 4, 4], ["cy", 8, 4, 3, 4], ["dee", 7, 3, 4, 3], ["eli", 6, 3, 3, 2], ["fay", 9, 5, 5, 4], ["gus", 3, 2, 1, 2], ["hal", 10, 5, 5, 5], ["ivy", 8, 4, 4, 3], ["jo", 5, 2, 3, 3], ["kai", 9, 4, 4, 4], ["lu", 7, 4, 3, 4]] => rows
"== Survey analyzer ==" ^0
"A survey: how likely each person is to recommend us (0 to 10), and three questions on a scale of 1 to 5." ^0
True => running
while running:
"" ^0
"1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show_rows(rows)
elif choice == "2":
questions(rows)
elif choice == "3":
show_nps(rows)
elif choice == "4":
show_top(rows)
elif choice == "5":
split(rows)
elif choice == "6":
add(rows) => rows
elif choice == "7":
False => running
else:
"Pick a number from 1 to 7." ^0
"Bye." ^0
Python projection (main.py)
import survey
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 number(s, most):
if len(s) == 0 or len(s) > most:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
return n
def pad(s, n):
while len(s) < n:
s = s + " "
return s
def lpad(s, n):
while len(s) < n:
s = " " + s
return s
def names(rows):
out = ""
for r in rows:
if out != "":
out = out + ", "
out = out + r[0] + " " + str(survey.total(r))
if out == "":
return "(nobody)"
return out
def show_rows(rows):
print(" name recommend group q1 q2 q3 total")
for r in rows:
print(" " + pad(r[0], 7) + lpad(str(r[1]), 9) + " " + pad(survey.group(r[1]), 10) + lpad(str(r[2]), 2) + lpad(str(r[3]), 3) + lpad(str(r[4]), 3) + lpad(str(survey.total(r)), 7))
print(str(len(rows)) + " responses.")
def questions(rows):
labels = ["recommend", "q1", "q2", "q3"]
for k in range(1, 5):
xs = survey.column(rows, k)
s = 0
for x in xs:
s = s + x
m2 = survey.median_twice(xs)
med = str(int(m2 / 2))
if m2 % 2 == 1:
med = str(int((m2 - 1) / 2)) + ".5"
low = 0
high = 10
if k > 1:
low = 1
high = 5
dist = ""
for v in range(low, high+1):
c = 0
for x in xs:
if x == v:
c = c + 1
if c > 0:
if dist != "":
dist = dist + " "
dist = dist + str(v) + ":" + str(c)
print(" " + pad(labels[k - 1], 10) + "mean " + survey.two_places(s, len(xs)) + " (" + survey.fraction_text(s, len(xs)) + "), median " + med + " " + dist)
if k > 1:
box = 0
for x in xs:
if x >= 4:
box = box + 1
print(" " + pad("", 10) + "answered 4 or 5: " + str(box) + " of " + str(len(xs)) + ", " + survey.two_places(box * 100, len(xs)) + "%")
def show_nps(rows):
g = survey.nps(rows)
n = len(rows)
print("Promoters (9-10): " + str(g[0]) + ", passives (7-8): " + str(g[1]) + ", detractors (0-6): " + str(g[2]) + ", of " + str(n) + ".")
print("Net Promoter Score: " + survey.two_places((g[0] - g[2]) * 100, n) + " (" + survey.fraction_text((g[0] - g[2]) * 100, n) + "), on a scale from -100 to +100.")
moves = []
still = []
for r in rows:
if r[1] == 6 or r[1] == 8:
moves = moves + [r[0] + " " + str(r[1]) + " to " + str(r[1] + 1)]
elif r[1] < 10:
still = still + [r[0]]
a = ""
for x in moves:
if a != "":
a = a + ", "
a = a + x
if a == "":
a = "nobody"
print("One point more would change the score only for " + a + ": a recommend score counts only when it crosses 6 to 7 or 8 to 9.")
print(str(len(still)) + " others would raise the mean recommend score and leave the Net Promoter Score as it is.")
def show_top(rows):
t = plain(input("how many at the top (1 to " + str(len(rows)) + ")> "))
if t == "":
print("Cancelled.")
return
n = number(t, 2)
if n < 1 or n > len(rows):
print("Type a whole number from 1 to " + str(len(rows)) + ".")
return
print("Top " + str(n) + " by total of q1 to q3:")
for p in ["strict", "dense", "exclude"]:
a = survey.top(rows, n, p)
b = survey.top(survey.reversed_rows(rows), n, p)
print(" " + pad(p, 8) + str(len(a)) + ": " + names(a))
same = True
if len(a) != len(b):
same = False
else:
for x in a:
found = False
for y in b:
if y[0] == x[0]:
found = True
if not found:
same = False
if not same:
print(" " + pad("", 8) + "with the responses in the reverse order: " + names(b))
print("Strict gives exactly the number asked for, but when the cut falls inside a tie it keeps whoever came first. Dense keeps the whole tie, exclude drops it.")
def split(rows):
happy = []
unhappy = []
for r in rows:
if r[1] >= 7:
happy = happy + [r]
else:
unhappy = unhappy + [r]
for part in [["recommend 7 or more", happy], ["recommend 6 or less", unhappy]]:
g = part[1]
if len(g) == 0:
print(" " + pad(part[0], 21) + "nobody")
else:
s = 0
for r in g:
s = s + survey.total(r)
print(" " + pad(part[0], 21) + str(len(g)) + ", mean total of q1 to q3 " + survey.two_places(s, len(g)))
def add(rows):
print("A response as name, recommend (0 to 10) and q1 to q3 (1 to 5), such as zed 8 4 4 3.")
t = plain(input("response> "))
if t == "":
print("Cancelled.")
return rows
w = []
cur = ""
for c in t + " ":
if c == " ":
if cur != "":
w = w + [cur]
cur = ""
else:
cur = cur + c
if len(w) != 5:
print("Type a name and four numbers.")
return rows
for r in rows:
if r[0] == w[0]:
print(w[0] + " has answered already.")
return rows
if len(w[0]) > 6:
print("Keep a name to 6 letters.")
return rows
for c in w[0]:
if not (c >= "a" and c <= "z"):
print("A name is lower-case letters.")
return rows
rec = number(w[1], 2)
if rec < 0 or rec > 10:
print("The recommend score is a whole number from 0 to 10.")
return rows
q = []
for k in range(2, 5):
v = number(w[k], 1)
if v < 1 or v > 5:
print("q1 to q3 are whole numbers from 1 to 5.")
return rows
q = q + [v]
if len(rows) >= 30:
print("The survey holds 30 responses.")
return rows
print(w[0] + " added.")
return rows + [[w[0], rec, q[0], q[1], q[2]]]
rows = [["ana", 10, 5, 4, 5], ["bo", 9, 4, 4, 4], ["cy", 8, 4, 3, 4], ["dee", 7, 3, 4, 3], ["eli", 6, 3, 3, 2], ["fay", 9, 5, 5, 4], ["gus", 3, 2, 1, 2], ["hal", 10, 5, 5, 5], ["ivy", 8, 4, 4, 3], ["jo", 5, 2, 3, 3], ["kai", 9, 4, 4, 4], ["lu", 7, 4, 3, 4]]
print("== Survey analyzer ==")
print("A survey: how likely each person is to recommend us (0 to 10), and three questions on a scale of 1 to 5.")
running = True
while running:
print("")
print("1) responses 2) questions 3) Net Promoter Score 4) top respondents 5) satisfied or not 6) add a response 7) quit")
choice = plain(input("choice> "))
if choice == "1":
show_rows(rows)
elif choice == "2":
questions(rows)
elif choice == "3":
show_nps(rows)
elif choice == "4":
show_top(rows)
elif choice == "5":
split(rows)
elif choice == "6":
rows = add(rows)
elif choice == "7":
running = False
else:
print("Pick a number from 1 to 7.")
print("Bye.")
survey.eml
eml# P112 survey analyzer - each response is [name, recommend 0-10, q1, q2,
# q3] with q1-q3 on a 1-5 scale. Averages are exact fractions; the Net
# Promoter Score is the share of promoters (9-10) minus the share of
# detractors (0-6). As in the corpus case survey-score-analyzer,
# respondents split at a threshold; as in the corpus case top-n-with-ties,
# "the top N" on scores with ties depends on a policy - strict, dense or
# exclude - and strict's answer depends on the order of the input.
def gcd(a, b):
while b != 0:
a % b => r
b => a
r => b
return a
def fraction_text(num, den):
# "49/12", "4" or "-1/3"
"" => sign
num => a
if num < 0:
"-" => sign
0 - num => a
gcd(a, den) => g
if g == 0:
1 => g
int(a / g) => a
int(den / g) => d
if d == 1:
return sign + str(a)
return sign + str(a) + "/" + str(d)
def two_places(num, den):
# num / den to two decimals, halves away from zero
"" => sign
num => a
if num < 0:
"-" => sign
0 - num => a
a * 100 => x
x % den => r
int((x - r) / den) => q
if 2 * r >= den:
q + 1 => q
q % 100 => c
int((q - c) / 100) => w
if c < 10:
return sign + str(w) + ".0" + str(c)
return sign + str(w) + "." + str(c)
def column(rows, k):
[] => out
for r in rows:
out + [r[k]] => out
return out
def sorted_ints(xs):
[] => out
for x in xs:
out + [x] => out
for i in [1:len(out) - 1]:
i => j
while j > 0 and out[j] < out[j - 1]:
out[j] => t
out[j - 1] => out[j]
t => out[j - 1]
j - 1 => j
return out
def median_twice(xs):
# twice the median, so it stays whole
sorted_ints(xs) => s
len(s) => n
if n % 2 == 1:
return 2 * s[int((n - 1) / 2)]
return s[int(n / 2) - 1] + s[int(n / 2)]
def nps(rows):
# [promoters, passives, detractors]
0 => p
0 => m
0 => d
for r in rows:
if r[1] >= 9:
p + 1 => p
elif r[1] >= 7:
m + 1 => m
else:
d + 1 => d
return [p, m, d]
def group(score):
if score >= 9:
return "promoter"
if score >= 7:
return "passive"
return "detractor"
def total(r):
return r[2] + r[3] + r[4]
def ranked(rows):
# by total, highest first; equal totals keep their order (stable)
[] => out
for r in rows:
out + [r] => out
for i in [1:len(out) - 1]:
i => j
while j > 0 and total(out[j]) > total(out[j - 1]):
out[j] => t
out[j - 1] => out[j]
t => out[j - 1]
j - 1 => j
return out
def top(rows, n, policy):
# strict: exactly n, the first n of the ranking; dense: everyone with a
# total at least the n-th; exclude: everyone with a total above the
# n-th's when the n-th is tied with the next one, else the first n
ranked(rows) => r
if n >= len(r):
return r
total(r[n - 1]) => cut
[] => out
if policy == "strict":
for i in [0:n - 1]:
out + [r[i]] => out
return out
if policy == "dense":
for x in r:
if total(x) >= cut:
out + [x] => out
return out
if total(r[n]) != cut:
for i in [0:n - 1]:
out + [r[i]] => out
return out
for x in r:
if total(x) > cut:
out + [x] => out
return out
def reversed_rows(rows):
[] => out
for r in rows:
[r] + out => out
return out
Python projection (survey.py)
def gcd(a, b):
while b != 0:
r = a % b
a = b
b = r
return a
def fraction_text(num, den):
sign = ""
a = num
if num < 0:
sign = "-"
a = 0 - num
g = gcd(a, den)
if g == 0:
g = 1
a = int(a / g)
d = int(den / g)
if d == 1:
return sign + str(a)
return sign + str(a) + "/" + str(d)
def two_places(num, den):
sign = ""
a = num
if num < 0:
sign = "-"
a = 0 - num
x = a * 100
r = x % den
q = int((x - r) / den)
if 2 * r >= den:
q = q + 1
c = q % 100
w = int((q - c) / 100)
if c < 10:
return sign + str(w) + ".0" + str(c)
return sign + str(w) + "." + str(c)
def column(rows, k):
out = []
for r in rows:
out = out + [r[k]]
return out
def sorted_ints(xs):
out = []
for x in xs:
out = out + [x]
for i in range(1, len(out)):
j = i
while j > 0 and out[j] < out[j - 1]:
t = out[j]
out[j] = out[j - 1]
out[j - 1] = t
j = j - 1
return out
def median_twice(xs):
s = sorted_ints(xs)
n = len(s)
if n % 2 == 1:
return 2 * s[int((n - 1) / 2)]
return s[int(n / 2) - 1] + s[int(n / 2)]
def nps(rows):
p = 0
m = 0
d = 0
for r in rows:
if r[1] >= 9:
p = p + 1
elif r[1] >= 7:
m = m + 1
else:
d = d + 1
return [p, m, d]
def group(score):
if score >= 9:
return "promoter"
if score >= 7:
return "passive"
return "detractor"
def total(r):
return r[2] + r[3] + r[4]
def ranked(rows):
out = []
for r in rows:
out = out + [r]
for i in range(1, len(out)):
j = i
while j > 0 and total(out[j]) > total(out[j - 1]):
t = out[j]
out[j] = out[j - 1]
out[j - 1] = t
j = j - 1
return out
def top(rows, n, policy):
r = ranked(rows)
if n >= len(r):
return r
cut = total(r[n - 1])
out = []
if policy == "strict":
for i in range(0, n):
out = out + [r[i]]
return out
if policy == "dense":
for x in r:
if total(x) >= cut:
out = out + [x]
return out
if total(r[n]) != cut:
for i in range(0, n):
out = out + [r[i]]
return out
for x in r:
if total(x) > cut:
out = out + [x]
return out
def reversed_rows(rows):
out = []
for r in rows:
out = [r] + out
return out