I am unsure about how to remove cases from the "select case" statement in visual basic after a certain case has been selected more than 3 times - vb.net

I've a word guessing game in development. It works so that when the play button is clicked, a random number between 1 and 20 will be generated. This new randomly generated number will then go into the select case statement as shown in the code below:
Dim RND As New Random
Dim rndNumber As String
rndNumber = RND.Next(1, 20)
Dim RndWord as string
Dim RndHint as string
select case(rndnumber)
case 1
RndWord = "hockey"
RndHint = "A ball game played with curved, wooden sticks"
Case 2
RndWord = "dinghy"
RndHint = "This is a small boat usually made out of rubber"
These are just 2 out of 20 similar cases.
The selected case contains a word and a hint that will be displayed upon that case being selected. My problem is, how do I remove a case after it has been selected three times; removing the word and the hint from the program completely so that they won't appear again. I've looked into different types of arrays; however, after 2 hours of research and many attempts at using them they don't seem to fit this purpose.

Firstly remove your case statement, it makes is less manageable. What if tomorrow you decide to add 5 more questions? You will end up changing the code. You can keep all the data externally and have them read into a dictionary object.
Define these at class level:
Dim questions As Dictionary(Of Integer, KeyValuePair(Of String, String)) = New Dictionary(Of Integer, KeyValuePair(Of String, String))()
Dim questionAppearedCount As Dictionary(Of Integer, Integer) = New Dictionary(Of Integer, Integer)()
Create new Sub with this:
Public Sub FillQuestions()
questions.Add(1, New KeyValuePair(Of String, String)("hockey", "A ball game played with curved, wooden sticks"))
questions.Add(2, New KeyValuePair(Of String, String)("dinghy", "This is a small boat usually made out of rubber"))
'Also, you may use File.ReadAllLines() to fill from a file.
End Sub
Finally, BtnGenerate_Click
Dim rnd As Random = New Random()
Dim rndNumber As Integer = rnd.[Next](1, 20)
If questionAppearedCount.ContainsKey(rndNumber) Then
questionAppearedCount(rndNumber) = questionAppearedCount(rndNumber) + 1
Else
questionAppearedCount.Add(rndNumber, 1)
End If
If questionAppearedCount(rndNumber) > 3 Then
'do not show question, instead get Next random
Else
return question(rndNumber)
End If
Here is the code, so I keep questions in dictionary, show them using dictionary.
In the above code, question can be read from external file using File.ReadAllLines()
When question is shown, add to the count and if count is > 3, don't show.

Make an array to count each case.
If the count is 3 you loop.
If less you increase the counter in the array and exit the loop so it ll use a case statement.
Be carreful in this case if all counters reach 3 the program wil loop infinitively.
Else an array for all the cases data.
When 3 is reached the datas are moved at the end of the array and the max index is decreased by one. At beginning its 20... next 19.. etc...

