Convert a 2D array to a 1D array - excel

How do I work around the 255 characters per cell limit when converting a range (= multidimensional array) to single dimensional array with the use of Application.Index(array, row, column)?
The following truncated example reproduces the error:
Error 13. Type mismatch
(The complete code is on superuser where I tried to help another user).
How to reproduce
Open a new Excel sheet and insert the formula =REPT("x",256) to cell A1
This creates a 256 characters long string which is just 1 character too long for the last step
Open the VBA editor (Alt+F11) and paste the below code somewhere
Execute the code line by line with F8
Function StringLengthTest()
Dim arr2D As Variant
Dim arr1D As Variant
arr2D = Rows(1)
arr1D = Application.Index(arr2D, 1, 0)
End Function
You'll see the same error at the last line when Excel tries to convert a range (2D) to a 1D array while one of its cells has more than 255 characters.
To prove this, change =REPT("x",256) to =REPT("x",255) and run the code again. This time it will work.
Question: Should I declare my variables in another way? Is there a better way to convert a range (which is always a 2D object at first) to a single dimensional array?
I know I could use a loop to iterate through the arrays and save all 2D array values one by one to a 1D array. But that's not efficient. Imagine really large sheets.

by far the best way of getting anything from a cells into memory (an array) is to use an array variant. I think the problem you are having is with index not with your method.
Hopefully this code should explain it.
Dim v_data As Variant
Dim rw As Long, cl As Long ' for row and column
Dim arr1d() As Variant
Dim count As Long
' I'm going to use UsedRange to get the whole sheet .UsedSheet
' If you just want things from one row or column then just spec
' activesheet.range("A1:A100") or use cells()
With ActiveSheet.UsedRange
ReDim v_data(1 To .Rows.count, 1 To .Columns.count)
v_data = .Value
End With
'now we have all the things from that sheet.
'so to convert to 1d array where the cell value is say = 1
For rw = LBound(v_data) To UBound(v_data)
For cl = LBound(v_data, 2) To UBound(v_data, 2) ' note the comma 2 for the second dimension bounds.
If v_data(rw, cl) = 1 Then
count = count + 1
ReDim Preserve arr1d(1 To count)
arr1d(count) = v_data(rw, cl)
End If
Next cl
Next rw
For count = LBound(arr1d) To UBound(arr1d)
Debug.Print arr1d(count)
Next count
now the trick is to farm this off to a function that takes a few args( a 2d range, what you are looking for in that range) and returns your list.
To get your data back into a workbook
ActiveSheet.Cells(1, 1).Resize(UBound(arr1d), 1).Value = arr1d
make a range of the exact same size in terms of the bounds of your array and then ensuring you use .value just pop in the variant array.

Related

How to Split a Range with delimiter, extract Unique values and re-Concatenate

I am not experienced with VBA so this problem may be due to simple non understanding. I want to write a function that can look through a range, split the cell Values in that range by looking for a new line, and then concatenate the unique values and return those.
An example of one cell in a range might look like:
D19-160
D19-171
D19-154
etc.
I want to split each one up and check if its unique and then combine all the uniques into one final cell.
I have tried using a previous function ConcatUniq and combining it with split, so it would take a series of tokens instead of a range.
Function SplitConcatUniq(xRg As Range, xChar As String) As String
Dim txt As String
Dim i As Integer
Dim FullName As Variant
Dim xDic As Object
Set xDic = CreateObject("Scripting.Dictionary")
txt = Concatenate(xRg)
FullName = Split(txt, Chr(10))
For i = 0 To UBound(FullName)
xDic(FullName(i)) = Empty
Next
SplitConcatUniq = Join$(xDic.keys, xChar)
Set xDic = Nothing
End Function
I am expecting one cell to have a combination of all the unique strings, but instead I am just getting a #Value! error. Thanks for any help or even a better way to go about this.
How to use your unchanged example code via user defined function
It's not possible to combine ("concatenate") range data as you did because there's no exact VBA pendant, but you could define a function of your own leaving your code in OP unchanged so far. This approach only intends to be as close as possible to your original code without making further approvements.
Note: Take into account the known limitation of the Transpose method as described in the code comments below.
Function Concatenate(xRg As Range) As String
' Purpose: get concatenated string from a contiguous set of cells in a column or row
' delimited by linefeed characters - chr(10)
' Note: accepts only contiguous data of one row or one column due to range argument
' Caveat: known limitation of transposition: array length of 2^16, i.e. roughly 64k)
If xRg.Rows.Count > 1 And xRg.Columns.Count > 1 Then Exit Function
'[1] get data to variant 2-dim array
Dim v: v = xRg
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'[2] change to 1-dimensioned "flat" array
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' 2 a) transposition in case of column data creates a flat 1-dim array
v = Application.Transpose(v)
' 2 b) double transposition needed only in case of "row" data
If xRg.Rows.Count = 1 Then v = Application.Transpose(v)
'[3] return function result
Concatenate = Join(v, Chr(10))
End Function

Assigning a specific cell from a Range to a variable VBA

