Improve this ugly piece of code - vb.net

I'm writing a QR code generator in VB.net
First I check what value (of the QR code version) was chosen by the user.
Each version has fixed count of bits per mode (0001, 0010, 0100, 1000).
Basically what I got now is the following:
Private Function get_number_of_bits() As Integer
Dim bits As Integer
If listVersion.Value < 10 Then
If get_binary_mode(listMode.SelectedItem) = "0001" Then
bits = 10
End If
If get_binary_mode(listMode.SelectedItem) = "0010" Then
bits = 9
End If
If get_binary_mode(listMode.SelectedItem) = "0100" Or _
get_binary_mode(listMode.SelectedItem) = "1000" Then
bits = 8
End If
ElseIf listVersion.Value < 27 Then
If get_binary_mode(listMode.SelectedItem) = "0001" Then
bits = 12
End If
If get_binary_mode(listMode.SelectedItem) = "0010" Then
bits = 11
End If
If get_binary_mode(listMode.SelectedItem) = "0100" Then
bits = 16
End If
If get_binary_mode(listMode.SelectedItem) = "1000" Then
bits = 10
End If
Else
If get_binary_mode(listMode.SelectedItem) = "0001" Then
bits = 14
End If
If get_binary_mode(listMode.SelectedItem) = "0010" Then
bits = 13
End If
If get_binary_mode(listMode.SelectedItem) = "0100" Then
bits = 16
End If
If get_binary_mode(listMode.SelectedItem) = "1000" Then
bits = 12
End If
End If
Return bits
End Function
Which works but of course it is an ugly piece of ..... code :)
What would be a better way to write this?
EDIT
As requested.
listMode is a combobox which is filled with:
Private Function get_encoding_modes() As Dictionary(Of String, String)
Dim modes As New Dictionary(Of String, String)
modes.Add("0000", "<Auto select>")
modes.Add("0001", "Numeric (max. 7089 chars)")
modes.Add("0010", "Alphanumeric (max. 4296 chars)")
modes.Add("0100", "Binary [8 bits] (max. 2953 chars)")
modes.Add("1000", "Kanji/Kana (max. 1817 chars)")
Return modes
End Function
Code for get_binarymode
Private Function get_binary_mode(ByVal mode As String) As String
Dim modes As New Dictionary(Of String, String)
modes = get_encoding_modes()
Dim result As String = ""
Dim pair As KeyValuePair(Of String, String)
For Each pair In modes
If pair.Value = mode Then
result = pair.Key
End If
Next
Return result
End Function