I recommend this code.
My sample also has a list of questions.
A very important differences though:
It does the filtering of the data BEFORE the random call. So, it's only searching for the questions that are valid. In certain cases, repeated random calls very close together might produce the same result, so this verifies you only do it once.
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Linq
Public Module Module1
Dim questionAppearedCount As Dictionary(Of Integer, Integer)
Public Sub Main()
Dim questions As Dictionary(Of Integer, KeyValuePair(Of String, String)) = New Dictionary(Of Integer, KeyValuePair(Of String, String))()
questions.Add(1, New KeyValuePair(Of String, String)("hockey", "A ball game played with curved, wooden sticks"))
questions.Add(2, New KeyValuePair(Of String, String)("dinghy", "This is a small boat usually made out of rubber"))
questionAppearedCount = New Dictionary(Of Integer, Integer)()
SelectQuestion(1)
SelectQuestion(1)
SelectQuestion(1)
' Get the Filtered List
Dim filteredQuestions() as KeyValuePair(Of Integer, KeyValuePair(Of String, String)) = questions.Where(Function(ByVal item as KeyValuePair(Of Integer, KeyValuePair(Of String, String))) Not questionAppearedCount.ContainsKey(item.Key) OrElse questionAppearedCount(item.Key) < 3).ToArray()
Dim rand As New Random
Console.WriteLine(filteredQuestions(rand.Next(0, filteredQuestions.Count - 1)).Value)
End Sub
Public Sub SelectQuestion(ByVal questionNumber as Integer)
' Add Question
If questionAppearedCount.ContainsKey(questionNumber) Then
questionAppearedCount(questionNumber) += 1
Else
questionAppearedCount(questionNumber) = 1
End If
End Sub
End Module
And here's some vb.net helper code that you can use to REFACTOR your case statements automatically. Just paste your code into it and it will return the equivalent code to the console. You'll replace your code with this code.
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Linq
Public Module Module1
Dim questionAppearedCount As Dictionary(Of Integer, Integer)
Public Sub Main()
'Dim questions as New Dictionary(Of Integer, KeyValuePair(Of String, String))
For rndnumber as Integer = 1 to 20
Dim RndWord as String = ""
Dim RndHint as String = ""
select case(rndnumber)
case 1
RndWord = "hockey"
RndHint = "A ball game played with curved, wooden sticks"
Case 2
RndWord = "dinghy"
RndHint = "This is a small boat usually made out of rubber"
end SElect
If Not String.IsNullOrEmpty(rndword) Then
'questions.Add(rndnumber, new KeyValuePair(Of String, String)(rndword, rndhint))
Console.WriteLine("questions.Add(" & rndnumber & ", new KeyValuePair(Of String, String)(""" & rndword.Replace("""", """""") & """,""" & RndHint.Replace("""", """""") & """))")
End If
Next
End Sub
End Module

Related

New to programming language, need help to read a .dat file into array Vs VB, console .net framework

