0

Here is what my code looks like now.

I'm trying to make a multi choice question program

It's quite rudimentary as a way of proceeding but I find it difficult to structure the code in a more "professional" way.

fun readlist(filename) =
    let 
        val file = TextIO.openIn filename
        val text = TextIO.inputAll file
        val _ = TextIO.closeIn file
    in
        String.tokens (fn c => c = #"\n") text
    end;
  
(*Get user answer from keybord*)
fun getAnswer() = (
    print "\nPlease enter your answer. Options(case sensitive): \nA-) \tB-) \tC-) \tD-)\n";
    let
        val return = valOf (TextIO.inputLine TextIO.stdIn)
    in
        return
    end
);
    
(*Verify if the choosen line is a new starting question block base on fact that each question block contains 7 line*)
fun getQuestionLine(t) = 
    case t of 
    [] => []
    |t::ts => if t mod 7 = 0 then
        t::getQuestionLine(ts)
     else
        getQuestionLine(ts);

fun toIntList(x:int) = x;
val getblockStarts = getQuestionLine(List.tabulate(List.length(readlist("questionBank.txt")), toIntList));
fun member (x, []) = false
    | member (x, y::yt) = x = y orelse member (x, yt);

fun seed () =
    let
        val aReal = Time.toReal(Time.now()) - 1.0e9
        val floorReal = Real.realFloor(aReal)
        val dec = aReal - floorReal
        val sd1 = Real.floor(dec);
        val sd2 = Real.floor(1000.0 * dec);
    in
        Random.rand(sd1,sd2)
    end;

fun randList (N) =
    let
      fun subfun (N, i) intList =
          if N = 0 then intList
          else
              let
                  val tmp = Random.randRange(1, (List.length(readlist("questionBank.txt"))))
                  val randVal = tmp i
              in
                  if member(randVal, getblockStarts) then
                      subfun (N - 1, i) (randVal :: intList)
                  else
                      subfun (N, i) intList
              end;
    in
        subfun (N, seed()) []
    end;

fun ask(questionIndexList) =
    let
      fun loop (n) =
          let
              val question = print(
                  "\n"^
                  List.nth(readlist("questionBank.txt"), List.nth(questionIndexList, n-1))^"\n\t"^
                  List.nth(readlist("questionBank.txt"), List.nth(questionIndexList, n-1)+1)^"\n\t"^
                  List.nth(readlist("questionBank.txt"), List.nth(questionIndexList, n-1)+2)^"\n\t"^
                  List.nth(readlist("questionBank.txt"), List.nth(questionIndexList, n-1)+3)^"\n\t"^
                  List.nth(readlist("questionBank.txt"), List.nth(questionIndexList, n-1)+4)^"\n\t"^
                  List.nth(readlist("questionBank.txt"), List.nth(questionIndexList, n-1)+5)^"\n"
              )
              val userAns = getAnswer()
          in
              if userAns = List.nth(readlist("questionBank.txt"), List.nth(questionIndexList, n-1)+6)^"\n" then
                (
                  print("Your answer is CORRECT.\n");
                  loop (List.length(List.take(questionIndexList, List.length(questionIndexList)-1)))
                )
              else (
                  print(
                      "Your answer is INCORRECT.\nThe correct answer is: "^
                      List.nth(readlist("questionBank.txt"), List.nth(questionIndexList, n-1)+6)
                      ^"\n\n"
                  );
                  loop (List.length(List.take(questionIndexList, List.length(questionIndexList)-1)))
                )
          end
    in
        loop (List.length(questionIndexList))
    end;

ask(randList(3));

I added a capture from my question file. enter image description here

All questions follow this format

My code works the way I want it to, but the last lap in the loop is endless. The console offers me three questions that I answer and I have the right answer if I didn't find it, but the third question is asked again and again without end.

I've been looking all over the place and I don't know where my mistake lies. I would need help to get out of this loop.

2 Answers2

2

There is no condition to take you out of loop; you recurse unconditionally in both conditional branches.

You could fix that by checking for zero, if it weren't for the fact that

List.length(List.take(questionIndexList, List.length(questionIndexList)-1))

is always the same number.
(Which is List.length(questionIndexList)-1 – if you take K elements from a list, the result has length K. There is no need to create the list just to determine its length.)

But I would structure this differently.

You are reading the file every time you need the questions.
It is better to read the file once and pass the list around.

First, a type to represent the question and answers:

type Options = string * string * string * string;
datatype QA = QA of string * Options * string;

And a couple of functions for gathering those from the unwieldy list of strings:

fun toQAs [] = []
  | toQAs (q::o1::o2::o3::o4::a::qas) = (QA (q, (o1,o2,o3,o4), a)) :: (toQAs qas);

fun readQuestions file = toQAs (readlist file);

And a utility function for printing a question:

fun printQuestion (QA (q, (o1,o2,o3,o4), _)) = print ("\n"^ q^"\n\t"^ o1^"\n\t"^ o2^"\n\t"^ o3^"\n\t"^ o4^"\n");

The ask function just takes a list of questions and asks all of them:

fun ask [] = print "No further questions.\n"
  | ask  (q::qs) =
    let val (QA (_, _, answer)) = q
    in
        printQuestion q;
        if getAnswer() = answer^"\n" then
             print "Your answer is CORRECT.\n"
         else
             print ("Your answer is INCORRECT.\nThe correct answer is: "^ answer ^"\n\n");
        ask qs
    end;

And finally, a function that creates a random list of questions based on the file, and asks them:

fun go () =
    let
        val QAs = readQuestions "questionBank.txt"
    in
        ask (map (fn i => List.nth(QAs, i)) (randList (length QAs) 3))
    end;
molbdnilo
  • 64,751
  • 3
  • 43
  • 82
  • @PeterSmith I changed my code to be able to use your suggestions But I have this error at the end: test1.sml:88.46-88.69 Error: operator is not a function [tycon mismatch] operator: int list in expression: (randList (length QAs)) 3 – IndifinedGenome Dec 08 '20 at 23:04
  • @PeterSmith I would also like to know how I could proceed if I wanted to calculate the percentages of correct answers by category based on the assumption that each question belongs to a category? I used to do it one way but with the datatype I think it's going to be a bit harder to handle – IndifinedGenome Dec 08 '20 at 23:11
  • @IndifineGenome Who is "Peter Smith"? First, you made a parentheses mistake. Use copy and paste (this was copied and pasted from tested code). Second, it's very easy to add a "category" to the question type. Then you can, for instance, return the correctly and incorrectly answered questions from `ask` and calculate statistics afterwards. – molbdnilo Dec 09 '20 at 07:17
  • Sorry that was a mistake – IndifinedGenome Dec 11 '20 at 13:47
0

I changed my ask function to use the number of questions to ask rather than the list of blocks of questions to display.

fun ask(n) =
    let
      fun loop (n) =
          let
              val newQues = randList(1)
              val question = print(
                  "\n"^
                  List.nth(readlist("questionBank.txt"), hd newQues)^"\n\t"^
                  List.nth(readlist("questionBank.txt"), (hd newQues)+1)^"\n\t"^
                  List.nth(readlist("questionBank.txt"), (hd newQues)+2)^"\n\t"^
                  List.nth(readlist("questionBank.txt"), (hd newQues)+3)^"\n\t"^
                  List.nth(readlist("questionBank.txt"), (hd newQues)+4)^"\n\t"^
                  List.nth(readlist("questionBank.txt"), (hd newQues)+5)^"\n"
              )
              val userAns = getAnswer()
          in
              if userAns = List.nth(readlist("questionBank.txt"), (hd newQues)+6)^"\n" then
                (
                  print("Your answer is CORRECT.\n");
                  if n > 1 then loop (n-1) else ()
                )
              else (
                  print(
                      "Your answer is INCORRECT.\nThe correct answer is: "^
                      List.nth(readlist("questionBank.txt"), (hd newQues)+6)
                      ^"\n\n"
                  );
                  if n > 1 then loop (n-1) else ()
                )
          end
    in
        loop (n)
    end;

ask(5);

So it's working as i want now.

If sommeone have optimizing ideas, i'll be happy to read them.