Pass range into an array and retrieve value - arrays

I seem to have a fairly simple problem which is giving me grief. The following code throws a 'Subscript out of range' error. Therefore, I assume that the array is not being populated? Can anyone spot the glaring hole in what am I doing wrong...?
Dim p() As Variant
p = Sheet4.Range("G20:G29")
Sheet4.Select
Range("R2") = p(0)
ps G20:G29 contain strings, no blank cells

Hold it. Figured out the syntax. Forgot the Variant array was multidimensional
'First item
Range("R2") = p(1, 1)
'Second item
Range("R2") = p(2, 1)

Related

I'm having problems assigning values to an array I created in vb. Seems really simple but I can't figure out what I'm doing wrong

So I created an n x 2 array to hold some figures I would be inputting at runtime. Then I went ahead to add code which would add the values to the array. I wanted to know if the array was populated properly so I added code to populate a listbox with each figure inputted. However I don't think the array is populating as I think it should because the listbox isn't updating. The relevant code section is below;
N_secteion = txtNumSections.Text
Dim input_info(,) As Double = New Double(N_secteion - 1), 1) {}
Dim intIndexGrade As Integer
Dim intIndexLength As Integer
For intIndexGrade = 1 To N_secteion - 1
input_info(intIndexGrade - 1, 0) = Grade(intIndexGrade)
ListBox1.Items.Add(input_info(intIndexGrade - 1, 0).ToString())
Next
Grade(intIndexGrade) extracts values from another listbox on the form as the loop continues. This doesn't seem to be a problem since I've monitored it and it seems to pick up these values just fine. I would really appreciate some help. Been at this for too long.
N_secteion = txtNumSections.Text
Dim input_info(,) As Double = New Double((N_secteion - 1), 1) {}
Dim intIndexGrade As Integer
Dim intIndexLength As Integer
For intIndexGrade = 1 To N_secteion
input_info(intIndexGrade - 1, 0) = Grade(intIndexGrade)
ListBox1.Items.Add(input_info(intIndexGrade - 1, 0).ToString())
Next
So the issue was I wasn't looping through intIndexGrade enough times. Once this was corrected, the ListBox began to populate with all the values of the array variable because it only did so once the loop was complete.

remove duplicate rows in array

I have a question about arrays. First let me show you the code that I have got:
Dim TopStud() As Variant
TopStud = Range("A1", Range("A2").End(xlDown).End(xlToRight))
'code that removes duplicate values in array
'end code
Worksheets.Add
Range(ActiveCell, ActiveCell.Offset(UBound(TopStud, 1) - 1, UBound(TopStud, 2) - 1)).Value = TopStud
The code above shows how I get a range of values in an array. I want to remove duplicate values that is been saved in this array into a new worksheet. See above. Is there a easy way to do this?
I want the following result:
If you want to remove the whole record for any duplicates in the current column:
Cells.RemoveDuplicates Columns:=ActiveCell.Column,Header:=xlYes

Converting one type of array to another

