Excel VBA: Need Workaround for 255 Transpose Character Limit When Returning Variant Array to Selected Range - vba

I am struggling with a common problem involving an apparent Excel 255-character-limit. I encounter an error when attempting to return a variant-array from a Function to the selected range on the worksheet. When each of the cells in the Function's returning array are under 255 characters, they post to the sheet just as they should: one element appears in each cell within the selected range. However, if any element in my returning variant array is longer than 255 characters I get a Value! error. These errors are bad because I need my long elements and want to keep the data together!
Versions of this problem appear over and over again in many forums, yet I am able to find a clear simple, all-purpose solution for returning variant arrays to the selected range (not necessarily containing formulas) when the array cells exceed 255 characters. My largest elements are around 1000, but it would be better if the solution could accommodate elements up to 2000 characters.
Preferably, I want this to be implemented with a function, or lines of additional code which can be added to my function (not a subroutine). My reason for wanting to avoid subroutines: I do not want to have to hard-code any ranges. I want this to be flexible and for the output location to be dynamically based on my current selection.
Please, if you can help find a way to produce a function, which takes a Variant Array as input, and which maintains the desired array:cell 1:1 relationship, I'd appreciate it greatly.
So this function with short cells works:
Function WriteUnder255Cells()
Dim myArray(3) As Variant 'this the variant array I will attempt to write
' Here I fill each element with less than 255 characters
' it should output them if you call the function properly.
myArray(0) = "dog"
myArray(1) = "cat"
myArray(2) = "bird"
myArray(3) = "fly"
WriteUnder255Cells = Application.Transpose(myArray())
End Function
But this fuction, with cells exceeding 255 will not output.
Function WriteOver255Cells()
Dim myArray(3) As Variant 'this the variant array I will attempt to write
' Here I fill each element with more than 255 characters
' exceeding the 255-character limit causes the VALUE! errors when you output them
myArray(0) = "ThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelaxydog"
myArray(1) = "ThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydog"
myArray(2) = "ThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydog"
myArray(3) = "ThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydogThequickbrownfoxjumpedoverthelazydog"
WriteOver255Cells = Application.Transpose(myArray())
End Function
This is how you produce the output and results:
First you need to create the two modules(to insert one function into each module, paste the code from one into the respective module). To run "WriteUnder255Cells()", select an area of 4 rows x 1 column on the sheet (this where you return the module) and type "=WriteUnder255Cells()" into the formula bar (do not enter the quotes). Note these are called like array formulas, so instead of hitting (enter) to create the output, you need to hit (control + shift + enter). Repeat the same process for WriteOver255Cells() to produce the errors.
Here are some documents/forums discussions which address it. The solutions seem to be either overly specific or clunky because they evoke subroutines (which I want to avoid):
https://support.microsoft.com/en-us/kb/213841
http://www.mrexcel.com/forum/excel-questions/852781-visual-basic-applications-evaluate-method-255-character-limit.html
Excel: Use formula longer that 255 characters
VBA code error when array value exceeds 255 characters
http://dailydoseofexcel.com/archives/2005/01/10/entering-long-array-formulas-in-vba/
https://forums.techguy.org/threads/solved-vba-access-to-excel-255-char-limit-issue.996495/
http://www.mrexcel.com/forum/excel-questions/494675-255-character-cell-limit-visual-basic-applications-workaround.html
Array formula with more than 255 characters
http://www.mrexcel.com/forum/excel-questions/388250-size-limit-transferring-variant-range-excel-2007-a.html

This works for me:
Function Over255()
Dim myArray(3) As String '<<<<< not variant
myArray(0) = String(300, "a")
myArray(1) = String(300, "b")
myArray(2) = String(300, "c")
myArray(3) = String(300, "d")
'Over255 = Application.Transpose(myArray())
Over255 = TR(myArray)
End Function
'like Application.Transpose...
Function TR(arrIn) As String()
Dim arrOut() As String, r As Long, ln As Long, i As Long
ln = (UBound(arrIn) - LBound(arrIn)) + 1
ReDim arrOut(1 To ln, 1 To 1)
i = 1
For r = LBound(arrIn) To UBound(arrIn)
arrOut(i, 1) = arrIn(r)
i = i + 1
Next r
TR = arrOut
End Function
Seems like you need to return a string array and Application.Transpose doesn't do that