I am trying to read from a data file called TXT.dat and store the circled values into a separate array's using the type of commands I have used. I cannot use streamreader,streamwriter this is what I am learning at university but we were taught to read an array not certain values as I will be tested using a file which has
2 factorial values from a single file and store int two arrays.
I have spent hours trying before going to paid sites who always answer with i/o streamreader which doesnt help
.dat file i created to practice
MY CODE
Sub Main()
Const i_val As Integer = 6
Dim j As Integer = 6 'loop readers
Dim Arayn_Fact(i_val - 1) As Double 'array for 2nd value per line
Dim Aray_Fact2n(j - 1) As Double 'array for 4th value per line
Read_Values(i_val - 1, Arayn_Fact)
End Sub
Sub Read_Values(ByVal i As Integer, ByRef _A() As Double)
Dim fid1 As Integer = FreeFile()
Dim fid2 As Integer = FreeFile() + 1
Dim tmp As Double
FileOpen(fid1, "TXT.dat", OpenMode.Input, OpenAccess.Read)
For i = 0 To 5 Step 1
Input(fid1, tmp)
Input(fid1, _A(i))
Next i
FileClose(fid1)
Console.ReadKey()
End Sub
Without getting too fancy, here's some very basic code to do what you've outlined:
Sub Main()
Dim fileName As String = "txt.dat"
Dim Aray_Fact2() As Integer 'array for 2nd value per line
Dim Aray_Fact4() As Integer 'array for 4th value per line
Dim list2 As New List(Of Integer)
Dim list4 As New List(Of Integer)
Dim lines() As String = System.IO.File.ReadAllLines(fileName)
For Each line As String In lines
Dim values() As String = line.Split(",")
If values.Length >= 3 Then
Dim f2, f4 As Integer
If Integer.TryParse(values(1), f2) AndAlso Integer.TryParse(values(3), f4) Then
list2.Add(f2)
list4.Add(f4)
Else
Console.WriteLine("Invalid value in line: " & line)
End If
Else
Console.WriteLine("Invalid number of entries in line: " & line)
End If
Next
Aray_Fact2 = list2.ToArray
Aray_Fact4 = list4.ToArray
Console.WriteLine("Aray_Fact2: " & String.Join(",", Aray_Fact2))
Console.WriteLine("Aray_Fact4: " & String.Join(",", Aray_Fact4))
Console.Write("Press Enter to Quit...")
Console.ReadLine()
End Sub
If you are completely against the use of Lists and ToArray, then you'd have to read the file TWICE. Once to determine how many lines are in the file so you can dimension the arrays to the correct size. Then another time to actually read the values and populate the arrays.
First, some style things. Passing the array ByRef is incorrect. Arrays are already reference types, and so passing ByVal still passes the reference to the array object. But don't even pass the array in the first place. It's better practice to let the Read_Values() method create and return the array to the calling function.
And no one uses that ancient/ganky FileOpen() API anymore. I mean that. NO. ONE. It's not even appropriate for teaching, as the Stream types are closer to what the low level operating system/file system are doing. If you can't use StreamReader and friends, there are still better options out there.
Sub Main()
Dim Array_Fact() As Double = Read_Values("TXT.dat", 1)
Dim Array_Fact2n() As Double = Read_Values("TXT.dat", 3)
Console.ReadKey(True)
End Sub
Function Read_Values(filePath As String, position As Integer) As Double()
Dim lines() As String = File.ReadAllLines(filePath) 'Look Ma, no StreamReader
Dim result(lines.Length - 1) As Double
For i As Integer = 0 To Lines.Length - 1
result(i) = Double.Parse(lines(i).Split(",")(position).Trim())
Next line
Return result
End Function
But what I'd really do is keep the 2nd and 4th values in the same collection and read everything in one pass through the file. This uses features like Generics, Tuples, Lambdas+Linq, and more that you won't get to for some time, but I feel like it's useful to show you where you're going:
Sub Main()
Dim Facts = Read_Values("TXT.dat")
Console.WriteLine(String.Join(",",Facts.Select(Function(f) $"({f.Item1}:{f.Item2})")))
Console.ReadKey(True)
End Sub
Function Read_Values(filePath As String) As IEnumerable(Of (Double, Double))
Return File.ReadLines(filePath).Select(
Function(line) 'Poor man's CSV. In real code, I'd pull in a CSV parser from NuGet
Dim fields = line.Split(",")
Return (Double.Parse(fields(1).Trim()), Double.Parse(fields(3).Trim()))
End Function
)
End Function

Arraylist.Contains Doesn't Return True VB.NET