I have one 2D array that I'm wanting to populate into another. I got a bit mixed up between lists, dictionaries and simple arrays, so I'm thinking I have two different types or array. The edited code with some attempts and resulting errors:
Dim _inifile As String = "C:\Users\Steve\Scripts\Projects\IniRecEdit\Tests\insrow.ini"
Public IniLines() As String = File.ReadAllLines(_inifile)
Public _ini(IniLines.Length - 1)() As String
For I As Integer = 0 To IniLines.Length - 1
_ini(I) = IniLines(I).Split("="c)
Next
'.....code
Dim _tempini(Lines.Length - 1, SQSIZE - 1) As String
Dim tagrow As Integer
Dim tagcol As Integer
Dim taglist() As String
Dim RowSel As Integer = 1
Dim cControl As Control
For Each cControl In Me.Panel1.Controls
If cControl.Tag <> Nothing Then
taglist = Split(cControl.Tag, ","c)
If taglist(0) = "Cell" Then
tagcol = Val(taglist(1))
tagrow = Val(taglist(2))
If tagrow <= RowSel Then
If tagcol = 0 Then
_tempini(tagrow, tagcol) = _ini(tagrow)(tagcol)
Debug.WriteLine("Lower or equal then Selected row. 1st Column. From ini to row:" & tagrow)
' EDIT etc etc... more code here
Next cControl
' DIFFERENT CODE TRIED AT THIS STAGE to transfer from one array to the other:
ReDim _ini(Lines.Length - 1)
For countrow As Integer = 0 To _tempini.GetLength(0) - 1
For countcol As Integer = 0 To _tempini.GetLength(1) - 1
_ini(countrow) = _tempini(countrow)._tempini(countcol)
Next
Next
' Produces: Error 2 Number of indices is less than the number of dimensions of the indexed array.
ReDim _ini(Lines.Length - 1)
For countrow As Integer = 0 To _tempini.GetLength(0) - 1
For countcol As Integer = 0 To _tempini.GetLength(1) - 1
_ini(countrow)(countcol) = _tempini(countrow, countcol)
Next
Next
'Produces: Additional information: Object reference not set to an instance of an object.
As I say, I'm not even sure whether I'm using lists of not for "_ini" The Locals Window on Visual Studio shows the vars as:
_ini is "String()()"
_tempini is "String(,)"
I'm increasingly aware I need to go back to basics with vb and learn all of the concepts involved. However, a quick assist would let me try and complete this thing I'm knocking together with blu-tack and string :)
You are actually using arrays. The declaration of a list would look like this:
Dim myList As New List(Of String)()
A list has always one dimension and the initial size of a list is 0 (unless you pass an enumeration to the constructor). You have to add items one by one using the Add method. This makes the list grow dynamically.
I assume that your ini-file looks like this
key1 = value1
key2 = value2
key3 = value3
Also I assume that the value does not contain an equal sign, otherwise I would look for the first instance of an equal sign an split the string there "manually".
Using a two dimensional array seems not appropriate in order to store key-value-pairs. There is a handy struct KeyValuePair(Of TKey, TValue) you could use if you need a string or a list. Using an array of KeyValuePairs has the advantage that you will not have to deal with two-dimensional arrays.
However, if you want to look up values for given keys, the Dictionary(Of TKey, TValue) is more appropriate, as it is optimized for a very fast lookup.
Dim dict As New Dictionary(Of String, String)
For i As Integer = 0 To IniLines.Length - 1
Dim parts = IniLines(i).Split("="c)
dict.Add(parts(0).Trim(), parts(1).Trim())
Next
Then you can retrive a value like this
Dim value As String
If dict.TryGetValue("some key", value) Then
' Use the value
Else
' Sorry, key does not exist
End If
Or if you are sure that the key you are looking for really exists, you can simply write
value = dict("some key")
TLDR
(1) Working with Arrays...
(2) Why are you getting "Object reference
not set to an instance of an object."
(3) Multidimensional Arrays
and Arrays of Arrays
(4) Why are you getting "Number of indices is
less than the number of dimens..."
(5) What I would do if I were
you...
Working with Arrays : Declaration(1) and Instanciation(2)
(1) declaration is when you write the name of a variable and its type. You can then access the object reffered by this variable in your code.
(2) we can talk about (variable/object) instanciation when an actual object has been created and registered somewhere in the memory. You can then manipulate that object via something that points to that object : the variable.
A declaration doesn't always mean the actual object is created. (I won't dig in ValueType and ReferenceType, sorry guys, I'm just cutting corners while explaining things without the complex verbose of strict dotNet)
Arrays declaration is different from instanciation :
Dim myArray As String()
This is just a declaration. Your Array is Nothing at this time and therefore contains nothing. Calling something like myStringVariable = myArray(0) will throw an Exception (Object reference not set to an instance of an object) meaning your array declaration points to nowhere because you haven't created an object of type String() and made your Array declaration points to that object.
Dim myArray() As String
This is the same declaration as above. Which one is correct ? I don't know and it's a matter of taste. VB is known for letting you do things the way you want. If you're good, you'll be able to use the one or the other like breathing. In fact, I really don't care.
Dim myArray As String() = new String() {}
This is just an instanciation of your array. Your array is registered in memory, but is of length 0 (zero) and contains no single item (Length = 0).
Remeber the declaration above ? (myArray() As String or myArray As String()) In fact, there is a difference in the two syntax when you talk about instanciation :
you can write : Dim myArray(3) As String
but you can't write : Dim myArray As String(3)
The later throws an exception. The variable type (String) cannot have indexes delimiters.
Dim myArray(3) As String '...
... also declares an Array and instanciate it with 4 predefined items like below.
Dim myArray As String() ' this is a declaration.
Redim myArray(3) ' this is an instanciation with a defined size.
This is one another way to define the number of items in your array using the Redim keyword. You have 4 "slots" in that Array from index [0] to index [3]. Your array looks like this :
[0][""]
[1][""]
[2][""]
[3][""]
.
Dim myArray As String = New String(3) {}
This is the same as above, but in one line : you're declaring and instanciating an Array of String that contains 4 items of type String which are all empty string (they are not Nothing)
Dim myArray() As String = { "One", "Two", "Three", "Four" }
This is a one line declaration, instanciation and items-setting :
[0]["One"]
[1]["Two"]
[2]["Three"]
[3]["Four"]
Redim
Redim is used to redifine the number of items in an Array. One dimensional arrays just behaves the way they are supposed to.
Suppose you have defined your array as :
Dim myArray() As String = { "One", "Two", "Three", "Four" }
Obviously, you have four String. Then use Redim :
Redim myArray(6) ' to get 7 items ..!
This is what you get :
[0][""]
[1][""]
[2][""]
[3][""]
[4][""]
[5][""]
[6][""]
You can resize an Array, but to keep the items inside, you'll have to use a keyword : Preserve
Redim Preserve myArray(6)
myArray(4) = "Five"
'myArray(5) = "Six"
myArray(6) = "Seven"
Because I've commented out the setting of the sixth item, the array will look like this :
[0]["One"]
[1]["Two"]
[2]["Three"]
[3]["Four"]
[4]["Five"]
[5][""] ' Empty String.
[6]["Seven"]
What happens if you Redim Preserve the Array above twice while killing some items ?
Redim Preserve myArray(2)
Redim Preserve myArray(6)
' You get :
[0]["One"]
[1]["Two"]
[2]["Three"]
[3][""]
[4][""]
[5][""]
[6][""]
You've lost items 3..6 ! Don't expect to get them back.
Public _ini(IniLines.Length - 1)() As String
What this code does ? Writing Dim myArray(3)() As String ("3" could be any positive or null integer) results in the following Array structure :
[0][Nothing]
[1][Nothing]
[2][Nothing]
[3][Nothing]
Why are you getting Nothing but not a bidimensional array ? Not even empty strings ?
It's because the declaration above creates a ONE dimensional Array of Array (of the type defined, String here)
VB.net allows you to declare an Array of Array this way (I don't know yet if C# can - but I really don't care : I never, and will never use this type of syntax) This is not a bidimensional Array. To define the values, you'll have to create each instance of the contained Array per item (or line) and assign it to your Array.
In the declaration above, while you have explicitely initialized your Array, its contents [0..3] are still simple declarations that points to no instance of anything, thus, explaining the "Nothing" thing.
Then upon getting datas from your file, your _ini variable could look like this, depending on the content of your file (that's why I asked you to provide a sample of the content of your file) :
[0][ ["Key0" | "Value0"] ]
[1][ ["Key1" | ""] ]
[2][ [""] ]
[3][ ["Key3" | "Value3" | "OtherData" | "MoreData" | "EvenMoreData"] ]
...
Reading a (String) value can be done like follows :
Dim myStringVariable As String = _ini(0)(1) ' Gets "Value0"
=> Be carefull when you use Array or Arrays along with
multidimentional arrays. They are not the same !
Let's pick the code below :
ReDim _ini(Lines.Length - 1)
For countrow As Integer = 0 To _tempini.GetLength(0) - 1
For countcol As Integer = 0 To _tempini.GetLength(1) - 1
_ini(countrow)(countcol) = _tempini(countrow, countcol)
Next
Next
Your _tempini is a bidimentional array of String.
Your _ini is an one dimensions array of array of String.
Scan this line :
_ini(countrow)(countcol) = _tempini(countrow, countcol)
1) _ini(countrow) contains Nothing (whatever the value of countrow) = contains NO Array (of String) because you've just called ReDim _ini(Lines.Length - 1) right before the For loop.
2) Therefore your assignation _ini(countrow)(countcol) = ... is the same as running the following at runtime :
Nothing(countcol) = _tempini(countrow, countcol)
Nothing is not an Array, so the (countcol) indexing has no meaning : you're indexing an item value to an object expected to be an Array that doesn't even exist.
That's why you get the Exception Object reference not set to an instance of an object.
Multidimensional Arrays / Redim / Preserve
The declaration and instanciation above still apply !
Public myBiDiArray(,) As String ' Declaration ONLY : bi dimensional array
Public myBiDiArray(2,1) As String ' Declaration AND Instanciation
Public myBiDiArray(,) As String = New String(2, 1) {} ' Dec. + Inst.
Public myBiDiArray(,) As String = _
{ {"One", "Un"}, {"Two", "Deux"}, {"Three", "Trois"} }
' ^^ Declaration + Instanciation + Item Setter
Public myBiDiArray(,) As String = _
New String(,) { {"One", "Un"}, {"Two", "Deux"}, {"Three", "Trois"} }
' ^^ Declaration + Instanciation + Item Setter
Let's have a Bidimensional Array of String with rows and columns...
Dim myArray(2, 1) As String ' 3 lines, two columns
This array contains the following items :
Line 0 = [""][""]
Line 1 = [""][""]
Line 2 = [""][""]
Now you want to resize the Array and use the Redim (only) Keyword...
Redim myArray(5, 3) ' ...
...which would set the lines to 6 and columns to 4 :
' Array Size = 6, 4
Line 0 = [""][""][""][""]
Line 1 = [""][""][""][""]
Line 2 = [""][""][""][""]
Line 3 = [""][""][""][""]
Line 4 = [""][""][""][""]
Line 5 = [""][""][""][""]
That's great !
Now let's set a value in Row(0) and Column(0)
myArray(0, 0) = "Cell,0,0"
' You get :
Line 0 = ["Cell,0,0"][""][""][""]
Line 1 = [""] [""][""][""]
Line 2 = [""] [""][""][""]
Line 3 = [""] [""][""][""]
Line 4 = [""] [""][""][""]
Line 5 = [""] [""][""][""]
But what happens with the Preserve Keyword ?
Redim Preserve myArray(3, 2) ' 4 lines and 3 columns
You get an exception : 'ReDim' can only change the rightmost dimension.
But you must know that this Exception is not handled by the debugger unless you explicitly write a Try/Catch routine that encapsulates the Redim Preserve Code. Otherwise, the application just exit the method/function containing that piece of code without any warning and your Array remains untouched !
To redim a multidimensional array (not only the last dimension) while preserving its content, you must create a new array of the desired size and copy the content of the former one to this new array, then make your former array point to that new one...
_ini(countrow) = _tempini(countrow)._tempini(countcol)
I'm not aware of such syntax in VB.Net. Perhaps you got this from another language or a Reference that adds extensions to Arrays (like System.Linq) which extends (?) the members of an Array to itself for some purpose.., but I'm not aware of that.
It looks to me like a simple syntax error. That's why you get the Exception Number of indices is less than the number of dimensions of the indexed array.
To get the (string) value of a bidimensional array, just write :
myStringValue = _tempini(countrow, countcol)
The error warning is not pointing to the systax error though : the debugger stopped at the closing backet and discarded the remaining piece of text :
_ini(countrow) = _tempini(countrow ... ' <- compiler expects a comma here.
Writing this will produce the following error :
myStringValue = _tempini(countrow, countcol)._tempini(countcol)
Compiler Error : 'tempini' is not a member of 'String'.
Again, don't mess with bidimentional arrays and arrays of arrays : Writing...
_ini(countrow) = _tempini(countrow, countcol) ' ...
... has no meaning ! Even if the syntax looks correct, the above code actually tries to assign a String value to an Array(Of String) variable, which will throw an InvalidCastException.
I'll be honest : Arrays are a great bit of fun with fixed/predefined size/dimensions. From the moment I'm working with unknown number of elements/items at runtime, I leave Arrays behind.
What I would do ?
Oh! just the same Olivier Jacot-Descombes advised you to do :) No surprise, a Dictionary is very handy in your situation.
I would also enable those two :
Option Strict On
Option Explicit On
and disable this one : Option Infer Off.
Another PERSONAL advice is to avoid using the Tag property of a control. This tag is of type Object.
some Casting should be done to get a cleaner code.
Controls are usual components you'll use anywhere. One day, I've imagined I have tied an object of type 'String' to a specific control while it actually contained a Date or even Nothing. Why ? Because I confounded two different projects and lost track of the whole thing.
pass your code to a colleague or a friend, and be sure he/she'll ask you to document it about what you've done with the Tag Property... that simply means no one want to discover the obscure underlying logic of a code involving Objects. People like to deal with strongly typed variables, easy to debug and fast performing at runtime.
Also avoid the use of Val(). Val() is very handy because it can almost convert anything. However, because of this nice feature, it adds a workload on the CPU. Again, if you know you're dealing with Int32 or Double, then use Int32.Parse() or Double.Parse() but not Val(). Most misuse of Val() on the web are related to type inference and implicit casting. Debugging such kind of glitches are a real pain. Use Val only when you have no alternative.
I think you now have almost everything you need to get your code working with little changes. I have not provided the code for the changes though :
I don't understand where your samples of code lands in your application logic
I don't know what kind of datas you're manipulating
I don't even know what's the purpose of the code.
^^ So I just addressed two things :
why you're getting errors...
since you're new to VB, I just provided a custom explanation of Arrays.
Was that the (not so) quick assist you were looking for ? I don't know. That's the best I could give right now.
OK. People have been really helpful, but not really up to totally recoding at this stage. I did find a work around, however. It aint pretty and probably is really bad practice! However, got it up and running with the code I've got.
Essentially, I just did the process in reverse, constructed a line from the components in _tempini, delimited by a "=" and then just re-ran the original procedure for populating the _ini array in the first place. A total work-around, but hope will help any other amateurs like me getting stuck in these early stages.
However, I would say follow these fellas advice! Look into list, dictionaries etc before starting your code.
The fix:
ReDim _ini(((_tempini.Length) / 2) - 1)
Dim _newiniline As String
_newiniline = ""
For countrow As Integer = 0 To (((_tempini.Length) / 2) - 1)
For countcol As Integer = 0 To 1
_newiniline = _newiniline & _tempini(countrow, countcol)
If countcol = 0 Then
_newiniline = _newiniline & "="
End If
_ini(countrow) = _newiniline.Split("="c)
Next
_newiniline = Nothing
Next