Is there a way to store specific cells into new variables from a range in VBA? What I mean is...
Suppose I have set the data below to a range call "numbers".
Now in VBA, for each row I want to extract each individual cell value and assign each value to a different variable. And then repeat again for the next row.
I essentially want to the use the values in a given row to do something and then have it repeat again for the next row.
Does this make sense???
This is what I've been playing around with... but I don't get how to assign each cell from a given row to a new variable
Public Sub try()
Dim rng As Range
Dim row As Range
Dim cell As Range
Dim n As Double
Set rng = Range("numbers")
For Each row In rng.Rows
For Each cell In row.Cells
n = cell.value
Next cell
Next row
End Sub
Try this:
Dim numberArray As Variant
' this line will assign numbers inside the range to an array
numberArray = Range("numbers").Value2
' now you are able to access all numbers in you range through this array, like this:
MsgBox numberArray(1, 1) 'it will show 1
The way you are doing it right now doesn't make sense, since you are assigning all values to one variable n, so on every iteration of a loop previous value gets overwritten, resulting in n having last value in a range, which is 3.
Is there any particular reason you want to store any cell value in a new variable?
With a given range it would be very easy to just store your values in a Variant Array. In your example it would be something like:
Public Sub try()
Dim rng As Range
Dim dataArray as Variant
Set rng = Range("numbers")
dataArray = rng
debug.print dataArray(1, 2) 'This would print 7 in your example range
end sub
You could then easily loop through your Variant Array like this:
Dim i as Long, j as Long
For i = 1 To UBound(dataArray, 1) 'This will loop through each row
For j = 1 To UBound(dataArray, 2) 'This will loop through each column (cell in your row)
Debug.Print dataArray(i, j)
Next
Next
UBound() returns the length of the Array at the given dimension as the second parameter. I am just printing the values again since I do not know what exactly your intention is.

Excel Put Array Value in Range - Transpose Gives Type Mismatch Error

UPDATE:
I had two problems that contributed to this error. First, I had a clash with a SAS add-in that was messing up the copy/paste functionality. Second, I had a null value in the array that was causing the transposition to fail. I have handled the null and everything works fine.
I'm trying to assign an array of values to my Excel worksheet. When I use the TRANSPOSE function I am getting type mismatch errors. I don't understand why, there are only about 200 "rows" in the set:
Worksheets("xyz").Range("A2").Resize(ValidCode, 5).Value = & _
Application.WorksheetFunction.Transpose(CodeData)
It works fine when I put a static value instead of the transposed array. I don't understand what's happening to it. It is type "Variante/Variante(0 to 4, 0 to 205)"
The array needs to be transposed because I had to redim preserve it in a code loop (so the "columns" are rows, etc.) I guess I could manually transpose it, but that seems unnecessary.
The & character is not necessary and in fact raises a 1004 error when I try to implement your code.
Otherwise, I cannot replicate the error you describe, this example code works with or without Transpose function, although you need to use Transpose to achieve the desired results.
Sub Test()
Dim ws As Worksheet: Set ws = Sheets("xyz")
Dim rng As Range: Set rng = ws.Range("A2")
Dim r As Integer
Dim c As Integer
Dim CodeData() As Variant
ReDim Preserve CodeData(4, 205) '## Create a dummy array to test this method
For r = 0 To UBound(CodeData, 1) '## populate the array with random numbers
For c = 0 To UBound(CodeData, 2)
CodeData(r, c) = Application.WorksheetFunction.RandBetween(0, 100)
Next
Next
'## Drop this array in to the worksheet:
rng.Resize(UBound(CodeData, 2) + 1, UBound(CodeData, 1) + 1).Value = _
Application.WorksheetFunction.Transpose(CodeData)
End Sub

empty cells returned by an array

I have been working on this particular problem for sometime and am obviously missing something very simple. I ma trying to create an aray based on a dynamic range in Excel and using the individual elements to compare against another array. The only problem with the attached code is it continues to show empty elements. Any guidance would be appreciated.
Part of my overall code attached.
Sub Test_Again()
Dim R As Long
Dim C As Long
Dim List() As Variant
Dim i As Integer
List = Sheets("Sheet11").Range("A2:A17").Value
For R = 1 To UBound(List, 1) ' First array dimension is rows.
For C = 1 To UBound(List, 2) ' Second array dimension is columns.
Debug.Print List(R, C)
Next C
Next R
ReDim List(UBound(List, 1))
Do Until i = UBound(List)
If List(i) = Now() Then
End If
i = i + 1
Loop
End Sub
The normal Redim will clear your array - unless you use Redim Preserve. However, according to the help:
If you use the Preserve keyword, you can resize only the last array dimension and you can't change the number of dimensions at all. For example, if your array has only one dimension, you can resize that dimension because it is the last and only dimension. However, if your array has two or more dimensions, you can change the size of only the last dimension and still preserve the contents of the array.
Therefore, in your case Redim will not help you here. If you want to transfer a two dimensional array to a one dimensional array, you need to do this manually instead:
Sub Test_New()
Dim lRow As Long, lCol As Long
Dim vListSource() As Variant, vListTarget() As Variant
'Assign soure array
vListSource = Sheets("Sheet11").Range("A2:A17").Value
'Show full content for debug
For lRow = LBound(vListSource) To UBound(vListSource) ' First array dimension is rows.
For lCol = LBound(vListSource, 2) To LBound(vListSource, 2) ' Second array dimension is columns.
Debug.Print vListSource(lRow, lCol)
Next lCol
Next lRow
'Transfer array to one dimension
ReDim vListTarget(LBound(vListSource) To UBound(vListSource))
For lRow = LBound(vListSource) To UBound(vListSource)
vListTarget(lRow) = vListSource(lRow, LBound(vListSource, 2))
Next lRow
'Your check code
For lRow = LBound(vListTarget) To UBound(vListTarget)
If vListTarget(lRow) = Now() Then
'Do something here
End If
Next lRow
End Sub
This will copy the first row of your range/array to a one dimensional array and use this for further processing.
However, from your code and question I do not see the advantage of redimming it to one dimension - you could easily do your loop one the two dimensional array - and just look in the first and only column.

