2

I have been developing a quiz application that uses a textfile to store questions.

The questions are formatted in this format "QUESTION##CHOICE_A##CHOICE_B##CHOICE_C##CHOICE_D##ANSWER" I want it to read each line splits it into 6 different parts using "##" as the split string and store it into arrays such as Questions, CHOICE_A,CHOICE_B,CHOICE_C,CHOICE_D

My code does not loop. it just stores the first line

A graphical illustration is shown below of the questions

enter image description here enter image description here

Here is my code

Dim sr As StringReader = New StringReader(My.Resources.ResourceManager.GetObject(globalVariables.currSubject))

    Dim questions As String

    Dim splitquestions(6) As String

    Dim Unsplitquestions(6) As String




    Dim i As Integer = 0

    Do Until sr.Peek = -1

        questions = sr.ReadLine

        Unsplitquestions(i) = questions

        splitquestions = Unsplitquestions(i).Split(New String() {"##"}, StringSplitOptions.RemoveEmptyEntries)

        ' Splits and Stores Into Various 
        '
        '


        globalVariables.ArrayQuestions.Add(splitquestions(0))

        globalVariables.optionA.Add(splitquestions(1))

        globalVariables.optionB.Add(splitquestions(2))

        globalVariables.optionC.Add(splitquestions(3))

        globalVariables.optionD.Add(splitquestions(4))

        globalVariables.Answer.Add(splitquestions(5))

    Loop
5
  • Why still use an ArrayList? There are better options now. A List(Of Question) for example Commented Sep 23, 2014 at 13:41
  • To answer your specific question with a more obvious statement, the sr.peek =-1 after your first run. you must be reaching the end or stop of string. I'd try a different loop method if I were you. Commented Sep 23, 2014 at 13:42
  • you would be sooooo much better off creating a Quiz class for that rather than storing related data in several arrays Commented Sep 23, 2014 at 13:43
  • sparkysword what loop do you suggest. Commented Sep 23, 2014 at 16:19
  • Plutonix can you give me more light into what you mean Commented Sep 23, 2014 at 16:20

2 Answers 2

3

No, do not use an ArrayList for manipulating a set of objects like this. You should try to think in an Object Oriented way. A QuestionEntry is an entity that contains a QuestionText, 4 possible question answers and one QuestionAnswer.

So let's try this code

Public Class QuestionEntry
   Public QuestionText as String
   Public ChoiceA as String
   Public ChoiceB as String
   Public ChoiceC as String
   Public ChoiceD as String
   Public QuestionAnswer as String
End Class

Dim questions = new List(Of QuestionEntry)()
Dim line As String
Do While sr.Peek() >= 0
    line = sr.ReadLine
    Console.WriteLine(line)
    Dim parts = line.Split(New String() {"##"}, StringSplitOptions.RemoveEmptyEntries)
    Dim q = new QuestionEntry()
    With q

            .QuestionText = parts(0)
            .ChoiceA = parts(1)
            .ChoiceB = parts(2)
            .ChoiceC = parts(3)
            .ChoiceD = parts(4)
            .QuestionAnswer = parts(5)

    End With
    questions.Add(q)
Loop

Of course this is just an example and a bit of error checking will be required to make the code more safe. For example before creating the new question entry you should check if the array returned by the split has effectively 6 elements. (parts.Length = 6)

Now all of your text is handled by an instance of a List(Of QuestionEntry) and you could use it like a normal array

Dim qe = questions(0)
Console.WriteLine("Question: " & qe.QuestionText)
Console.WriteLine("Choice A: " & qe.ChoiceA)
Console.WriteLine("Choice B: " & qe.ChoiceB)
Console.WriteLine("Choice C: " & qe.ChoiceC)
Console.WriteLine("Choice D: " & qe.ChoiceD)
Console.ReadLine("Enter your answer:")
Sign up to request clarification or add additional context in comments.

Comments

1

The best way to do this is to rely on an existing delimited data parser. The .Split() method is very often horrible for this: performance is sub-par, and there are all kings of edge cases (more than you'd think) where it just doesn't work well. There is even a parser already built into .Net: Microsoft.VisualBasic.FileIO.TextFieldParser.

Additionally, ArrayLists really only exist for pre-.Net 2.0 compatibility. There's is no good reason to ever use one any more. At very least, use a generic List(Of String). In this case, though, your best option is to build a quick class:

Public Class Question
    Public Property QuestionText As String
    Public Property OptionA As String
    Public Property OptionB As String
    Public Property OptionC As String
    Public Property OptionD As String
    Public Property Answer As String
End Class

Now you read your file like this:

Dim results As New List(Of Question)()
Using rdr As New TextFieldParser(My.Resources.ResourceManager.GetObject(globalVariables.currSubject))
    rdr.Delimiters = new String() {"##"}
    Dim row() As String

    While Not rdr.EndOfData
        row = rdr.ReadFields()
        results.Add(New Question() {
             QuestionText = row(0), 
             OptionA = row(1), 
             OptionB = row(2), 
             OptionC = row(3),
             OptionD = row(4), 
             Answer = row(5)
         })
    End While
End Using

Even with the class definition, that's a whole let less code than the original, and it's much easier to maintain, as well.

I'd also be tempted to write this as an Iterator:

Public Iterator Function ReadQuestions(ByVal FileName As String) As IEnumerable(Of Question)
    Using rdr As New TextFieldParser(FileName)
        rdr.Delimiters = new String() {"##"}
        Dim row() As String

        While Not rdr.EndOfData
            row = rdr.ReadFields()

            Yield New Question() {
                QuestionText = row(0), 
                OptionA = row(1), 
                OptionB = row(2), 
                OptionC = row(3),
                OptionD = row(4), 
                Answer = row(5)
            }
        End While
    End Using
End Function

I have two final changes to suggest. The first to add a constructor to the Question type that accepts a string array. This would remove one bit of advanced syntax (the object initializer) from the code, and simplify reading through the portion of the code that actually reads the data. The second isto make the ReadQuestions() method a shared member of the Question type. The final result is this:

Public Class Question
    Public Property QuestionText As String
    Public Property OptionA As String
    Public Property OptionB As String
    Public Property OptionC As String
    Public Property OptionD As String
    Public Property Answer As String

    Public Sub New(ByVal data() As String)
        'Add error handling here
        QuestionText = data(0), 
        OptionA = data(1), 
        OptionB = data(2), 
        OptionC = data(3),
        OptionD = data(4), 
        Answer = data(5)
    End Sub

    Public Shared Iterator Function ReadFromFile(ByVal FileName As String) As IEnumerable(Of Question)
        Using rdr As New TextFieldParser(FileName)
            rdr.Delimiters = new String() {"##"}

            While Not rdr.EndOfData                                
                Yield New Question(rdr.ReadFields()) 
            End While
        End Using
    End Function
End Class

And you call all this from your existing code like so:

Dim Questions = Question.ReadFromFile(My.Resources.ResourceManager.GetObject(globalVariables.currSubject))
For Each q As Question in Questions
    '...
Next 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.