Index was outside the bounds of the String array

I have this test code in VB:
Dim st As New ScheduledTasks("\\webserver")
Dim tasknames() As String = st.GetTaskNames
ListBox1.Items.Add(tasknames(1))
st.Dispose()
When i run it i get an error on line:
ListBox1.Items.Add(tasknames(1))
Index was outside the bounds of the array.
Does anyone have any suggestions what im doing wrong?
tasknames must contain at least 2 items for your code to work. (Note that arrays are 0-based, so you start counting at 0)
You have to check whether it really contains that amount:
If tasknames.Length >= 2
Then
ListBox1.Items.Add(tasknames(1))
End If

Assigning values dynamically in a classic ASP FOR EACH loop

I'm trying to assign a value to a series of variables inside a FOR EACH loop, but keep getting a "Type mismatch" error.
personOrder = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15" 'order items displayed onscreen
personArray = split(personOrder, ",")
For each i in personArray
imageArray(i) = objContentXML.selectSingleNode("/page/profile" & i & "/image").text
Next
Note: I've made the var personOrder a list because in the future items might change order.
I think there's a problem with assigning array values in a FOR EACH loop. Annoyingly it works fine in a FOR loop, but I'm trying to future proof my code.
Apologies if this question is too stupid. I'm returning after 9 months of nappies and burping to the world of code (less nappies, more burping).
On which statement do you get this error? I can execute the following code without any error messages:
personOrder = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15" 'order items displayed onscreen
personArray = split(personOrder, ",")
dim imageArray
redim imageArray(uBound(personArray) + 1)
For each i in personArray
imageArray(i) = i
Next
You must create an array first and declare the number of items you want to put in it (use redim to change the dimensions).
I replaced your objectContentXML with a simple statement, for testing purposes.

Resources