comps.Contains doesn't return TRUE even though comps contains it.
I debugged it step by step and I can't see where the problem is.
By the way the purpose of the code is to show the pairs that sum up to SUM value. (If the sum is 5 and theres 1 and 4 then the code should return 1 and 4)
Public Function getPairs(ByVal array As ArrayList, ByVal sum As Integer)
Dim comps, pairs As New ArrayList
For index = 0 To array.Count - 1
If (comps.Contains(array(index)) = True) Then
pairs.Add(array(index))
Else
comps.Add(sum - array(index))
End If
Next
Return pairs
End Function
Sub Main()
' 1,3,2,5,46,6,7,4
' k = 5
'Dim arraylist As New ArrayList()
Console.Write("Enter your array :")
Dim arraylist As New ArrayList
arraylist.AddRange(Console.ReadLine().Split(","))
Console.Write("Enter the sum:")
Dim sum As Integer = Console.ReadLine()
getPairs(arraylist, sum)
Console.ReadKey()
End Sub
The ArrayList you populate from user input contains strings (results from splitting the user input string). The comps ArrayList contains integers (results from subtraction). When it tries to find the string "2" in the ArrayList that contains a 2, it fails.
You should convert your user input to integers so that you are comparing the same data types.
First, turn on Option Strict. Tools Menu -> Options -> Projects and Solutions -> VB Defaults. This will point out problems with your code and help you to avoid runtime errors.
ArrayList is not used much in new code but is around for backward compatibility. List(Of T) is a better choice for new code.
Module Module1
Sub Main()
' 1,3,2,5,46,6,7,4
' k = 5
'Dim arraylist As New ArrayList()
Console.Write("Enter your array :")
Dim arraylist As New ArrayList
'Option Strict would not allow this line to compile
'.Split takes a Char, the same c tells the compiler that "," is a Char
arraylist.AddRange(Console.ReadLine().Split(","c))
Console.Write("Enter the sum:")
'Option Strict would not allow a string to be dumped into an integer
Dim sum As Integer
Dim Pairs As New ArrayList
If Integer.TryParse(Console.ReadLine, sum) Then
'Your Function returns an array list but you
'throw it away by not setting up a variable to receive it
Pairs = getPairs(arraylist, sum)
Else
Console.WriteLine("Program aborted. Sum was not a number.")
End If
For Each item In Pairs
Console.WriteLine(item)
Next
Console.ReadKey()
End Sub
'Functions need a return data type in the declaration
Public Function getPairs(ByVal array As ArrayList, ByVal sum As Integer) As ArrayList
Dim comps, pairs As New ArrayList
For index = 0 To array.Count - 1
'I don't see how this can ever be true since comps is empty
If comps.Contains(array(index)) Then 'Since .Contains returns a Boolean, no = True is necessary
pairs.Add(array(index))
Else
'Ideally each item in array should be tested to see if it is a number
'You will get an exception if CInt fails
comps.Add(sum - CInt(array(index)))
'You never use the comps ArrayList
End If
Next
'The pairs ArrayList is empty
Return pairs
End Function
End Module
I don't see how this code could accomplish what you describe as your goal. I think you should start again. Talk through how you would accomplish your task. Then write it out on paper, not in code. Then you will see more clearly how to code your project.
The big problem is the original code is this line:
Dim comps, pairs As New ArrayList
That code creates two ArrayList reference variables, but only one ArrayList object. comps remains null/Nothing.
But beyond that, the ArrayList type has been dead since .Net 2.0 came out back in 2005... more than 10 years now. It only exists today for backwards compatibility with old code. Don't use it!
This is better practice, especially in conjunction with Option Strict and Option Infer:
Public Function getPairs(ByVal items As IEnumerable(Of Integer), ByVal sum As Integer) As IEnumerable(Of Integer)
Dim comps As New HashSet(Of Integer)()
Dim result As New List(Of Integer)()
For Each item As Integer In items
If Not comps.Add(item) Then
result.Add(sum - item)
End If
Next
Return result
End Function
Sub Main()
Console.Write("Enter your array: ")
Dim input As String = Console.ReadLine()
Dim list As List(Of Integer) = input.Split(",").Select(Function(item) CInt(item)).ToList()
Console.Write("Enter the sum: ")
Dim sum As Integer = CInt(Console.ReadLine())
Dim pairs = getPairs(list, sum).Select(Function(s) s.ToString())
Console.WriteLine("Pairs are: {0}", String.Join(", " pairs))
Console.ReadKey()
End Sub

Contents of List(Of String) are not saved