How to assign an Excel Range to a 2D array?

Could you please say- how a Excel Range("G2:AA1000") can be assigned to a 2D array? If possible how to return back that 2D array to the same range after performing some operation on that 2D array?After assignment a Range to an 2D array,How each row will be identified from that 2D matrix?
Thanks,
There is an easy way to make changes to an area using an array, and write it out to the same place, or somewhere else.
This example code will copy data from one area to another, using an array:
Sub example()
Dim testdata()
testdata = Range("A1:B13")
Range("D1:E13") = testdata ' simple copy
Range("G1") = testdata ' copy only 1 cell
Range("I1:K22") = testdata 'try to copy too much
End Sub
The testdata array starts from 1, and will extend to the number of columns and rows specified in the range. In this case, testdata(1,1) refers to the data obtained from A1, testdata(1,2) refers to B1, finishing up with testdata(13,1) referring to A13, and testdata(13,2) referring to B13.
Setting the range equal to the array in the next line copies the array into the specified location.
If the area is smaller than the original array, it will copy only enough of the array to fill that space, so Range("D1")=testdata will only place one cell on the sheet.
If you specify a larger area, then #N/A will fill the area that is not in the space covered by array elements, so Range("A1:A3")=testdata will fill A1 and A2 with data from the array, but A3 will have #N/A
Result of example program:
Note: A1:B13 is the original data, which gets copied with the subsequent range(??)=testdata
Here's a worked-out example of reading a range of data from a worksheet, operating on the array, and then writing it back out to the same worksheet.
Sub RangeArray()
Dim Rng As Range
Dim Arr()
Dim ArrItem
Dim i As Long, j As Long
Dim rUB as Long, cUB as Long
Set Rng = Worksheets("Sheet1").Range("A1:G19")
rUB = Rng.Rows.Count 'Row upper bound
cUB = Rng.Columns.Count ' Column upper bound
ReDim Arr(1 To rUB, 1 To cUB)
'Read worksheet range into array
For i = 1 To rUB
For j = 1 to cUB
Arr(i, j) = Rng.Cells(i, j).Value
Next
Next
'Do something to array
For i = 1 To rUB
For j = 1 To cUB
If i <> j Then
Arr(i, j) = Arr(i, j) / (i * j)
End If
Next
Next
'Write array back to worksheet
Set Rng = Worksheets("Sheet1").Range("I1")
For i = 1 To rUB
For j = 1 To cUB
Rng.Offset(i - 1, j - 1).Value = Arr(i, j)
Next
Next
End Sub
Yes, an Excel range can be assigned to a 2D array in one single assignment. In C++/CLI it looks like this:
cli::array<Object^, 2>^ arrData = safe_cast<cli::array<Object^, 2>^>(rg->Value[Excel::XlRangeValueDataType::xlRangeValueDefault]);
In c# or visual basic it would look considerably simpler (see here for example https://www.automateexcel.com/vba/assign-range-to-array/, btw in dotnet the object is now playing the role of the variant data type). Note that it must be a two dimensional array and the returned array has a one-based indexing, and not a zero based indexing.
For large data sets this method is a lot faster than the looping. The looping generates lots of COM objects. I compared the two methods with a 33000 row Excel range and the data import into the array was almost instantaneous while with the looping it took very long and it heated up the CPU.
One way to loop through a range is to use the For...Next loop with the Cells property. Using the Cells property, you can substitute the loop counter (or other variables or expressions) for the cell index numbers. In the following example, the variable counter is substituted for the row index. The procedure loops through the range C1:C20, setting to 0 (zero) any number whose absolute value is less than 0.01.
Sub RoundToZero1()
For Counter = 1 To 20
Set curCell = Worksheets("Sheet1").Cells(Counter, 3)
If Abs(curCell.Value) < 0.01 Then curCell.Value = 0
Next Counter
End Sub

Resources