Related

VBA MIN and MAX function always returning 0

Hello I am trying to get the MIN and MAX values from the array and it always returns "0" despite anything. My code:
Dim MachineCapacitySmallestArray() As Variant
MachineCapacitySmallestArray = thisworkbook.worksheets(1).range("C25:D25")
SmallestCapacity = Application.Min(MachineCapacitySmallestArray)
in range I have natural numbers
I tried formatting those cells to numbers etc. but nothing works. What is the mistake I'm making and how to fix it?
According to the comments, it seems that your problem is your data, you have likely strings in your cell, not numbers (maybe somehow imported?)
As already mentioned, changing the cell format doesn't change the content of a cell, it just defines how to display data. The number 3.14 can be displayed as 3, as 3.140000, as 00003.14 or as 3.14E+00, nothing changes it's value. However, a String '3.14 is a combination of the characters 3, ., 1 and 4 and has nothing to do with a number. Setting a cell format after the value is in the cell will not convert it to a number.
If you read your data into VBA, VBA will get the exact values from Excel and in your case, you will have to convert it into numbers manually, for example with the following routine. The On Error Resume Next will prevent a type mismatch if a cell doesn't contain something that can be converted into a number.
Sub ArrToNumber(ByRef arr)
Dim i As Long, j As Long
For i = LBound(arr, 1) To UBound(arr, 1)
For j = LBound(arr, 2) To UBound(arr, 2)
On Error Resume Next
arr(i, j) = CDbl(arr(i, j))
On Error GoTo 0
Next
Next
End Sub
Now just add a call to this routine to your code. If you want to have the numbers also in Excel, remove the comment sign from the last statement.
Dim MachineCapacitySmallestArray() As Variant
MachineCapacitySmallestArray = thisworkbook.worksheets(1).range("C25:D25")
ArrToNumber MachineCapacitySmallestArray
SmallestCapacity = Application.Min(MachineCapacitySmallestArray)
' thisworkbook.worksheets(1).range("C25:D25") = MachineCapacitySmallestArray

Efficient Data Transfer from Excel VBA to Web-Service