"TL;DR girl" to the rescue! made the code less ugly! learned about LINQ in VB, and how do to (and not do) Lambdas. removed need for reverse dictionary search, removed a lot of repeating yourself, and just made things generally pleasant to work with. :) ♡
Okay, I wanted to take a stab at this, even though Visual Basic isn't my usual thing. However, there were some things that I thought I could improve, so I decided to take a stab. First of all, I created a solution and implemented some basic functionality because I am not fluent in VB and figured it would be easier to do that way. If you're interested, the entire solution can be found here: UglyCode-7128139.zip
I created a little sample form and tried to include everything I could glean from the code. Here's a screenshot:
This little app was really all I used to test the code; but even though I used this as target for the code I wrote, I think I came up with some good ways of dealing with things that can easily be made more generic. All of it is currently implemented in the main form code file, but there's nothing preventing it being pulled out into some more generic helper classes.
First, I tackled the lookups. One thing I wasn't sure of was the number of the third Version that could be selected, since it's covered by the Else part of the first big If statement in the question. This wasn't a problem for the lookup I created, since there was only the one unknown value. I chose 0 but it should probably be updated to the real value if there is one. If it's really just a default, then 0 should be fine for our purposes.
I guess first, let's look at the lookup for number of bits, and the lookup for encoding:
Lookups
You'll note that my lookups and a helper function are declared as Public Shared. I did this because:
* neither the lookups nor the helper function requires knowing anything about a specific instance of the class it belongs to.
* it allows you to create the item once for the entire application, so you avoid having to create it anew each time.
* multiple creations isn't an issue for this application, but I just did it on principle: for large lookups and/or applications which created many instances of a class containing a lookup, the memory and processing requirements can become burdensome.
BitsLookup:
' Maps from a Version and Mode to a Number of Bits
Public Shared BitsLookup _
As New Dictionary(Of Tuple(Of Integer, String), Integer) From
{
{VersionAndMode(10, "0001"), 10},
{VersionAndMode(10, "0010"), 9},
{VersionAndMode(10, "0100"), 8},
{VersionAndMode(10, "1000"), 8},
{VersionAndMode(27, "0001"), 12},
{VersionAndMode(27, "0010"), 11},
{VersionAndMode(27, "0100"), 16},
{VersionAndMode(27, "1000"), 10},
{VersionAndMode(0, "0001"), 14},
{VersionAndMode(0, "0010"), 13},
{VersionAndMode(0, "0100"), 16},
{VersionAndMode(0, "1000"), 12}
}
The idea here is very simple: instead of representing the lookup in a procedural manner, via the big If statement or via Select Case, I tried to see what the code was really doing. And from what I can tell this is it. Representing it in this kind of structure seems to me to fit better with the actual meaning of the data, and it allows us to express our ideas declaratively (what to do) rather that imperatively (how to do it). VB's syntax is a little bulky here but lets go through the parts.
Tuple(Of Integer, String)
A Tuple is just a convenient way to group data items. Until I saw it in .NET I had only heard of it in the context of a relational database. In a relational database a Tuple is approximately equivalent to a row in a table. There are a few differences, but I'll avoid going off-track here. Just be sure you know that a Tuple is not always used in the same sense as it is here.
But, in this case, it seemed to me that the data was organized as a lookup of the number of bits, based upon both the version and mode. Which comes first is not really relevant here, since either here (or in any procedural lookup), we could just as easily reverse the order of the items without it making a difference.
So, there is a unique "thing" that determines the number of bits, and that "thing" is the combination of both. And, a perfect collection type to use when you have a unique thing (Key) that lets you look up something else (Value) is of course the Dictionary. Also note that, a Dictionary represents something very much like a database table with three columns, Version, BinaryMode, and NumberOfBits or similar. In a database you would set a key, in this case a primary key and/or index, and your key would be the combination of both Version and BinaryMode. This tells the database that you may only ever have one row with the same values for those fields, and it therefore allows you to know when you run a query, you will never get two rows from one set of values for each.
As New Dictionary(Of Tuple(Of Integer, String), Integer) From
In VB this is the way to create a Dictionary using an initializer: create a New Dictionary(Of T1, T2) and then use the From keyword to tell it that an initializer list is coming. The entire initializer is wrapped in curly braces, and then each item gives a comma separated .Key and .Value for the item.
{VersionAndMode(10, "0001"), 10},
Now, the first item in our Dictionary is a Tuple(Of Integer, String). You can create a tuple either with something like New Tuple(Of T1, T2)(Item1Val, Item2Val) or something like Tuple.Create(Of T1, T2)(Item1Val, Item2Val). In practice, you will usually use the .Create method, because it has one very nice feature: it uses type inference to determine which type you actually create. In other words, you can also call Tuple.Create(Item1Val, Item2Val), and the compiler will infer T1 and T2 for you. But, there is one main reason that I created this helper function instead:
Public Shared Function _
VersionAndMode(Version As Integer, Mode As String) _
As Tuple(Of Integer, String)
Return Tuple.Create(Of Integer, String)(Version, Mode)
End Function
And that's because Tuple doesn't tell you anything about the data you are containing. I might even be tempted in a production application to even create a VersionAndMode class that simply Inherits Tuple<Of Integer, String) just because it's a lot more descriptive.
That pretty much covers the lookup initialization. But what about the actual lookup? Well, let's ignore for a moment where the values are coming from, but so, now the lookup is trivial. The complexity of the If statement in the original is now contained in what I believe is a much more descriptive fashion, it's a declarative way of stating the same information in that procedure. And with that out of the way, we can just focus on what we're doing instead of how we're doing it: Dim NumberOfBits = BitsLookup.Item(Version, Mode). Well, I do declare. :)
There's another lookup, the EncodingModes lookup, and there's more to be said about that, so I'll cover it in the next section.
The Methods
Once we have the lookup in place, we can take a look at the other methods. Here are the ones I implemented.
Number Of Bits Lookup
So, here's what's left of the big If conglomeration:
Public ReadOnly Property NumberOfBits As Integer
Get
Return BitsLookup.Item(
VersionAndMode(Version, BinaryMode)
)
End Get
End Property
There's not really much left to say about this one.
Form Initialization Method
When you have a nice designer, it's tempting to try to do everything there so there's no code to write. However, in our case all the data we need is already contained in the lookups we created. If we were to simply enter the items into the listbox as strings, we'd end up not only repeating ourselves, violating one of the general principles of development (DRY, Don't Repeat Yourself), but we're also losing the nice connections we already have set up with our data.
So, let's take a look at that last lookup:
Public Shared EncodingModes As New Dictionary(Of String, String) From
{
{"0000", "<Auto Select>"},
{"0001", "Numeric (max. 7089 chars)"},
{"0010", "Alphanumeric (max. 4296 chars)"},
{"0100", "Binary [8 bits] (max. 2953 chars)"},
{"1000", "Kanji/Kana (max. 1817 chars)"}
}
Here, again, we just have a declarative way of saying the same thing that was said in the method that created the data imperatively, and again, it's advantageous because we only need to create it one time, and after that look data up based on the key. Here our Key is the "Encoding Mode" and the Value holds the text to displayed for any particular Key. But, so, what happens if we just enter the text into our ListBox in the forms designer?
Well, two things. First, we have entered it twice now. Second, is now we either would have to create a different lookup to go back, or, we have to go against the grain in the way a Dictionary is used. It's not impossible, but as you can see in the original get_binary_mode function, it's not very clean either. Plus, we've lost the advantages of the declarative nature of the Dictionary.
So, how do we use the existing lookup to create our ListBox items without repeating ourselves? Well, one thought would be to just grab the Values and put them in a list, and then putting that in the .Items field on the ListBox. But, see, we didn't solve the other problem; still we have to go backward, from Value to Key (which in a Dictionary isn't even guaranteed to be unique).
Fortunately, there's a solution: using ListBox.DataSource. This allows us to take many different data structures, and feed them to the listbox (nom nom), rather than being limited to List<T> and things that implement IList. But this doesn't necessarily select the proper items for display, so what do we do if it displays the wrong property? Well, the final missing piece is ListBox.DisplayMember where we set the name of the property to be used for display.
So, here's the code we can use to set up our listboxes:
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
listboxVersion.DataSource =
BitsLookup.Keys.Select(
Function(VAM As Tuple(Of Integer, String)) _
VAM.Item1()
).
Distinct().ToList()
If listboxVersion.Items.Count > 0 _
Then listboxVersion.SelectedIndex = 0
listboxMode.DisplayMember = "Item1"
listboxMode.DataSource =
EncodingModes.AsQueryable().Select(
Function(KVP As KeyValuePair(Of String, String)) _
Tuple.Create(KVP.Key, KVP.Value)
).
ToList()
If listboxMode.Items.Count > 0 _
Then listboxMode.SelectedIndex = 0
End Sub
So, I'm using functionality from LINQ here to get my data in whatever form makes sense from the lookups, setting that as the .DataSource, and then telling the ListBox which member to display. I love it when I get to tell things what to do. :) Now, I can't possibly do justice to Lambda Epxressions here, but let me take a quick stab. So, the first listbox is set up like so:
listboxVersion.DataSource =
BitsLookup.Keys.Select(
Function(VAM As Tuple(Of Integer, String)) _
VAM.Item1()
).
Distinct().ToList()
Each individual part is fairly understandable, and if you've worked with SQL or other types of queries, I'm sure this doesn't seem too unfamiliar. But, so, the problem with the way we have our data stored right now is that we only have the versions numbers that are in the Tuples in the BitLookup. Worse yet, there are several keys in the lookup that have each value contained in them. [Note that this is likely a sign that we should have that information's primary store somewhere else; it's ok for it to be part of something else, but we really shouldn't usually have data stored such that the primary store of the data contains duplicated information.]
As a reminder of what one of the rows looks like:
{VersionAndMode(10, "0001"), 10},
So, there are two things we have to accomplish here. Since the UI representation is the same as the actual number here, we don't have to worry about making something other than a list to hold the data. First, we need to figure out how to extract the values from the Keys of that lookup, and second, we need to figure a way to make sure that we don't have multiple copies of the data in our list.
Let's think about how we would do this if we were doing it imperatively. We'd say, ok, computer, we need to look through all the keys in the lookup. (ForEach). Then, we'd look at each one in turn, and take Item1's value (that's the property storing the version number), then probably check to see if it already existed in the list, and finally, if it was not already there (.IndexOf(item) < 0) we would add it. And this would be okay! The most important thing is that this gives the right behavior, and it is quite understandable.
However, it does take up space, and it's still very much concerned with how it's getting done. This would eliminate, for instance, improving performance without mucking about with the procedure itself. Ideally, we would want to be able to just tell the computer what to do, and have it hand it to us on a jewel-encrusted gold platter. (That's better than a silver one any day, right?) And this is where LINQ and Lambda expressions come in.
So, let's look at that code again:
listboxVersion.DataSource =
BitsLookup.Keys.Select(
Function(VAM As Tuple(Of Integer, String)) _
VAM.Item1()
).
Distinct().ToList()
We're using one of the LINQ extension methods .Select on the Key collection of the lookup, which does about what it sounds like: it selects something based on each item in the Key collection, and puts it all together into a nice collection for us. We're also using the .Distinct() extension on the result, which ensures that there's no more than one of each item in the list, and finally, we're using the ToList() method which puts everything into a list.
Inside the select is where the Lambda Expression comes in:
Function(VAM As Tuple(Of Integer, String)) _
VAM.Item1()
Caveat: VB only supports Lambda Expressions for things like this, not Lambda Statements. The difference is that a Lambda Expression does not specify a return type, and does not have an End Function. You'll notice I used a space, underscore pattern at the end of the first line, this is because Lambda Expressions must all be on one line, and the " _" tells the compiler to consider the next line to be continued as if it were one line. For full details on the restrictions, see Lambda Expressions (Visual Basic).
The parentheses on VAM.Item1() were inserted there for me by VB, but they are not required. But this function is what tells the .Select method which item to put into the new collection for each item in the source collection, and it also tells it what type should be collected (in this case an Integer). The default collection type for most of the common LINQ functions, including Select in this case is IEnumerable(T1), and in this case, since we are returning an Integer, the compiler can infer the type of the resulting collection, an IEnumerable(Integer). Distinct() remove duplicates and also returns IEnumerable(Integer), and ToList() returns a List(Integer) from an IEnumerable(Integer).
And that's type we need to set for our ListBox, so we're done with that!
And, also, there's the listbox with the Encoding Mode:
listboxMode.DataSource =
EncodingModes.AsQueryable().Select(
Function(KVP As KeyValuePair(Of String, String)) _
Tuple.Create(KVP.Key, KVP.Value)
).
ToList()
This code works the very same way: we take the EncodingModes lookup Dictionary with items like {"0000", "<Auto Select>"},, we perform a Select to get an IEnumerable returned to us, the function takes a single line (KeyValuePair) from the dictionary, but then it does something a little different. It returns a Tuple with the Key and Value both! Why becomes apparent in the final section, but the important thing is that we're returning something that has both the pieces of data in it, and this is in fact the solution to the problem with figuring out how to get the data we need from the listbox.
So, we're in the home stretch. Here are the last couple of items we use to set the textbox with the number of bits:
Private ReadOnly Property Version As Integer
Get
Dim SelectedVersion As Integer = _
listboxVersion.SelectedItem
Return SelectedVersion
End Get
End Property
This property just returns the current value from the ListBox, which contains the values we pulled out of the lookup in the setup.
Private ReadOnly Property BinaryMode As String
Get
Dim EncodingMode As Tuple(Of String, String) = _
listboxMode.SelectedItem
Dim RetVal As String = "0001"
If EncodingMode.Item1 <> "0000" _
Then RetVal = EncodingMode.Item1
Return RetVal
End Get
End Property
And this property pulls the BinaryMode, but notice: there's no need to use the Dictionary in reverse: since we used a DataSource, we can simply pull out the selected item, cast it to the data type we put in, and then we can get out the associated bit of data without ever having to go back to the Dictionary.
Just by the fact that the user selected a particular item, we know what the corresponding binary key is, and return that. And, the other cool thing about that is that even if there were duplicate Values in the Dictionary, there would be no ambiguity about which was the proper value. (Now, the user wouldn't know, and that's a problem, but can't solve everything at once. :D)
The one little hitch in that property was what to do if the EncodingMode turned out to be '0000'. (That had a value of "<Auto Select>" in the Values, and is not accounted for by the lookup.) So, I auto selected it to be "0001"! I'm sure a more intelligent manner would be chosen for a real application, but that's good enough for me, for now.
Pulling (Putting?) It All Together
Well, the very last piece of the puzzle, the thing that actually gets the number of bits and sets it to the TextBox on the form:
Private Sub btnSelect_Click(sender As System.Object, e As System.EventArgs) _
Handles btnSelect.Click
txtNumberOfBits.Text = NumberOfBits.ToString()
End Sub
So, all we had to do is take the NumberOfBits field which returns the number of bits based on the items the user has selected for Version and EncodingMode. Kinda anti-climactic, huh?
Well, sorry for the length, I hope this has been helpful, I know I learned a few things. :)

I'll suggest this 'improvement'.
get_binary_mode() gets called once. a bit of a performance gain.
Your 3 cases for listVersion.value are still 3, but easier to read. When you need to add a 4th, it's a simple job to add another Case and Exit Select. Be sure to keep the Exit Select statements in case a value like 9 comes along, as it satisifes both <10 and <27.
a separate function and separation of logic for each case (under 10, under27, etc). This should be more maintainable in the future.
When something needs changing, we know exactly where to go, and it's very localized.
Obviously my naming conventions need some work to have the intent expressed in more human readable/understandable terms.
certainly this answer contains more lines of code. I'd rather read & maintain smaller self-contained functions that do one thing (and one thing well). YMMV.
you could go one step further and get rid of the local variable bits. Simply Return AnalyzeUnderXX().
Private Function get_number_of_bits() As Integer
Dim mode As String = get_binary_mode(listMode.SelectedItem)
Dim bits As Integer
Select Case Convert.ToInt32(listVersion.Value)
Case Is < 10
bits = AnalyzeUnder10(mode)
Exit Select
Case Is < 27
bits = AnalyzeUnder27(mode)
Exit Select
Case Else
bits = AnalyzeDefault(mode)
End Select
Return bits
End Function
Private Function AnalyzeUnder10(input As String) As Integer
Select Case input
Case "0001"
Return 10
Case "0010"
Return 9
Case "0100" Or "1000"
Return 8
End Select
End Function
Private Function AnalyzeUnder27(input As String) As Integer
Select Case input
Case "0001"
Return 12
Case "0010"
Return 11
Case "0100"
Return 16
Case "1000"
Return 10
End Select
End Function
Private Function AnalyzeDefault(input As String) As Integer
Select Case input
Case "0001"
Return 14
Case "0010"
Return 13
Case "0100"
Return 16
Case "1000"
Return 12
End Select
End Function

Related

i want to make a new variable that says shot1 shot2 shot3 so on and forth how do i do this?

Here is what i have tried so far , but no new shot variable is being declared
Module Module1
Dim shotlist As New List(Of Boolean)
Dim shono As Integer = 0
Dim shonos As String
Dim shotname As String
Dim fshot As Boolean
Dim shots As String
Sub Main()
For i As Integer = 0 To 1000
Dim ("shots" & i) as String = "shots" & i
fshot = Convert.ToBoolean(shots)
Next
End Sub
End Module
You can't do things this way. The variable names you see when you write a program are lost, turned into memory addresses by the compiler when it compiles your program. You cannot store any information using a variable's name - the name is just a reference for you, the programmer, during the time you write a program (design time)
Think of variables like buckets, with labels on the outside. Buckets only hold certain kinds of data inside
Dim shotname as String
This declares a bucket labelled shotname on the outside and it can hold a string inside. You can put a string inside it:
shotname = "Shot 1"
You can put any string you like inside this bucket, and thus anything you can reasonably represent as a string can also be put inside this bucket:
shotname = DateTime.Now.ToString()
This takes the current time and turns it into a string that looks like a date/time, and puts it in the bucket. The thing in the bucket is always a string, and lots of things (nearly anything actually) can be represented as a string, but we don't type all our buckets as strings because it isn't very useful - if you have two buckets that hold numbers for example, you can multiply them:
Dim a as Integer
a=2
Dim b as Integer
b=3
Dim c as Integer
c=a*b
But you can't multiply strings, even if they're strings trust look like numbers; strings would have to be converted to numbers first. Storing everything as a string and converting it to some other type before working on it then converting it back to a string to store it would be very wearisome
So that's all well and good and solves the problem of storing varying information in the computer memory and giving it a name that you can reference it by, but it means the developer has to know all the info that will ever be entered into the program. shotname is just one bucket storing one bit of info. Sure you could have
Dim shotname1 as String
Dim shotname2 as String
Dim shotname3 as String
But it would be quite tedious copy paste exercise to do this for a thousand shotname, and after you've done it you have to refer to all of them individually using their full name. There isn't a way to dynamically refer to bucket labels when you write a program, so this won't work:
For i = 0 To 1000
shotname&i = "This is shotname number " & i
Next i
Remember, shotname is lost when compiling, and i is too, so shotname&i just flat out cannot work. Both these things become, in essence, memory addresses that mean something to the compiler and you can't join two memory addresses together to get a third memory address that stores some data. They were only ever nice names for you to help you understand how to write the program, pick good names and and not get confused about what is what
Instead you need the mechanism that was invented to allow varying amounts of data not known at the design-time of the program - arrays
At their heart, arrays are what you're trying to do. You're trying to have load of buckets with a name you can vary and form the name programmatically.
Array naming is simple; an array has a name like any other variable (what I've been calling a bucket up to now - going to switch to calling them variables because everyone else does) and the name refers to the array as a whole, but the array is divided up into multiple slots all of which store the same type of data and have a unique index. Though the name of the array itself is fixed, the index can be varied; together they form a reference to a slot within the array and provide a mechanism for having names that can be generated dynamically
Dim shotnames(999) as String
This here is an array of 1000 strings, and it's called shotnames. It's good to use a plural name because it helps remind you that it's an array, holding multiple something. The 999 defines the last valid slot in the array - arrays start at 0, so with this one running 0 to 999 means there are 1000 entries in it
And critically, though the shotnames part of the variable name remains fixed and must be what you use to refer to the array itself(if you want to do something with the entire thing, like pass it to a function), referring to an individual element/slot in the array is done by tacking a number onto the end within brackets:
shotnames(587) = "The 588th shotname"
Keep in mind that "starts at 0 thing"
This 587 can come from anything that supplies a number; it doesn't have to be hard coded by you when you write the program:
For i = 0 to 999
shotnames(i) = "The " & (i+1) & "th shotname"
Next i
Or things that generate numbers:
shotnames(DateTime.Now.Minute) = "X"
If it's 12:59 when this code runs, then shotnames(59), the sixtieth slot in the array, will become filled with X
There are other kinds of varying storage; a list or a dictionary are commonly used examples, but they follow these same notions - you'll get a variable where part of the name is fixed and part of the name is an index you can vary.
At their heart they are just arrays too- if you looked inside a List you'd find an array with some extra code surrounding it that, when the array gets full, it makes a new array of twice the size and copies everything over. This way it provides "an array that expands" type functionality - something that arrays don't do natively.
Dictionaries are worth mentioning because they provide a way to index by other things than a number and can achieve storage of a great number of things not known at design time as a result:
Dim x as New Dictionary(Of String, Object)
This creates a dictionary indexed by a string that stores objects (I.e. anything - dates, strings, numbers, people..)
You could do like:
x("name") = "John"
x("age") = 32
You could even realize what you were trying to do earlier:
For i = 0 to 999
x("shotname"&i) = "The " & (i+1) & "th shotname"
Next i
(Though you'd probably do this in a Dictionary(Of string, string), ie a dictionary that is both indexed by string and stores strings.. and you probably wouldn't go to this level of effort to have a dictionary that stores x("shotname587") when it's simpler to declare an array shotname(587))
But the original premise remains true: your variable has a fixed part of the name (ie x) and a changeable part of the name (ie the string in the brackets) that is used as an index.
And there is no magic to the indexing by string either. If you opened up a dictionary and looked inside it, you'd find an array, together with some bits of code that take the string you passed in as an index, and turn it into a number like 587, and store the info you want in index 587. And there is a routine to deal with the case where two different strings both become 587 when converted, but other than that it's all just "if you want a variable where part of the name is changeable/formable programmatically rather than by the developer, it's an array"

Match Words and Add Quantities vb.net

I am trying to program a way to read a text file and match all the values and their quantites. For example if the text file is like this:
Bread-10 Flour-2 Orange-2 Bread-3
I want to create a list with the total quantity of all the common words. I began my code, but I am having trouble understanding to to sum the values. I'm not asking for anyone to write the code for me but I am having trouble finding resources. I have the following code:
Dim query = From data In IO.File.ReadAllLines("C:\User\Desktop\doc.txt")
Let name As String = data.Split("-")(0)
Let quantity As Integer = CInt(data.Split("-")(1))
Let sum As Integer = 0
For i As Integer = 0 To query.Count - 1
For j As Integer = i To
Next
Thanks
Ok, lets break this down. And I not seen the LET command used for a long time (back in the GWBASIC days!).
But, that's ok.
So, first up, we going to assume your text file is like this:
Bread-10
Flour-2
Orange-2
Bread-3
As opposed to this:
Bread-10 Flour-2 Orange-2 Bread-3
Now, we could read one line, and then process the information. Or we can read all lines of text, and THEN process the data. If the file is not huge (say a few 100 lines), then performance is not much of a issue, so lets just read in the whole file in one shot (and your code also had this idea).
Your start code is good. So, lets keep it (well ok, very close).
A few things:
We don't need the LET for assignment. While older BASIC languages had this, and vb.net still supports this? We don't need it. (but you will see examples of that still floating around in vb.net - especially for what we call "class" module code, or "custom classes". But again lets just leave that for another day.
Now the next part? We could start building up a array, look for the existing value, and then add it. However, this would require a few extra arrays, and a few extra loops.
However, in .net land, we have a cool thing called a dictionary.
And that's just a fancy term of for a collection VERY much like an array, but it has some extra "fancy" features. The fancy feature is that it allows one to put into the handly list things by a "key" name, and then pull that "value" out by the key.
This saves us a good number of extra looping type of code.
And it also means we don't need a array for the results.
This key system is ALSO very fast (behind the scene it uses some cool concepts - hash coding).
So, our code to do this would look like this:
Note I could have saved a few lines here or there - but that would make this code hard to read.
Given that you look to have Fortran, or older BASIC language experience, then lets try to keep the code style somewhat similar. it is stunning that vb.net seems to consume even 40 year old GWBASIC type of syntax here.
Do note that arrays() in vb.net do have some fancy "find" options, but the dictionary structure is even nicer. It also means we can often traverse the results with out say needing a for i = 1 to end of array, and having to pull out values that way.
We can use for each.
So this would work:
Dim MyData() As String ' an array() of strings - one line per array
MyData = File.ReadAllLines("c:\test5\doc.txt") ' read each line to array()
Dim colSums As New Dictionary(Of String, Integer) ' to hold our values and sum them
Dim sKey As String
Dim sValue As Integer
For Each strLine As String In MyData
sKey = Split(strLine, "-")(0)
sValue = Split(strLine, "-")(1)
If colSums.ContainsKey(sKey) Then
colSums(sKey) = colSums(sKey) + sValue
Else
colSums.Add(sKey, sValue)
End If
Next
' display results
Dim KeyPair As KeyValuePair(Of String, Integer)
For Each KeyPair In colSums
Debug.Print(KeyPair.Key & " = " & KeyPair.Value)
Next
The above results in this output in the debug window:
Bread = 13
Flour = 2
Orange = 2
I was tempted here to write this code using just pure array() in vb.net, as that would give you a good idea of the "older" types of coding and syntax we could use here, and a approach that harks all the way back to those older PC basic systems.
While the dictionary feature is more advanced, it is worth the learning curve here, and it makes this problem a lot easier. I mean, if this was for a longer list? Then I would start to consider introduction of some kind of data base system.
However, without some data system, then the dictionary feature is a welcome approach due to that "key" value lookup ability, and not having to loop. It also a very high speed system, so the result is not much looping code, and better yet we write less code.

Mid() usage and for loops - Is this good practice?

Ok so I was in college and I was talking to my teacher and he said my code isn't good practice. I'm a bit confused as to why so here's the situation. We basically created a for loop however he declared his for loop counter outside of the loop because it's considered good practice (to him) even though we never used the variable later on in the code so to me it looks like a waste of memory. We did more to the code then just use a message box but the idea was to get each character from a string and do something with it. He also used the Mid() function to retrieve the character in the string while I called the variable with the index. Here's an example of how he would write his code:
Dim i As Integer = 0
Dim justastring As String = "test"
For i = 1 To justastring.Length Then
MsgBox( Mid( justastring, i, 1 ) )
End For
And here's an example of how I would write my code:
Dim justastring As String = "test"
For i = 0 To justastring.Length - 1 Then
MsgBox( justastring(i) )
End For
Would anyone be able to provide the advantages and disadvantages of each method and why and whether or not I should continue how I am?
Another approach would be, to just use a For Each on the string.
Like this no index variable is needed.
Dim justastring As String = "test"
For Each c As Char In justastring
MsgBox(c)
Next
I would suggest doing it your way, because you could have variables hanging around consuming(albeit a small amount) of memory, but more importantly, It is better practice to define objects with as little scope as possible. In your teacher's code, the variable i is still accessible when the loop is finished. There are occasions when this is desirable, but normally, if you're only using a variable in a limited amount of code, then you should only declare it within the smallest block that it is needed.
As for your question about the Mid function, individual characters as you know can be access simply by treating the string as an array of characters. After some basic benchmarking, using the Mid function takes a lot longer to process than just accessing the character by the index value. In relatively simple bits of code, this doesn't make much difference, but if you're doing it millions of times in a loop, it makes a huge difference.
There are other factors to consider. Such as code readability and modification of the code, but there are plenty of websites dealing with that sort of thing.
Finally I would suggest changing some compiler options in your visual studio
Option Strict to On
Option Infer to Off
Option Explicit to On
It means writing more code, but the code is safer and you'll make less mistakes. Have a look here for an explanation
In your code, it would mean that you have to write
Dim justastring As String = "test"
For i As Integer = 0 To justastring.Length - 1 Then
MsgBox( justastring(i) )
End For
This way, you know that i is definitely an integer. Consider the following ..
Dim i
Have you any idea what type it is? Me neither.
The compiler doesn't know what so it defines it as an object type which could hold anything. a string, an integer, a list..
Consider this code.
Dim i
Dim x
x = "ab"
For i = x To endcount - 1
t = Mid(s, 999)
Next
The compiler will compile it, but when it is executed you'll get an SystemArgumenException. In this case it's easy to see what is wrong, but often it isn't. And numbers in strings can be a whole new can of worms.
Hope this helps.

Integer in VB.net turns into String in VBA

I have a list of objects that are "keys" into a table - the keys are simply the items in the first column. They can be Integers or Strings, depending on what DB table we read it from. Since we use them a lot, we cache that column in an ArrayList called "Keys".
We wrote cover methods to return Row, one that uses strings and the other integers. If you call the integer version it simply returns that row by index. If you call the string, it looks down Keys for a match, and uses that index to pull out the row.
Ok, so now I pass Keys to Excel, pull out one of them in a loop, and ask it what it is...
TypeName(DB.Keys(i))
And the object returns...
Long
Great, the keys must have been integers! So now I'll try to get the row for that key, by calling the accessor method, Row...
DB.Row(DB.Keys(i))
And it calls into the version that takes a String.
Whoa!
Can anyone think of a reason that VBA calling back into our VB.net DLL ends up calling the wrong accessor?
ADDED CODE: I can't figure out how to put the code in as a reply, so I'm editing this. Here is the code in the VB.net class:
Public Function Row(ByVal K As String) As DataRow
Dim R As DtaRow = DB.Tables(0).Rows(K)
Return New DataRow(R)
End Function
Public Function Accounts(ByVal K As Integer) As DataRow
Dim R As DtaRow = DB.Tables(0).Rows(K)
Return New DataRow(R)
End Function
If you're wondering, there's two versions of Rows, which take strings or ints.
This code works perfectly from VB.net. You can ask for a row by key string or by the row number, that invokes the proper Row, which calls the proper Rows, and out comes the proper answer.
But in VBA, it always calls the method with the string input. If I rename it to RowIHATEYOUALL then I can call the Integer version just fine. But when there are two of them, differing only in signature, no such luck.
And the A and i (see comments) was my typo.
The interop layer does not support overloaded methods. Whenever you call Row, the first declared method with that name is used. The .NET overload resolution algorithm does not apply here.
Other overloads are exposed to VBA as Row_2, Row_3, etc. Thus, the following code should do what you expect:
DB.Row_2(DB.Keys(i))
This implicit dependency on the order of declaration has a high potential for error. Thus, I would suggest to either
give the methods unique names if they are called from VBA,
or, if you want to retain the "nice" overloaded version for .NET, add compatibility methods for VBA:
<Obsolete("Compatibility method for VBA, use Row instead.")>
Public Function RowByKeyVBA(ByVal K As String) As DataRow
Return Row(K)
End Function
<Obsolete("Compatibility method for VBA, use Row instead.")>
Public Function RowByNumberVBA(ByVal K As Integer) As DataRow
Return Row(K)
End Function
Further information can be found in the following question:
COM->.NET - can't access overloaded method
Following Heinzi's notes (above) I fixed this by making three method signatures for each call, one takes an Object and then attempts to figure out what it is, the others take the String and Integers. Within VB/C#/etc the proper String or Integer methods get called as expected, from VBA the Object version is called, as Heinzi noted
This causes the very minor issue that a user may have a "number like value" that is actually a String. For instance, the array keys might be "User3232" or "3232", both of which are String objects in the table. So you have to be careful, simply asking if the Object can be converted to an Int32 is not enough. This is unlikely to be something that effects most users.

Sorting 2 arrays side by side

Basicly I have a program that retrieves data and parses it, which is fine, so it starts from:
11:981.8 which equals to November 981.8 now what I have done is split the "November" and the "981.8" into 2 different arrays, with other similar data, now what I need to be able to do, is sort the array in either Ascending or Descending order, however keeping in tact, the November and 981.8 side by side in a list box.
My current code is:
Private Sub sortData(ByVal strYear As String, ByVal strSort As String)
lbDispData.Items.Clear()
Dim strData As String = My.Settings.usage2011
Dim arrRawData() As String
Dim arrMonth As New ArrayList
Dim arrKilo As New ArrayList
arrRawData = strData.Split("_")
For Each strUsage As String In arrRawData
Dim arrSmall As String()
arrSmall = strUsage.Split(":")
arrSmall(0) = MonthName(arrSmall(0))
arrMonth.Add(arrSmall(0))
arrKilo.Add(arrSmall(1))
Next
If strSort = 0 Then
'Sort in ascending order
ElseIf strSort = 1 Then
'Sort in descending order
End If
End Sub
Or If possible is there a better way?
EDIT: Just to mention, there are many more values, not just November and 981.8, there would be for example December and 128.1, January and 191.1, etc.
Don't use two arrays. Use one array or List, and each item in the array should be either an instance of a custom Class which would have Month and Kilo properties, or a Tuple instance.
Then you can sort by whichever property you want, and don't have to worry about related information staying together.
To create a Tuple for an item, you use Tuple.Create:
Dim myTuple = Tuple.Create(monthValue, kiloValue)
Then you can access values from the Tuple using Item1, Item2, etc.
Dim month = myTuple.Item1
Dim kilo = myTuple.Item2
There are essentially two ways to store this kind of data: array of structures, or structure of arrays. Your implementation, at present, is the latter. As you suspect, the former is typically preferred in languages which provide access to objects and user-defined types.
To solve this problem using an array-of-structures data representation, you will define your own type - for instance, you could call it MonthFloatPair or something more relevant to your application, or you could simply use a built-in type that suits your needs - and have a single array of type MonthFloatPair. To add a MonthFloatPair, you'd parse a string exactly as you are now, and then simply assign the month to one member of MonthFloatPair and the number (Kilo?) to another. Then all you would have to do is to write a method to provide an ordering comparison of MonthFloatPair objects (I believe this is possible in vb.net, at any rate), and then sort this array normally however you'd like.
To solve this problem using a structure-of-arrays data representation, you do exactly what you're doing now. To sort, sort normally as you would for the array you're sorting on, with one caveat: every time you change the position of an element of the array on which you're basing the sorting (e.g., by exchanging in O(n^2) sorts, or placing into a new array as in MergeSort), you need to move the corresponding (same index) element in the other array in the same manner.