I am attempting to parse paragraphs such as the following...
Group 1. Does this or does that. Or Sometimes this. Or that.
Group 2. I do lots of things. But not this. Or that.
Group 3. I do this. I do that. Sometimes this. Sometimes that.
The "Group 1-3" are the org Names, and each following sentence separated by a period is a function.
Code:
Public Sub parseParagraphs(paragraphList As List(Of String))
Dim listOfOrgs As New List(Of EAB_Org)
Dim listOfFuntions As New List(Of String)
Dim orgName As String
For Each item In paragraphList
listOfFuntions.Clear()
Dim words As String() = item.Split(New Char() {"."c}) 'Splits on periods
orgName = words(0) 'Sets the orgName
For index As Integer = 1 To words.Count - 1 'rest of items in list are functions performed
listOfFuntions.Add(words(index))
Next
Dim anOrg As New EAB_Org(orgName, listOfFuntions)
listOfOrgs.Add(anOrg)
Next
End Sub
EAB Class:
Public Class EAB_Org
Dim orgName As String
Dim listOfTasks As List(Of String)
Public Sub New(theOrgName As String, theListOfTasks As List(Of String))
orgName = theOrgName
listOfTasks = theListOfTasks
End Sub
Public Function getOrgName()
Return orgName
End Function
Public Function getListOfTasks()
Return listOfTasks
End Function
End Class
For some reason, when I print out the contents of listOfOrgs, all the org names are correct, but the functions are all of the same and always the last set of functions read in.
Code I use to print:
Public Sub writeExcel(listOfOrgs As List(Of EAB_Org))
For Each anItem In listOfOrgs
Console.WriteLine(anItem.getOrgName)
For Each anotherItem In anItem.getListOfTasks
Console.WriteLine(anotherItem)
Next
Next
End Sub
Output Looks Like:
Group 1
I do this. I do that. Sometimes this. Sometimes that.
Group 2
I do this. I do that. Sometimes this. Sometimes that.
Group 3
I do this. I do that. Sometimes this. Sometimes that.
The problem is that in the constructor for EAB_Org, theListOfTasks is just a pointer to listOfFuntions (which you keep modifying) in the parseParagraphs Sub. In the constructor, you will need to create a new List(Of String) and copy the values from theListOfTasks into it.
Change the constructor to the following:
Public Sub New(theOrgName As String, theListOfTasks As List(Of String))
orgName = theOrgName
listOfTasks = New List(Of String)
For Each item As String In theListOfTasks
listOfTasks.Add(item)
Next
End Sub

Grouping Values in a dictionary