I have a large worksheet (~250K rows, 22 columns, ~40MB plain data) which has to transfer its content to an intranet API. Format does not matter. The problem is: When accessing the data like
Const ROWS = 250000
Const COLS = 22
Dim x As Long, y As Long
Dim myRange As Variant
Dim dummyString As String
Dim sb As New cStringBuilder
myRange = Range(Cells(1, 1), Cells(ROWS, COLS)).Value2
For x = 1 To ROWS
For y = 1 To COLS
dummyString = myRange(x, y) 'Runtime with only this line: 1.8s
sb.Append dummyString 'Runtime with this additional line 163s
Next
Next
I get a wonderful 2D array, but I am not able to collect the data efficiently for HTTP export.
An X/Y loop over the array and access myRange[x, y] has runtimes >1min. I was not able to find an array method which helps to get the imploded/encoded content of the 2D array.
My current workaround is missusing the clipboard (Workaround for Memory Leak when using large string) which works fast, but is a dirty workaround in my eyes AND has one major problem: The values I get are formatted, “.Value” and not “.Value2”, so I have to convert the data on server site again before usage, e.g. unformat currency cells to floats.
What could be another idea to deal with the data array?
My thoughts are that you create two string arrays A and B. A can be of size 1 to ROWS, B can be of size of 1 to COLUMNS. As you loop over each row in your myRange array, fill each element in B with each column's value in that row. After the final column for that row and before you move to the next row, join array B and assign to the row in A. With a loop of this size, only put necessary stuff inside the loop itself. At the end you would join A. You might need to use cstr() when assigning items to B.
Matschek (OP) was able to write the code based on the above, but for anyone else's benefit, the code itself might be something like:
Option Explicit
Private Sub concatenateArrayValues()
Const TOTAL_ROWS As Long = 250000
Const TOTAL_COLUMNS As Long = 22
Dim inputValues As Variant
inputValues = ThisWorkbook.Worksheets("Sheet1").Range("A1").Resize(TOTAL_ROWS, TOTAL_COLUMNS).Value2
' These are static string arrays, as OP's use case involved constants.
Dim outputArray(1 To TOTAL_ROWS) As String ' <- in other words, array A
Dim interimArray(1 To TOTAL_COLUMNS) As String ' <- in other words, array B
Dim rowIndex As Long
Dim columnIndex As Long
' We use constants below when specifying the loop's limits instead of Lbound() and Ubound()
' as OP's use case involved constants.
' If we were using dynamic arrays, we could call Ubound(inputValues,2) once outside of the loop
' And assign the result to a Long type variable
' To avoid calling Ubound() 250k times within the loop itself.
For rowIndex = 1 To TOTAL_ROWS
For columnIndex = 1 To TOTAL_COLUMNS
interimArray(columnIndex) = inputValues(rowIndex, columnIndex)
Next columnIndex
outputArray(rowIndex) = VBA.Strings.Join(interimArray, ",")
Next rowIndex
Dim concatenatedOutput As String
concatenatedOutput = VBA.Strings.Join(outputArray, vbNewLine)
Debug.Print concatenatedOutput
' My current machine isn't particularly great
' but the code above ran and concatenated values in range A1:V250000
' (with each cell containing a random 3-character string) in under 4 seconds.
End Sub

VBA Finding Max value, without using MAX function and printing corresponding cell

