just a quick question about an issue I am having in vb.
I need to create an array, then iterate through a string, adding the string value to the array only is it does not already exist, to produce an array of unique values.
The issue I am having is with the array.length operation - on a array that is nothing I am unable to retrieve the array length (0), as such I am unable to redimensionalise the array to be array length (must be array length as arrays are indexed from 0), providing me the ability to add the new value to the array and then continue the loop until all values have been checked to see if they exist within the array (using contains) and the value contain only unique values..
Been nipping my head for hours :)
Thanks
Martin
I must assume that you are working with VB.Net, so I will answer accordingly.
The answer you are literally looking for is:
Public Function GetUniqueChars(text As String) As Char()
Dim uniqueChars() As Char
ReDim uniqueChars(0)
If String.IsNullOrEmpty(text) Then Return uniqueChars
uniqueChars(0) = text(0)
For Each c In text.Substring(1)
If Not uniqueChars.Contains(c) Then
ReDim Preserve uniqueChars(uniqueChars.Length)
uniqueChars(uniqueChars.Length - 1) = c
End If
Next
Return uniqueChars
End Function
However, starting with Net 3.5 you can use LINQ to reduce this function to one line:
uniqueChars = text.Distinct().ToArray
It's been literally years since I last programmed in VB6 (was my favorite language at one time). That being said, I recall finding the length of an array using UBound() like:
iLength = UBound(myArray)
I also remember using that number to redeclare my array to add another value (i.e. dynamic arrays) using something like:
ReDim Preserve myArray(iLength)
Perhaps that will inspire a solution for your loop.
Related
Currently I have these data inside my textfile.txt:
I want to split them up into a 2D array when a comma is met. How do I do so?
This is my code for now. Please do point out the mistakes I made.
Dim priceArray(5, 2) As String
Dim i, j As Integer
Dim data As String
Dim oilpriceFile As System.IO.StreamReader
oilpriceFile = New System.IO.StreamReader("C:\Users\zack\OneDrive\Desktop\oilprice.txt")
For i = 0 To UBound(priceArray)
data = oilpriceFile.ReadLine()
For j = 0 To UBound(priceArray)
priceArray(i, j) = data.Split(",") //there's an error stating "value of type string() cannot be converted into string"
j = j + 1
Next
i = i + 1
Next
There are several things you are doing incorrectly.
First turn on Option Strict in the Project Properties and also under Options on the Tools menu.
You do not declare the increment variables used in For loops.
StreanReader needs to be disposed. You should know this because you always check the documentation before you use an unfamiliar framework class. When you do this you will see a Dispose method. When you see this, it needs to be called when you are through with using it. See Using...End Using.
You don't have to deal with Dispose if you use File.ReadAllLines(). This returns an array of the lines in the file.
A For loop increments the value of the first variable automatically. You do not increment it explicitly or you will skip values.
You have defined a 2 dimensional array. When you call UBound on the array, which dimension are you calling it on?? You must indicate which dimension you are asking about.
UBound(priceArray, 1) and UBound(priceArray, 2)
Where 1 and 2 designate the dimension you are getting.
This is the old vb6 way to get this value. The .net framework provides GetUpperBound() for the same purpose. This method uses the .net zero based rank where GetUpperBound(0) will return upper bound of the first dimension of the array.
The next problem is the use of Spilt. Split returns an array of strings and you are trying to assign it to a single string. It takes a Char as a parameter. You have passed a String. To tell the compiler that you intend a Char follow the String by a lower case c.
A side note. // is the comment indicator in C#. In vb.net use the single quote '.
Private Sub OPCode()
Dim priceArray(5, 2) As String
Dim lines = File.ReadAllLines("C:\Users\zack\OneDrive\Desktop\oilprice.txt")
For i = 0 To priceArray.GetUpperBound(0)
Dim data = lines(i)
Dim splits = data.Split(","c)
For j = 0 To priceArray.GetUpperBound(1)
priceArray(i, j) = splits(j)
Next
Next
End Sub
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"
Are there any problems with iterating over a dictionary in the following manner?
Dim dict As New Dictionary(Of String, Integer) From {{"One", 1}, {"Two", 2}, {"Three", 3}}
For i = 0 To dict.Count - 1
Dim Key = dict.Keys(i)
Dim Value = dict.Item(Key)
'Do more work
dict.Item(Key) = NewValue
Next
I have used it a lot without any problems. But I recently read that the best way to iterate over a dictionary was using a ForEach loop. This led me to question the method that I've used.
Update: Note I am not asking how to iterate over a dictionary, but rather if the method that I've used successfully in the past is wrong and if so why.
Are there any problems with iterating over a dictionary in the following manner?
Yes and no. Technically there's nothing inherently wrong with the way you're doing it as it does what you need it to do, BUT it requires unnecessary computations and is therefore slower than simply using a For Each loop and iterating the key/value-pairs.
Iterating keys, then fetching value
The Keys property is not a separate collection of keys, but is actually just a thin wrapper around the dictionary itself which contains an enumerator for enumerating the keys only. For this reason it also doesn't have an indexer that lets you access the key at a specific index like you are right now.
What's actually happening is that VB.NET is utilizing the extension method ElementAtOrDefault(), which works by stepping through the enumeration until the wanted index has been reached. This means that for every iteration of your main loop, ElementAtOrDefault() also performs a similar step-through iteration until it gets to the index you've specified. You now have two loops, resulting in an O(N * N) = O(N2) operation.
What's more, when you access the value via Item(Key) it has to calculate the hash of the key and determine the respective value to fetch. While this operation is close to O(1), it's still an unnecessary additional operation compared to what I'm talking about below.
Iterating key/value-pairs
The dictionary already has an internal list (array) holding the keys and their respective values, so when iterating the dictionary using a For Each loop all it does is fetch each pair and put them into a KeyValuePair. Since it is fetching directly by index this time (at a specific memory location) you only have one loop, thus the fetch operation is O(1), making your entire loop O(N * 1) = O(N).
Based on this we see that iterating the key/value-pairs is actually faster.
This kind of loop would look like (where kvp is a KeyValuePair(Of String, Integer)):
For Each kvp In dict
Dim Key = kvp.Key
Dim Value = kvp.Value
Next
See here:
https://www.dotnetperls.com/dictionary-vbnet
Keys. You can get a List of the Dictionary keys. Dictionary has a get accessor property with the identifier Keys. You can pass the Keys to a List constructor to obtain a List of the keys.
It cites an example similar to yours:
Module Module1
Sub Main()
' Put four keys and values in the Dictionary.
Dim dictionary As New Dictionary(Of String, Integer)
dictionary.Add("please", 12)
dictionary.Add("help", 11)
dictionary.Add("poor", 10)
dictionary.Add("people", -11)
' Put keys into List Of String.
Dim list As New List(Of String)(dictionary.Keys)
' Loop over each string.
Dim str As String
For Each str In list
' Print string and also Item(string), which is the value.
Console.WriteLine("{0}, {1}", str, dictionary.Item(str))
Next
End Sub
End Module
stackoverflowers.
I am basically wondering if anyone has got a tip on how I should go on about this. I am already serializing regular primitive types using the BinaryWriter (by length prefixing byte arrays).
Normally I would just add another entity to the primitivetypes dictionary that I have, but I want to be able to serialize an indefinite jagged array; where it doesn't matter if it's a System.Byte[][] or System.Byte[][][][][][]
I would need a way to prefix all the arrays' lengths and also a way to tell the deserializer how many arrays of arrays there are.
I am just thinking of a good way to do this, but I have been trying all day.
Just a snippet of what I have come up with (it's not complete at all, but maybe you get my conceptional approach);
Private Sub WriteArray(Writer As BinaryWriter, Type As Byte, Array As Array)
Writer.Write(Array.GetLength(0)) 'Writing length so we know how many arrays there are.
Dim Current As Type = Array.GetType() 'Getting the type of the (jagged-)array
If Current.HasElementType AndAlso Current.GetElementType.IsArray Then 'Checking whether it's a jagged-array.
Writer.Write(True) 'Writing true; which represents that it's a jagged-array.
For Each A As Array In Array
WriteArray(Writer, Type, A) 'Looping through the same method until...
Next
Else '... It's not jagged. -- This way is problematic; most of the times there are multiple jagged-arrays.
Writer.Write(False) 'Not jagged.
Select Case Type
Case 0 '0 = byte, I use other bytes for strings/integers/etc.
For Each Obj As Byte In Array
Writer.Write(Obj) 'Because it's not jagged we can finally write the bytes
Next
End Select
End If
End Sub
Hope to see you take part in the discussion, and that we might come up with a solution.
Dim A As Collection
Set A = New Collection
Dim Arr2(15, 5)
Arr2(1,1) = 0
' ...
A.Add (Arr2)
How can I access the Arr2 through A? For example, I want to do the following:
A.Item(1) (1,1) = 15
so the above would change the first element of the first two-dimensional array inside the collection...
Hmmm...the syntax looks legal enough without having VBA in front of me. Am I right that your problem is that your code "compiles" and executes without raising an error, but that the array in the collection never changes? If so, I think that's because your A.Item(1) might be returning a copy of the array you stored in the collection. You then access and modify the chosen element just fine, but it's not having the effect you want because it's the wrong array instance.
In general VBA Collections work best when storing objects. Then they'll work like you want because they store references. They're fine for storing values, but I think they always copy them, even "big" ones like array variants, which means you can't mutate the contents of a stored array.
Consider this answer just a speculation until someone who knows the underlying COM stuff better weighs in. Um...paging Joel Spolsky?
EDIT: After trying this out in Excel VBA, I think I'm right. Putting an array variant in a collection makes a copy, and so does getting one out. So there doesn't appear to be a direct way to code up what you have actually asked for.
It looks like what you actually want is a 3-D array, but the fact that you were tyring to use a collection for the first dimension implies that you want to be able to change it's size in that dimension. VBA will only let you change the size of the last dimension of an array (see "redim preserve" in the help). You can put your 2-D arrays inside a 1-D array that you can change the size of, though:
ReDim a(5)
Dim b(2, 2)
a(2) = b
a(2)(1, 1) = 42
ReDim Preserve a(6)
Note that a is declared with ReDim, not Dim in this case.
Finally, it's quite possible that some other approach to whatever it is you're trying to do would be better. Growable, mutable 3-D arrays are complex and error-prone, to say the least.
#jtolle is correct. If you run the code below and inspect the values (Quick Watch is Shift-F9) of Arr2 and x you will see that they are different:
Dim A As Collection
Set A = New Collection
Dim Arr2(15, 5)
Arr2(1, 1) = 99
' ...
A.Add (Arr2) ' adds a copy of Arr2 to teh collection
Arr2(1, 1) = 11 ' this alters the value in Arr2, but not the copy in the collection
Dim x As Variant
x = A.Item(1)
x(1, 1) = 15 ' this does not alter Arr2
Maybe VBA makes a copy of the array when it assigns it to the collection? VB.net does not do this. After this code, a(3,3) is 20 in vba and 5 in vb.net.
Dim c As New Collection
Dim a(10, 10) As Integer
a(3, 3) = 20
c.Add(a)
c(1)(3, 3) = 5
I recently had this exact issue. I got round it by populating an array with the item array in question, making the change to this array, deleting the item array from the collection and then adding the changed array to the collection. Not pretty but it worked....and I can't find another way.
If you want the collection to have a copy of the array, and not a reference to the array, then use the array.clone method:-
Dim myCollection As New Collection
Dim myDates() as Date
Dim i As Integer
Do
i = 0
Array.Resize(myDates, 0)
Do
Array.Resize(myDates, i + 1)
myDates(i) = Now
...
i += 1
Loop
myCollection.Add(myDates.Clone)
Loop
At the end, myCollection will contain the accumulative collection of myDates().