I have a dictionary that contains a random amount of variables (differs depending on the document it's scanning) in the following format
,A, 500
,ORD, 50000
,ORD, 200
I need to be able to group together all of the values that belong to the same class (ORD being one and A being another) and then add the integers together so that I can output
A - 500
ORD - 50200
So I think I need to assign different integer variables depending on the amount of classes and then add them together but I really don't know how to go about it
EDIT to insert code:
Dim sharenum As Int32 = 0
Dim tempshold As String = ""
For Each sHolder In shareholders
tempshold = Replace(sHolder.numberShares, ",", "")
sharenum = Convert.ToInt32(tempshold)
dicClassShares.Add("," & Trim(sHolder.shareClass) & ",", sharenum)
Change your dictionary structure to a Dictionary(Of String, List(Of Int32))
Then check for the key and either add or edit the dictionary entry depending on the presence of the key:
Dim dicClassShares As Dictionary(Of String, List(Of Int32)) = New Dictionary(Of String, List(Of Int32))
If Not dicClassShares.ContainsKey Then
dicClassShares.Add("," & Trim(sHolder.shareClass) & ",", New List(Of Int32))
End If
dicClassShares("," & Trim(sHolder.shareClass) & ",").Add(sharenum)
And finally obtain the summed value you want...
Dim summedValues As Dictionary(Of String, Int32) =
dicClassShares.ToDictionary(Of String, Int32)(
Function(k) k.Key,
Function(v) v.Value.Sum
)
That last bit is off the top of my head. Play around with it if you get errors.

Writing Entries in a VB Dictionary into a Text File

I'm working on VB in college and am running into a snag with one of my assignments. Can someone help? I'm aiming to try to take the following dictionary code:
Public Class Inventory
Public ItemInventory As New Dictionary(Of String, Item)
Public Function iItem(ByVal key As String) As Item
Return ItemInventory(key)
End Function
Public Sub addItem(ByVal item As String, ByVal Desc As String, ByVal DRate As Double, ByVal WRate As Double, _
ByVal MRate As Double, ByVal Quantity As Integer)
With ItemInventory
.Add(item, New Item(item, Desc, DRate, WRate, MRate, Quantity))
End With
End Sub
Public Sub removeItem(ByVal item As String)
With ItemInventory
.Remove(item)
End With
End Sub
Public Function returnKeys() As String()
Dim Keys() As String
With ItemInventory
Keys = .Keys.ToList.ToArray
End With
Return Keys
End Function
End Class
Not pretty, I know, but it gets the job done, that's all I aim to do. Now a bit of this also has to do with displaying a dictionary item in the program, which I'm also having issues with, however, I'd like to take this one step at a time, so we'll get to that later, if possible.
As per writing, this is my current code for reading and writing:
Imports System.IO
Public Class InventoryFile
Public Sub RFile(ByVal FPath As String, ByRef dInventory As Inventory)
Dim infile As StreamReader = File.OpenText(FPath)
Dim entireLine As String = infile.ReadLine()
Dim fields() As String = entireLine.Split(","c)
While infile.EndOfStream
Dim dItem As New Item
dItem.ID = fields(0)
dItem.Description = fields(1)
dItem.Daily = fields(2)
dItem.Weekly = fields(3)
dItem.Monthly = fields(4)
dItem.Quantity = fields(5)
'AddItem
dInventory.addItem(dItem.ID, dItem.Description, dItem.Daily, dItem.Weekly, _
dItem.Monthly, dItem.Quantity)
End While
End Sub
Public Sub WFile(ByVal FPath As String, ByRef dInventory As Inventory)
Dim outfile As StreamWriter = File.CreateText(FPath)
For Each Item As KeyValuePair(Of String, Item) In dInventory.ItemInventory
Next
End Sub
End Class
I hope that posted right. Now, reading in, as far as I understand, works just fine, in terms of a file going into a dictionary, however 'WFile', my StreamWriter, is what's got me stumped. Can someone help me with that? Likewise, its supposed to close and write to the file upon closing, and my only code for the close button is the Me.Close() command. How would I write a trigger to make the program write to the file? Know that the main form code, and my 'InventoryFile' are both separate classes, so this has to be done by referencing the other classes in question
Try this to write each dictionary key/value pair on a single line in the file:
Dim fs As FileStream
' Open the stream and write to it.
fs = File.OpenWrite(FPath)
For Each Item As KeyValuePair(Of String, Item) In dInventory.ItemInventory
fs.Write("{0}:{1}", Item.Key, Item.Value)
Next
UPDATE:
Since Item is both the variable used in the loop and the name of a class, change it to another name like singleItem and then pull out the other pieces of information from the Value portion of the key/value pair, because the Value is actually an Item class object, like this:
Dim fs As FileStream
' Open the stream and write to it.
fs = File.OpenWrite(FPath)
For Each singleItem As KeyValuePair(Of String, Item) In dInventory.ItemInventory
fs.Write("{0}:{1}:{2}:{3}:{4}:{5}:{6}", singleItem.Key, singleItem.Value.ID, singleItem.Value.Description, singleItem.Value.Daily, singleItem.Value.Weekly, singleItem.Value.Monthly, singleItem.Value.Quantity)
Next
In order to create the format your RFile procedure can read you need something like this:
For Each kvp As KeyValuePair(Of String, Item) In dInventory.ItemInventory
' using Karl's compact approach, but add commas
' since your Read expects them
outfile.Write("{0},{1},{2},{3},{4}...", kvp.Key, kvp.Value.ID, _
kvp.Value.Description, ... kvp.Value.Quantity.Tostring)
' the Value part of the fvp is another class, right? `Value.XXX` should drill
' into it to get to the members
Next
outfile.flush
outfile.close ' look into 'Using...'
Thats NOT how I would do it, look into serialization for a less fragile way to read/write the data. It is basically meant for just this sort of thing: save class data for later, and it is not that hard to use.
as for hooking it up, the button click would just call Inventory.WFile