Syntax request: "While" syntax for parsing arrays of JSON strings (VBA) - arrays

I am working to get my code to loop through arrays of JSON strings (of same format) until it reaches the end of the arrays (i.e., no strings left). I need the code to recognize it has reached the end by identifying that a certain identifier (present in every set) does not have additional information in the next array. So I believe I am looking for "while" syntax that says "while this identifier has content" proceed parsing the JSON according to the below code. My existing code works for array of strings for which I know the length - unfortunately the lengths are variable therefore I need flexible syntax to adjust with the lengths (i.e., "For 0 to n" doesn't work every time).
The JSON code I am parsing is in this format:
{"id":1,"prices":[{"name":"expressTaxi","cost":{"base":"USD4.50","fareType":
"time_plus_distance","cancelFee":"USD10.00","minimumAmt":"USD8.00","perMinute":"USD1.50",
"perDistanceUnit":"USD3.00"}}]
''note that this could have multiple arrays embedded. That is, from the "name" key to
''the "perDistanceUnit" key would all repeat, with different value pairs.
''The number of "name" to "perDistanceUnit" arrays is unknown.
Here the identifier structure I'd like to augment with some type of while loop (the "i" is the index number depending on the # of loop in the yet to be implemented "while" loop).
Json("prices")(i)("name")
So ideally looking for something like:
"While Json("prices")(i)("name") has information" then proceed on....
Please note again, everything works when I know the length -- just looking for a small syntax update, thank you! UPDATE: full code below:
Option Explicit
Sub getJSON()
sheetCount = 1
i = 1
urlArray = Array("URL1", “URL2”, “URL3”)
Dim MyRequest As Object: Set MyRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
Dim MyUrls: MyUrls = urlArray
Dim k As Long
Dim Json As Object
For k = LBound(MyUrls) To UBound(MyUrls)
With MyRequest
.Open "GET", MyUrls(k)
.Send
Set Json = JsonConverter.ParseJson(.ResponseText)
''[where I’d like some type of While statement checking into the below line for content]
Sheets("Sheet" & sheetCount).Cells(i, 1) = Json("prices")(i)("name")
Sheets("Sheet" & sheetCount).Cells(i, 2) = Json("prices")(i)("cost")("base")
i = i + 1
End With
sheetCount = sheetCount + 1
Next
End Sub

I'm not that familiar with the library you're using, bit it seems like it converts objects (items enclosed in { }) to dictionary objects, and array (things enclosed in [ ]) to Collections.
Depending on the structure of the parsed JSON, these objects may be nested: ie. one element in a dictionary may be a Collection(array).
Luckily for you both, of these object types have a Count property you can use to iterate over them (and the dictionary type also has a "keys" collection).
So to loop over each "price":
Dim i As Long, p As Object
For i = 1 To Json("prices").Count
Set p = Json("prices")(i)
Debug.Print p("name"), p("cost")("base"), p("cost")("fareType")
Next i

Related

VBA, How to pass in a string argument, convert to single character array and resize according to non-numerical values?

Ok,
To all those who may come across this question, this is a problem I have been banging my head against for the past two weeks and have made little or no progress, so any help would be extremely welcome.
Here's the set up; then I will follow with an excerpt of the code I have written:
I am writing a function to get a very specific formula for each file name in a given folder. This naturally requires me to write a program which can take string arguments (in this case, excel file names) from a very broad domain of possibilities and yield a very specific output based on some key -and highly unique- parameters. Hence, my function is bijective and the set of arguments and set of products are massive; therefore, I am in the process of writing a sub-process for this function which partitions the string argument, by character, into a corresponding array, remove all the unnecessary characters, concatenate the remaining characters into the output string, and then go through a series of checks to enforce whatever name-formula the file requires. For now, I am just focused on splitting the string into an array, removing all non-numeric characters and combining the remaining characters back into a single string.
Naturally, I have tried the split function, but to my knowledge VBA doesn't support the splitting of a string into single characters. So I have written the following code, which, admittedly, is a bit inelegant, but I think in principle must work. -It does not. Would someone kindly tell me why it doesn't, and make a recommendation for altering it.
Dim arr() As Variant
For i = Len(strArg) To i = 1
If IsNumeric(Mid$(strArg, i, 1)) = True Then
arr(i - 1) = Mid$(strArg, i, 1)
Else: arr(i - 1) = ""
End If
Next
newStr = Join(arr())
arr() always returns empty, so newStr is always "". Yet there are always numeric values in each string argument. -I can't imagine why I am getting this result. If I use ReDim arr(Len(strArg)), I get Len(strArg) number of " " back....
Thanks in advance to whomever may provide help.
Not sure why you need to split it into an array for this. Your description says you only want to have numeric characters returned in a new string variable. A function like this should work for you:
Function GetNumbers(ByVal arg_sText As String) As String
Dim i As Long
Dim sChar As String
Dim sNumbers As String
For i = 1 To Len(arg_sText)
sChar = Mid(arg_sText, i, 1)
If IsNumeric(sChar) Then sNumbers = sNumbers & sChar
Next i
GetNumbers = sNumbers
End Function
Then just call it in your code like this:
newStr = GetNumbers(strArg) 'Example: "ab1c2d" = "12"
Alternatively use a Regular Expression
Function NumOnly(s As String) As String
With CreateObject("VBScript.RegExp")
.Global = True
.MultiLine = False
.IgnoreCase = True
.Pattern = "[^0-9]+"
NumOnly = .Replace(s, "")
End With
End Function
As a further approach to the existing solutions, I'd like to demonstrate how to use the ►FilterXML() function and to check valid results.
The proposed function NumsOnly() consists only of three steps:
a) execute an XPath search upon xml content which has been created by getXML()
b) check valid results via procedure check
c) return the function result as new formed string
Coming close to the requirements in OP this includes also a way to
convert a string to a single character array (c.f. #JNevill 's comment to OP) and
to build a well-formed xml string as base for Filter.XML (see function getXML()) .
Main function NumsOnly()
Function NumsOnly(ByVal s As String) As String
'Purp: recognizes only numeric values in comparisons
'Note: no findings and single digit results are handled by proc check
Const xpath As String = "//*[.>0]" ' get only digits out of atomized characters
'a) execute XPath search upon xml content
Dim x: x = Application.FilterXML(getXML(s), xpath)
'b) check valid results
check x
'c) return only-nums string as function result
NumsOnly = x
End Function
Helper function getXML()
Extended udf based on Split string into array of characters?
Function getXML(ByVal s As String)
'Purp: return well-formed xml content string as base for FilterXML function
'1) atomize string elements into array
Dim buff() As String: buff = Split(StrConv(s, vbUnicode), Chr$(0))
ReDim Preserve buff(UBound(buff) - 1)
'2) return valid xml content string
getXML = "<ch><c>" & Join(buff, "</c><c>") & "</c></ch>"
End Function
Procedure check
As FilterXML returns findings of more than one element as a 2-dim array,
non-findings as Error 2015 and a single element as stand-alone value, it is necessary to distinguish between the returned var types:
Sub check(ByRef x, Optional ErrorResult As String = "")
'Purp: provide for correct xml result by checking var types
Select Case VarType(x)
Case vbError ' non-findings (Error 2015)
x = ErrorResult
Case Is >= vbArray ' 2-dim results (if more than 1 element)
x = Join(Application.Transpose(x), vbNullString)
'Case Else ' single element (here: digit, i.e. Double)
End Select
End Sub

Excel VBA: How to concatenate variant array elements (row numbers) into a range object?

I did research this question but could not find the specific answer I was looking for and am actually even more confused at present.
I created a macro that would run through rows on a sheet and run boolean checks on a number of cells in each row that looked for the presence or absence of specific values, or calculated the outcome of a specific inequality. On the basis of those checks, the macro may or may not pass the row number into a specific array. That part is working fine.
My issue is, now that I have the row numbers (stored in variant arrays) - I cannot figure out how to properly concatenate that data into a range and then take a bulk excel action on those items. What I'd like to do is create a range of those values and then delete all of those rows at once rather than looping through.
My macro is on my work computer, but here's something I wrote that should explain what I'm trying to do:
Sub Test()
Dim Str As String
Dim r As Range
Dim i, a As Integer
Dim Count As Integer
Dim RngArray()
Count = ThisWorkbook.Sheets("Sheet1").Cells(Rows.Count, "A:A").End(xlUp).Row
ReDim RngArray(Count)
a = 0
For i = 1 To Count
If Not i = Count Then
RngArray(a) = i
Str = Str & RngArray(a) & ":" & RngArray(a) & ", "
a = a + 1
ElseIf i = Count Then
RngArray(a) = i
Str = Str & RngArray(a) & ":" & RngArray(a)
a = a + 1
Else: End If
Next i
Set r = Range(Str)'Error Can Appear here depending on my concatenation technique
Range(Str).EntireRow.Delete 'error will always appear here
End Sub
I've combined a few steps here and left out any Boolean checks; in my actual macro the values in the arrays are already stored and I loop from LBound to UBound and concatenate those values into a string of the form ("1:1, 2:2, 3:3, ...., n:n")
The reason why I did this is that the rows are all over the sheet and I wanted to get to a point where I could pass the argument
Range("1:1, 2:2, 3:3, ..., n:n").EntireRow.Delete
I think it's clear that I'm just not understanding how to pass the correct information to the range object. When I try to run this I get a "Method Range of Object Global" error message.
My short term fix is to just loop through and clear the rows and then remove all of the blank rows (the macro keeps track of absolute positions of the rows, not the rows after an iterative delete) - but I'd like to figure out HOW to do this my way and why it's not working.
I'm open to other solutions as well, but I'd like to understand what I'm doing wrong here. I should also mention that I used the Join() to try to find a workaround and still received the same type of error.
Thank you.
After some experimentation with my dataset for the macro above, I discovered that it worked on small sets of data in A:A but not larger sets.
I ran Debug.Print Len(Str) while tweaking the set size and macro and found that it appears Range() can only accept a maximum of 240 characters. I still don't understand why this is or the reason for the specific error message I received, but the macro will work if Len(Str) < 240.
I'll have to loop backwards through my array to delete these rows if I want to use my present method...or I may just try something else.
Thanks to Andrew for his attention to this!

Array of 600+ Strings in excel VBA

I am doing a loop for each string in an array such that
filename = Array(file1,file2.....file600)
However VBA gets a compile error that is due to the array taking up 8 lines. As far as I am aware it only allows 1 line
(error says expected list or separator)
I am new to VBA sorry
You can escape new lines in VBA with _.
so your solution might look like
filename = Array("file1", _
"file2", _
"file3")
See How to break long string to multiple lines and If Statement With Multiple Lines
If you have 100's of names, however, you might be better off storing them in a worksheet and reading them in, rather than hard-coding them.
Should you strings in the array be actually "buildable" following a pattern (like per your examples: "file1", "file2", ...,"file600") then you could have a Function define them for you, like follows:
Function GetFileNames(nFiles As Long) As String()
Dim iFile As Long
ReDim filenames(1 To nFiles) As String
For iFile = 1 To nFiles
filenames(iFile) = "file" & iFile
Next
GetFileNames = filenames
End Function
which you'd call in your "main" code as follows
Sub main()
Dim filenames() As String
filenames = GetFileNames(600) '<--| this way'filenames' array gets filled with 600 hundred values like "file1", "file2" and so on
End Sub
The amount of code that can be loaded into a form, class, or standard module is limited to 65,534 lines. A single line of code can consist of up to 1023 bytes. Up to 256 blank spaces can precede the actual text on a single line, and no more than twenty-four line-continuation characters ( _) can be included in a single logical line.
From VB6's Help.
when programming, you don't build an array this big mannually, never.
either you store each multiline-string inside a Cell, and at the end you buid the array like this :
option Explicit
Sub ArrayBuild ()
Dim Filenames() 'as variant , and yes i presume when using multi files, the variable name should have an "s"
With Thisworkbook.sheets("temp") 'or other name of sheet
Max = .cells(.rows.count,1).end(xlup).row '=max number of rows in column 1
Filenames = .range( .cells(1,1) , .cells(Max,1)).value2 ' this example uses a one column range from a1 to a[max] , but you could also use a multi column by changing the second .cells to `.cells(Max, ColMax)`
end with
'do stuff
erase Filenames 'free memory
End Sub
An other way is to build an array like you build a house by adding one brick at a time, like this :
Dim Filenames() as string 'this time you can declare as string, but not in the previous example
Dim i& 'counter
For i=1 to Max 'same max as in previous example, adapt code plz...
redim Preserve Filenames (1 to ubound(filenames)+1) 'this is an example for unknown size array wich grows, but in your case you know the size (here Max, so you could declare it `Dim Filenames (1 to Max)` from the start, just wanted to show every option here.
Filenames(i) = Cells (i,1).value2 'for example, but can be anything else. Also i'm beeing lazy and did not reference the cell to its sheet, wich i'd never do in actual coding...
next i
EDIT i did re-read your Question, and it is even easier (basically because you ommited the bracets in your post and corrected it as comment...), use
user3598756 's code plz. I thought File1 is a variable, when it should be written as "File1" .
EDIT 2 why bother build and array where Filename(x)="Filex" anyway? you know the result beforehand

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

Vb.Net Sort 3x One Dimensional Arrays

I have 3 one dimensional arrays.
Each contains information that corresponds to the other 2 arrays.
e.g Array 1 contains a customer first name
Array 2 contains a customer last name
Array 3 contains the customer phone number.
This is not my actual example but is easiest to explain.
How do I sort all three arrays so that they are sorted in order by say customer last name.
If Mr Smith is sorted and has moved from position 10 to position 5 in the lastname array, I would expect his phone number and first name to also be in position 5 in the respective arrays.
I am dealing with arrays with 10,000's of items so I would like to avoid looping (my current method) as this is incredibly slow.
Hoping to use the array.sort methods.
Can someone help me?
Ok - So I have tried to use a new data Type but am still at a loss how I can instantly filter using this. Below is my sample code which has a couple of issues. If someone can resolve - it would love to learn how you did it.
The purpose of the code is to return an array containing grouped issues.
For simplicity I have assumed in the example that each constant found is an issue.
If an issue is found, combine it with other issues found on that same worksheet.
e.g The number 2 is found in both cells A1 and A2 on sheet 1. The array should return A1:A2.
If the issues are found in A1 on sheet 1 and A2 in sheet 2, two seperate array entries would be returned.
Test File and Code Here
Public Type Issues
ws_Sheet As Integer
rng_Range As String
s_Formula As String
s_Combined As String
d_ItemCount As Double
End Type
Sub IssuesFound()
Dim MyIssues() As Issues
Dim i_SheetCount As Integer
Dim s_Formula As String
Dim rng_Range As Range
Dim d_IssueCounter As Double
Dim s_SearchFor As String
Dim a_TempArray() As Issues
Dim d_InsertCounter As Double
d_IssueCounter = -1
' Loop All Sheets Using A Counter Rather Than For Each
For i_SheetCount = 1 To ActiveWorkbook.Sheets.Count
' Loop all Constants On Worksheet
For Each rng_Range In Sheets(i_SheetCount).Cells.SpecialCells(xlCellTypeConstants, 23)
If d_IssueCounter = -1 Then
' First Time and Issue Is Found, Start Recording In An Array
d_IssueCounter = d_IssueCounter + 1
ReDim MyIssues(0)
MyIssues(0).ws_Sheet = i_SheetCount
MyIssues(0).rng_Range = rng_Range.AddressLocal
MyIssues(0).s_Formula = rng_Range.Value
MyIssues(0).s_Combined = i_SheetCount & "#" & rng_Range.Value
MyIssues(0).d_ItemCount = 0
Else
' Going To Look For Issues Found On The Same Sheet with The Same Constant Value
s_SearchFor = i_SheetCount & "#" & rng_Range.Value
' HELP HERE: Need To Ideally Return Whether The Above Search Term Exists In The Array
' Without looping, and I want to return the position in the array if the item is found
a_TempArray = MyIssues 'Filter(MyIssues.s_Combined, s_SearchFor, True, vbTextCompare)
If IsVarArrayEmpty(a_TempArray) = True Then
' New Issue Found - Increase Counter By + 1
d_IssueCounter = d_IssueCounter + 1
' Increase The Array By 1
ReDim Preserve MyIssues(d_IssueCounter)
' Record The Information About The Constant Found. Sheet Number, Constant, Range, and also a combined string for searching and the array position
MyIssues(0).ws_Sheet = i_SheetCount
MyIssues(0).rng_Range = rng_Range.AddressLocal
MyIssues(0).s_Formula = rng_Range.Value
MyIssues(0).s_Combined = i_SheetCount & "#" & rng_Range.Value
MyIssues(0).d_ItemCount = 0
Else
' Get The Array Position Where Other Issues With The Same Worksheet and Constant are Stored
d_InsertCounter = a_TempArray.d_ItemCount
' Add The New Found Constant To The Range Already Containing The Same Constants on This Worksheet
MyIssues(d_InsertCounter).rng_Range = Union(rng_Range, Range(MyIssues(d_InsertCounter).rng_Range)).AddressLocal
End If
End If
Next
Next
End Sub
Function IsVarArrayEmpty(ByRef anArray As Issues)
Dim i As Integer
On Error Resume Next
i = UBound(anArray, 1)
If Err.Number = 0 Then
IsVarArrayEmpty = False
Else
IsVarArrayEmpty = True
End If
End Function
Sample Test File and Code Here
As suggested, you should not be using concurrent arrays at all. You should be defining a type with three properties and then creating a single array or collection of that type.
To answer your question though, there is no way to sort three arrays in concert but there is a way to sort two. What that means is that you can create a copy of the array that you want to use as keys and then use the copy to sort one of the other arrays and the original to sort the other. Check out the documentation for the Array.Copy overload that takes two arrays as arguments.
That said, copying the array and then sorting twice is a big overhead so you may not gain much, if anything, from this method. Better to just do it the right way in the first place, i.e. use a single array of a complex type rather than concurrent arrays of simple types. It's not 1960 any more, after all.

Resources