Looping through database with random number generation - vb.net

When adding a new DataRow, on of the items gets a random number from 100000000 to 999999999.
I need to loop through the data when this number is created to make sure it isn't there already. ( yes for a number this big chances are it will not ever be there) On the off chance that it is there, I need to generate a new number and loop through the data again.
I need to generate a random number until the random number does not match one in the database. I just can't seem to get my head around the logic today.
Here is the only thing I could come up with and I am not sure if it will work.
Dim TaskID As Integer
Do
intMergeID = Merge.RandomNumber()
For Each GetTask As DataRow In MasterDBDataSet.DBTasks
TaskID = GetTask.Item("MergeID")
If TaskID = intMergeID Then
Exit For
End If
Next
Loop Until intMergeID <> TaskID

It seems to be a pretty straightforward bit of logic.
You need to:
Generate the number
Check for the value in the database
If it exists go back to 1
If it doesn't exist then add it
A recursive function could do this pretty well. Some pseudo code for the function is:
Function CheckDatabase(ByVal number As Long) as Bool
Run database query
if(queryResult returns true)
generate new number
CheckDatabase(newNumber)
Else
Add to database
End Function

Related

Getting a count of "find" from multiple .txt files

I am currently running a find and replace through hundreds of .txt files at a time. I am looking for a way to pull a count of the number of finds for my value.
Here is the code I am currently running, hoping to be able to add to or modify this code.
Dim flatfiles As String() = IO.Directory.GetFiles("C:\DATA\TEST\", "*.txt").Where(Function(x) File.ReadAllText(x).Contains("Bob")).ToArray
For Each f As String In flatfiles
Dim contents As String = File.ReadAllText(f)
File.WriteAllText(f, contents.Replace("Bob", "Bill"))
Next
One (inefficient) way to do this would be to include a counter outside of the For Each loop (Dim itemsFound as Integer = 0), then increment it by the count of find in each file, using something like:
itemsFound = itemsFound + (Regex.Split(contents, find).Length - 1)
Regex.Split splits the string up whenever it finds find, which means the count you're looking for is one less than the number of items in the list.
I would say as well, that you're calling File.ReadAllText twice in your code, so you could improve it by getting rid of the Where code, and just check in your For each loop (seeing as you're now counting the number of instances in the file anyway, it's easy enough to check for 0 occurrences). Alternatively, you could replace the .Where code to store the contents of the file in an array rather than the name of the files (although this can be dangerous if the files are large); or you could even just do it all in Linq if you want some obfuscated code...

Auto-generate Unique ID within the constructor

Using VB.net, create a new class called staff, with three properties:
Name , LastName, ID - should be suitable for use as a primary key in a database.
Provide a class constructor to populate Name and LastName. ID should be auto-generated within the constructor and should not be passed in.
I know how to create a class, properties and constructor, I just need to know how to auto-generate ID field within the constructor. Is it possible to do this?
what I usually do is either make the id field in database as identity field and primary key so it automatically inserts the next available id or In my application I read the last ID from database and add one to it. But I need to know how to auto-generate ID field within the constructor.
Guid
If you do not have any constrain about ID type you can use a GUID:
Dim id As Guid = Guid.NewGuid()
You may even keep it as string:
Dim id As String = Guid.NewGuid().ToString("N")
That should be granted to be unique across different machines (to satisfy your requirement that it has to be suitable for use as a primary key in a database). See also this post.
Timestamp
That was worse case, if you do not have such strict requirement (uniqueness across a network) you may use a timestamp. Of course, in this case, you have to consider more issues:
Legal time: time goes back and forward twice per year.
Zones: what if user enter data in London and then he moves to New York?
Concurrency: you have to assume no one else adds records to your database (you may have collisions if they use a different technique). Also you can't apply this if execution is concurrent (multiple instance of your program running together).
Timer granularity: system date has a coarse granularity: if you construct many objects in a short period of time then you may have duplicate IDs. Workaround in this post.
Counter
If all these conditions are satisfied:
Multiple instances of your application won't run in parallel.
You're working on a single computer (not a network).
Database is empty every time your application starts.
You may use a Shared counter incremented each time a new object is constructed. If system timer granularity isn't an issue (see paragraph about timestamp)you may use system up time as ID. With limitations about granularity it should work even with multiple instances of the same application running concurrently.
If you use a Shared field you have to deal with synchronization issues. As suggested in this comment you may use a SyncLock. As alternative you may use an Interlocked.Increment operation.
Hash code
If all condistions for a counter are satisfied and this one also:
Your application is 32 bit.
Your object is not a ValueType and it doesn't override GetHashCode() method.
You can use hash-code (obtained with GetHashCode()) because (from MSDN):
In other words, two objects for which the ReferenceEquals method returns true have identical hash codes.
Because Object.ReferenceEquals() returns true only if you compare same instance then each instance will have a unique hash code (because in a 32 bit application hash code is object reference itself). Be aware this is an implementation detail and it may change.
Random number
I know this may shock someone but a good random number generator for a 64 bit value has a very low probability of collisions. I repeat very very low probability (see this article for more math details). Just do not use System.Random for this!
According to which seed you use you may be able to generate random numbers in a network scenario too (do not forget - citation needed - that earlier drafts for one local network protocol proposed a 32 bit random number as address, it has been changed because of bad feedback from users but it doesn't mean it can't work).
You want a number that won't repeat ever! So why not just use this?
Dim dateAndTime As Date
dateAndTime = Now
TextBoxPID.Text = Format(dateAndTime, "yyyyMMddHHmmss").ToString
Unless your data entries are going to take place in milliseconds, this solution works great. If you are running into a millisecond issue then just add a counter to the end of the string.
counter +=1
TextBoxPID.Text = Format(dateAndTime, "yyyyMMddHHmmss").ToString & counter.ToString
If you are working on a network and have several people doing data entry then add their employee id to the string. There are easy solution to every issue, but in most, if not all cases, this will work without issue.
Generate Random Unique User ID depending on SNTP server.
My requirements are a bit different; yet I needed to generate a random and unique User ID, that is 10 numbers, spending fair time couldn't find a suitable solution.
so I ended up with the following function; its unique and random result.
As per one application instant on one test machine it is incremental unique result; because the user will generate the ten digits one time on a non pre-selective timestamp. In addition to playing with the random alpha prefix; I hope this function can provide a solution:
Imports System.Globalization
Imports System.Net
Public Class GetNetTime
Public Shared Function GetUTC()
' connect to google servers
' you could use some SNTP time servers but can't be sure port will be open
' or you could just ping your own webserver
Dim myNetRequest As WebRequest = HttpWebRequest.Create("http://www.example.com")
' read response header from connection
Dim response = myNetRequest.GetResponse()
' read date/time header
' assume its UTC format
Dim GlobalUTC As String = response.Headers("date").ToString
' convert string to datetime object
Dim parsedDateTime As DateTime = DateTime.Parse(GlobalUTC)
' get UNIX time stamp
Dim unixTime = (parsedDateTime - New DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds
Return unixTime
End Function
End Class
To test the output, you could add:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim utc As String = GetNetTime.GetUTC
' add random alpha prefix to UNIX time stamp
Dim sPrefix As String = ""
Dim rdm As New Random()
For i As Integer = 1 To 3 ' if you need more than 3 alpah random charachters adjust i length
sPrefix &= ChrW(rdm.Next(65, 90))
Next
MsgBox(sPrefix & utc) ' OR MsgBox("ID" & sPrefix & utc)
' code here to use result
End Sub
I find this solution more useful than querying the SQL table and read last record id and do increment.
Notes:
Please don't mind long answer; as I tried to comment the code and
explain the scenario in details.
I think this is good for generating
UserID for application running on multiple workstations.
Please don't put the function in for ... loop or exhaust run it.
Output examples:
GYK1501270543
VWT1501270606
WRH1501270634
SKI1501270648
QXL1501270716
This is also based on #wpcoder answers above but a basic form and this one works for me
Public Function UIDGen(ByRef f As String) As String
Dim currentTime As DateTime = DateTime.UtcNow
Dim StringTime As String = currentTime.ToString
Dim parsedDateTime As DateTime = DateTime.Parse(StringTime)
Dim unixTime = (parsedDateTime - New DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds
Dim utcString As String = unixTime.ToString
Dim sPrefix As String = ""
Dim rdm As New Random()
For i As Integer = 1 To 3 ' 3 Letters enough ?
sPrefix &= ChrW(rdm.Next(65, 90))
Next
f = (sPrefix & utcString)
Return f
End Function

Getting Duplicate Random Numbers

I'm doing basic random number generation using this Shared Function:
Public Shared Function RandomNumber(ByVal MaxNumber As Integer, Optional ByVal MinNumber As Integer = 0) As Integer
'initialize random number generator
Dim r As New Random(Date.Now.Ticks And &HFFFF)
If MinNumber > MaxNumber Then
Dim t As Integer = MinNumber
MinNumber = MaxNumber
MaxNumber = t
End If
Return r.Next(MinNumber, MaxNumber)
End Function
Called like this: dim x as integer = Random(2100000000)
Very simple, and the seed value comes straight from a MS example.
HERE'S THE PROBLEM: I'm getting duplicate numbers on occasion, but always created at times that are usually at least 5 or 10 minutes apart. I can see if I was calling the function multiple times per second or millisecond, because that'd kind of "breaks" the seed. But these are showing up at extended time spans. What else could be causing this?
Duplicate seed issue?
It might be better defining r as static so that it is initialised once when first invoked. Refer to this answer Random integer in VB.NET
The Random constructor takes an Integer as its parameter which is 32-bits. As spencer7593 said, with only 16 bits, you're repeating the sequence every 6.5ms. Try:
Dim r As New Random(Date.Now.Ticks And &HFFFFFFFF)
However, this will do the same thing:
Dim r As New Random()
Better yet, don't create a new Random object each time:
Private Static r As New Random()
Public Shared Function RandomNumber(MaxNumber As Integer, Optional MinNumber As Integer = 0) As Integer
...
Return r.Next(MinNumber, MaxNumber)
End Function
Q: What else could be causing this?
A: It could be happening purely by random. Random numbers are just that: random. At any point in time, whether its seconds or hours away from another point in time, its just as likely for a number to appear as any other number. There is no guarantee that a number won't be repeated.
On the other hand, it looks like your seed value is only on the order of 16 bits. And that's like a total of 65,536 possibilities. There's 10,000 ticks in a millisecond, so ever 6.5 milliseconds you have the possibility of reusing the same seed.
It's not at all clear whether the VB Random is using some other kind of entropy beyond that seed or not. (But gathering entropy for inclusion would slow down the initialization, so it may not be, as a performance consideration.)
According the docs, creating two Random objects using the same seed value results in Random objects that create duplicate sequences of unique numbers.
http://msdn.microsoft.com/en-us/library/ctssatww.aspx
I think that answers the question why it is happening.
I guess the next question is why do you need to instantiate a new Random object? If you need multiple objects, then instantiating several of them, but making sure you are using a different seed value for each one would be one approach.
But before you go there, I recommend you consider using just one Random. Calls to get random numbers can be serviced from an existing Random, rather than creating a new one every time you need a random number.
Try it another way:
Public Function RandomNumber2(ByVal MaxNumber As Integer, Optional ByVal MinNumber As Integer = 0) As Integer
' Initialize the random-number generator.
Randomize()
' Generate random value between MaxNumber and MinNumber.
Return CInt(Int((MaxNumber * Rnd()) + MinNumber))
End Function
See Randomize Function (Visual Basic) for more details. Hope this helps.

Copy contents of returned SQL Query row to a Listbox or Array

tl;dr Method of copying contents of an SQL query to an array or a string or a listbox. Single columns, multiple rows.
Working on a small project for some extra course credit.
Developing it currently in Visual Studio 2010.
Essentially is an interactive menu where users select items and can add them to an inbuilt
list and it will calculate total nutritional information and costs etc..
I'm having an issue however. When the user reaches the order builder page they can select the type of item they wish to buy.
E.G.
Beef
Clicking this should then populate a list box with all the related items.
I'm hoping to do this via a database connection.
I currently have an embedded database.
Their where 2 ways I've tried to do this but both proved unsuccessful or perhaps I'm just doing it wrong.
First method.
Dim index As Integer = 0
Dim length As Integer = adapter.productscounter()
' Small query that works out total number of rows.
For index = 0 To length
ListBox1.Items.Add(adapter.SelectBeef(index))
Next
This gives me the error:
There is no row at position 0.
which I seem unable to solve. The query runs upon trial execution and theirs something their.
Index out of range exception
The other method I was attempting was similar code however using an array and then copying the contents of that into the listbox.
Dim index As Integer
Dim test(5)
Dim length As Integer = adapter.productscounter()
Dim counter As Integer
For index = 0 To length
test(index) = adapter.SelectChicken()
counter = counter + 1
Next
For counter = 0 To length
ListBox1.Items.Add(test(index))
Next
Generates:
Argument nullexception
Value cannot be null.
Parameter name: item.
In Visual Basic, the standard is to start a list with element 1, not element 0. You could try:
For index = 1 To length
ListBox1.Items.Add(adapter.SelectBeef(index))
Next
Though of course, I have no insight in what the SelectBeef method does.

Improve this ugly piece of code

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