Arithmetic Drill
December 31, 2010
Children just learning the basic facts of arithmetic need repetitious drill to certify their knowledge, something that a computer does well. Toy stores purvey many brightly-colored plastic boxes that do the job, at a price. Today’s exercise asks you to do the same, minus the plastic box. Consider the following dialog, where the computer’s output is in roman and the child’s response is in italic:
4 + 4 = 8
Right!
8 + 3 = 12
Wrong, try again!
8 + 3 = 11
Right!
9 + 4 = 13
Right!
7 + 8 = ?
15
9 + 5 = 14
Right!
8 + 0 = ^Z
Goodbye!
After the drill program presents a problem, the child either enters his answer, asks for help by entering a question mark, or quits by entering an end-of-file. Correct answers are rewarded, incorrect answers cause the problem to be asked again.
Your task is to write a program that drills children on their addition facts. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.
[…] Praxis – Arithmetic Drill By Remco Niemeijer In today’s Programming Praxis exercise, our goal is to write a program that allows children to test their […]
My Haskell solution (see http://bonsaicode.wordpress.com/2010/12/31/programming-praxis-arithmetic-drill/ for a version with comments):
import Control.Monad import System.Random import Text.Printf drill :: Maybe Int -> IO () drill n = play n =<< liftM2 (,) rnd rnd where rnd = randomRIO (1, maybe 10 id n) play :: Maybe Int -> (Int, Int) -> IO () play n (a,b) = printf "%d + %d = " a b >> catch getLine (\_ -> return "quit") >>= \s -> case s of "quit" -> putStrLn "Goodbye!" "?" -> print (a + b) >> drill n x -> if x == show (a + b) then putStrLn "Right!" >> drill n else putStrLn "Wrong, try again!" >> play n (a,b) main :: IO () main = drill NothingMy Python 3 solution:
#/usr/bin/env python3 """Usage: ./drill [op_count [max_addend]] """ import sys from random import randint def drill(max_addend, op_count): ops = [randint(0, max_addend) for i in range(op_count)] question = " + ".join(str(op) for op in ops) + " = " try: response = input(question) while response != str(sum(ops)): if response == "?": print(question + str(sum(ops))) return True print("Wrong, try again!") response = input(question) except EOFError: print("\nGoodbye!") return False print("Right!") return True def main(args): try: op_count = int(args[1]) try: max_addend = int(args[2]) except (IndexError, ValueError): max_addend = 10 except (IndexError, ValueError): op_count = 2 max_addend = 10 while drill(max_addend, op_count): pass return 0 if __name__ == "__main__": sys.exit(main(sys.argv))Here’s a simple ruby version:
require 'readline' while true a = rand(10) b = rand(10) prompt = "#{a} + #{b} = " while line = Readline.readline(prompt, true) answer = line.strip if answer == "?" puts "#{a+b}" break elsif line.strip == "#{a+b}" puts "Right!" break else puts "Wrong, try again!" end end if line == nil puts "\nGoodbye!" break end endMy Python solution:
#!/usr/bin/env python import random def drill(n): x, y = random.randrange(n), random.randrange(n) answer = int(raw_input("{0} + {1} =\t".format(x, y))) while x + y != answer: print "Wrong, try again!" answer = int(raw_input("{0} + {1} =\t".format(x, y))) print "Right!" return if __name__ == "__main__": import sys n = 10 if len(sys.argv) == 1 else int(sys.argv[1]) try: while True: drill(n) except KeyboardInterrupt: print "\nGoodbye!"Sorry, only handles keyboard interrupts. This one also handles EOF:
#!/usr/bin/env python import random def drill(n): x, y = random.randrange(n), random.randrange(n) answer = int(raw_input("{0} + {1} =\t".format(x, y))) while x + y != answer: print "Wrong, try again!" answer = int(raw_input("{0} + {1} =\t".format(x, y))) print "Right!" return if __name__ == "__main__": import sys n = 10 if len(sys.argv) == 1 else int(sys.argv[1]) try: while True: drill(n) except (KeyboardInterrupt, EOFError): print "\nGoodbye!"In C:
#include <stdio.h> #include <stdlib.h> #define UPPER_LIMIT 10 #define RANDOM_NUMBER ((int)(random() % UPPER_LIMIT)) #define MSG_RIGHT "Right!\n" #define MSG_WRONG "Wrong, try again!\n" #define MSG_BYE "\nGoodbye!\n" int main() { int guess, a, b, guessing; char buffer[11]; while (a = RANDOM_NUMBER, b = RANDOM_NUMBER, guessing = 1) { if ((a == 0) && (b == 0)) break; while (guessing) { if (feof(stdin)) { printf(MSG_BYE); return 0; } printf("%d + %d = ", a, b); if (fgets(buffer, 10, stdin) == NULL) break; guess = (int)strtol(buffer, NULL, 0); printf((guessing = guess != a + b) ? MSG_WRONG : MSG_RIGHT); } } };; Hum, my comment does not show up (even with refreshes), but the system says I already posted it!
(define (drill)
(call/cc
(lambda(exit)
(let loop ()
(and (call/cc
(lambda (break)
(let ((a (random-integer 10))
(b (random-integer 10)))
(if (or (zero? a) (zero? b))
(break #f))
(map display `(,a ” + ” ,b ” = “))
(let ((v (read)))
(cond
((eof-object? v) (exit ‘bye))
((not (number? v)) (break #f))
((= v (+ a b)) (display ‘yeah!))
(else (display ‘booh)))))))
(newline))
(loop)))))
My ruby version:
Not sure what is meant by handling an end-of-file in the terminal. Just learning Ruby, so probably not the best style.
def quiz (num1=rand(10), num2=rand(10))
while true
print “#{ num1 } + #{ num2 } = ”
answer = STDIN.gets.strip
if answer == “?”
puts “#{ num1 + num2 }”
quiz
elsif answer.to_i == num1 + num2
puts “Right!”
quiz
else
puts “Wrong, try again”
end
end
end
quiz
@Axio: does all that call/cc really bring some benefits or is it just to show some different way it could be done?
Roti: it’s just the way to code it that seemed clear and natural to me. One might argue that call/cc is quite overkill here, as I just use it instead of exceptions to simulate “break” (as in a “while loop”). But it’s just an exit continuation, so this is a quite common pattern that keeps code understandable.
So, whether it is a benefit or not will probably be subjective here.
PHP version
function quiz(){ $nums = array(mt_rand(0,10),mt_rand(0,10)); $answer = FALSE; $correct = array_sum($nums); while($answer !== $correct) { echo "{$nums[0]} + {$nums[1]} = \n"; $handle = fopen ("php://stdin","r"); $answer = (int) trim(fgets($handle)); if ($answer !== $correct) { echo "Wrong, try again!\n"; } } echo "Right!\n"; quiz(); } quiz();golf score: 305
(require(lib “1.ss” “srfi”))(let p()(let l((n(random 10))(m(random 10)))(printf “~a + ~a = ” n m)(flush-output)(let*((s(read))(z(reduce(lambda(x a)(if(eq?(car x)s)x a))0`((0 “Wrong, try again!”,l,n,m)(,(+ n m) “Right!”,p)(?,(+ n m),p)(^Z “Goodbye!”,void)))))(displayln(cadr z))(apply(caddr z)(cdddr z)))))
C Version:
#include
#include
#include
int createQuestion(int questionType); /*question type decides if the question will be + or -. It returns 0 if answered correct or 1 if wrong.*/
main()
{
int a = 0;
int result = 0;
printf(“\nAnswer the questions. If you get 5 questions wrong the game ends. Try to answer as many as possible.\n”);
printf(“Press 1 for addition problems or 2 for subtraction problems, or 3 for both: “);
scanf(“%d”,&a);
while(result<5){
switch (a)
{
case 1:
result = result +createQuestion(1);
printf("\nresult:%d\n",result);
break;
case 2:
result = result +createQuestion(2);
break;
case 3:
result = result +createQuestion(3);
break;
}//switch
}//while
}//main
int createQuestion(int questionType) {
int int1, int2,input, answer;
srand ( time(NULL) );
int1 = rand() % 256 + 1;
int2 = rand() %256+1;
switch (questionType)
{
case 1:
printf("\n%d+%d= ",int1,int2);
answer = int1+int2;
break;
case 2:
printf("\n%d-%d= ",int1,int2);
answer = int1-int2;
break;
case 3:
if(int1<int2){
printf("\n%d+%d= ",int1,int2);
answer = int1+int2;
}
else{
printf("\n%d-%d= ",int1,int2);
answer = int1 – int2;
}
break;
}
scanf("%d",&input);
printf("\n%d\n",answer);
if(input==answer)
return 0;
else
return 1;
}
I got rid the reduce by using racket’s built in assq function, eliminating the need for srfi/1.
New golf score for my racket scheme version: 238
(let p()(let l((n(random 10))(m(random 10)))(printf”~a + ~a = “n m)(flush-output)(let*((s(read))(z(assq s `((,(+ n m)”Right!”,p)(?,(+ n m),p)(^Z”Goodbye!”,void)(,s”Wrong, try again!”,(lambda()(l n m)))))))(displayln(cadr z))((caddr z)))))
Possible solution REXX
p = 1 op = '+' do forever x = random(copies('9',p)) y = random(copies('9',p)) calc = x op y interpret 'erg='calc ans = '*' do until ans == erg | ans == '?' say calc'=' parse pull ans select when ans == '?' then say erg when \ datatype(ans,'N') then exit when ans == erg then say 'right!' otherwise say 'wrong, try again!' end end end exit