I think I need to do a loop here but I'm not quite sure how exactly to write out the syntax as I'm used to just using the max function.
The function I need to create takes in two arrays; the first array has the numeric values while the second array has strings. The function is supposed to find the value in the first array that is the largest and return the corresponding string from the second array.
I'm not sure exactly how to construct my loop. I'm thinking I need to use some form of conditional statements.
Here's what I have so far:
Function FindMax(valueArray() As Integer, nameArray() As String) As String
Dim i As Long, y As Long
y = valueArray(0) 'change to 1 if using a different array structure
FindMax = nameArray(0) 'change to 1 if using a different array structure
For i = LBound(valueArray, 1) To UBound(valueArray, 1)
If valueArray(i) > y Then
y = valueArray(i)
FindMax = nameArray(i)
End If
Next i
Debug.Print ; y
Debug.Print ; FindMax
End Function
Here's a worksheet formula that gets the job done quick & easy:
=INDEX($C$3:$C$10,MATCH(MAX($B$3:$B$10),$B$3:$B$10))
If your:
Numbers of which to find the Maximum are in cells B3:B10, and,
Strings that you want to return are in cells C3:C10
...then the Maximum can be found with:
{MyMax} =MAX($B$3:$B$10)
...and the "Position #` of {MyMax} can be found with:
{Pos#} =MATCH( {MyMax} ,$B$3:$B$10)
...and the corresponding string can be found with:
=INDEX($C$3:$C$10, {Pos#} )
...so if we put it all together, we get:
=INDEX($C$3:$C$10,MATCH(MAX($B$3:$B$10),$B$3:$B$10))
Function FindMax(valueArray() As Integer, nameArray() As String) As String
dim i as long, y as long
y = valueArray(0) 'change to 1 if using a different array structure
FindMax = nameArray(0) 'change to 1 if using a different array structure
for i = lbound(valueArray,1) to ubound(valueArray,1)
if valueArray(i) > y then
y = valueArray(i)
FindMax = nameArray(i)
end if
next i
End Function
Pay attention to the bottom half of the code. See where is say :
this=FindMax(arr,arr2)
?
That is how you call a function. Obviously you'll need two arrays to pass to this function. I suggest googling "Functions vba" and do some light reading.

Excell cell value is not read as Number?

I am trying to add the data in the two cells of the excel sheet but even if the excel cell is of the type number it does not add up the cells. It seems that there is space infornt of the number that it does not add....image is below.
Is there a vba code to remove this space from each of the cell if its presesnt.
I have exported the excel from a pdf.
Excel will attempt to convert any value to a number if you apply an operator to it, and this conversion will handle spaces. So you can use =A1*1 or A1+0 to convert a value in A1 to a number, or something like this within a function =SUM(IFERROR(A1*1,0)).
That kind of implicit conversion automatically performs a trim(). You can also do this conversion explicitly by using the funciton N(), or NumberValue() for newer versions of Excel. However, as others have pointed out, many characters won't be automatically handled and you may need to use Substitute() to remove them. For instance, Substitute(A1,160,"") for a non-breaking space, a prime suspect because of its prevalence in html. The Clean() function can give you a shortcut by doing this for a bunch of characters that are known to be problematic, but it's not comprehensive and you still need to add your own handling for a non-breaking space. You can find the ASCII code for any specific characters that are grieving you by using the Code() function... for instance Code(Mid(A1,1,1))
Character Handling UDF
The UDF below gives flexibility to the character handling approach by allowing multiple characters to be removed from every cell in a range, and produces a result that can be used as an argument. For example, Sum(RemoveChar(A1:A5,160)) would remove all non-breaking spaces from the range being summed. Multiple characters can removed by being specified in either a range or array, for example Sum(RemoveChar(A1:A5,B1:B3)) or Sum(RemoveChar(A1:A5,{160,150})).
Function RemoveChar(R As Range, ParamArray ChVal() As Variant)
Dim x As Variant
Dim ResVals() As Variant
ReDim ResVals(1 To R.Count)
'Loop through range
For j = 1 To R.Count
x = R(j).Value2
If x <> Empty Then
'Try treating character argument as array
'If that fails, then try treating as Range
On Error Resume Next
For i = 1 To UBound(ChVal(0))
x = Replace(x, Chr(ChVal(0)(i)), "")
Next
If Err = 92 Then
Err.Clear
For Each Rng In ChVal(0)
x = Replace(x, Chr(Rng.Value2), "")
Next
End If
Err.Raise (Err)
On Error GoTo 0
'If numeric then convert to number
'so that numbers will be treated as such
'when array is passed as an argument
If IsNumeric(x) Then
ResVals(j) = Val(x)
Else
ResVals(j) = x
End If
End If
Next
'Return array of type variant
RemoveChar = ResVals
End Function
Numeric Verifying UDF
The drawback with replacing characters is that it's not comprehensive. If you want something that's more of a catch-all, then perhaps something like this.
Function GetNumValues(R As Range)
Dim c, temp As String
Dim NumVals() As Double
ReDim NumVals(1 To R.Count)
'Loop through range
For j = 1 To R.Count
'Loop through characters
'Allow for initial short-circuit if already numeric
For i = 1 To Len(R(j).Value2)
c = Mid(R(j).Value2, i, 1)
'If character is valid for number then include in temp string
If IsNumeric(c) Or c = Application.DecimalSeparator Or c = Application.ThousandsSeparator Then
temp = temp + c
End If
Next
'Assign temp string to array of type double
'Use Val() function to convert string to number
NumVals(j) = Val(temp)
'Reset temp string
temp = Empty
Next
'Return array of type double
GetNumValues = NumVals
End Function

In Excel 2010, how could I remove duplicates and concatenate values within a cell range that includes multiple values cells?

I made a document in Excel 2010 however, the functionality I'm hoping to get from it doesn't seem to be possible (at least not with the default Excel functions) and I don't know enough about VB programming to make my own UDF. (I'm actually using one I found online which does part of what I want, but doesn't meet all of my needs.)
Let me break it down:
I have multiple sheets with groups of fields where users can add numbers (some will be blank, some will contain a single number, some will contain multiple comma-separated numbers)
I have an "Overview" sheet where I want to Concatenate those numbers (and remove any duplicates) within a few different sections (only looking at specific field groups).
I found a ConcatIf UDF that works fairly well for this, however it can't handle non-consecutive cells to concatenate (For example, I want to concatenate and remove duplicates from cells D30, G30, J30 and M30 together) (Here's the UDF:)
Function ConcatIf(ByVal compareRange As Range, ByVal xCriteria As Variant, Optional ByVal stringsRange As Range, _
Optional Delimiter As String, Optional NoDuplicates As Boolean) As String
Dim i As Long, j As Long
With compareRange.Parent
Set compareRange = Application.Intersect(compareRange, Range(.UsedRange, .Range("a1")))
End With
If compareRange Is Nothing Then Exit Function
If stringsRange Is Nothing Then Set stringsRange = compareRange
Set stringsRange = compareRange.Offset(stringsRange.Row - compareRange.Row, _
stringsRange.Column - compareRange.Column)
For i = 1 To compareRange.Rows.Count
For j = 1 To compareRange.Columns.Count
If (Application.CountIf(compareRange.Cells(i, j), xCriteria) = 1) Then
If InStr(ConcatIf, Delimiter & CStr(stringsRange.Cells(i, j))) <> 0 Imp Not (NoDuplicates) Then
ConcatIf = ConcatIf & Delimiter & CStr(stringsRange.Cells(i, j))
End If
End If
Next j
Next i
ConcatIf = mid(ConcatIf, Len(Delimiter) + 1)
End Function
It also can't handle the "multiple numbers in one cell" as separate numbers.
Is there a way to make a Concatenate UDF that "parses" the cells it's looking at to look for duplicates between the multiple numbers cells and the single numbers cells, and then output the result? Preferably allowing it to take a series of non-consecutive cells to work on (across different sheets).
Sorry if the explanation is a bit convoluted, it's my first time asking for this kind of help. :x
Here's an example:
If I have cells with:
2,4,6
2,6
2
4
6
6,8
I'd want to be able to simply get:
2,4,6,8
Right now, instead, I'd get:
2,4,6,2,6,6,8
Try the below. You can adapt it appropriately if you need to change the delimiter etc. I have documented what it is doing and why.
Example formula: =blah(A1:A7,A8,C9) (it can also be called from code)
Example output: 2,4,6,8
Public Function Blah(ParamArray args()) As String
'Declarations
Dim uniqueParts As Collection
Dim area As Range
Dim arg, arr, ele, part
Dim i As Long
'Initialisations
Set uniqueParts = New Collection
'Enumerate through the arguments passed to this function
For Each arg In args
If TypeOf arg Is Range Then 'range so we need to enumerate its .Areas
For Each area In arg.Areas
arr = area.Value 'for large ranges it is greatly quicker to load the data at once rather than enumerating each cell in turn
For Each ele In arr 'enumerate the array
addParts CStr(ele), uniqueParts 'Call our sub to parse the data
Next ele
Next area
ElseIf VarType(arg) > vbArray Then 'an array has been passed in
For Each ele In arg 'enumerate the array
addParts CStr(ele), uniqueParts 'Call our sub to parse the data
Next ele
Else 'assume can be validly converted to a string. If it cannot then it will fail fast (as intended)
addParts CStr(arg), uniqueParts 'Call our sub to parse the data
End If
Next arg
'process our results
If uniqueParts.Count > 0 Then
ReDim arr(0 To uniqueParts.Count - 1)
For i = 1 To uniqueParts.Count
arr(i - 1) = uniqueParts(i)
Next i
'we now have an array of the unique parts, which we glue together using the Join function, and then return it
Blah = Join(arr, ",")
End If
End Function
'Sub to parse the data. In this case the sub splits the string and adds the split elements to a collection, ignoring duplicates
Private Sub addParts(partsString As String, ByRef outputC As Collection)
'ByRef is unecessary but I use it to document that outputC must be instantiated
Dim part
For Each part In Split(partsString, ",")
On Error Resume Next 'existing same key will raise an error, so we skip it and just carry on
outputC.Add part, part
On Error GoTo 0
Next part
End Sub