VBA Concatenate 2 columns without loop - excel

I have my column B with Strings like "6L2AAB".
My column D with Strings like "E3"
I'd like to produce in my column J the concatenation of B&D, for instance "6L2AABE3", for each row
My code throws a "13" error
.Range("J:J").Value = .Range("B:B").Value & "" & .Range("D:D").Value
Is there a way to do this without a loop ? Thanks

Edit: added loop-based approach to compare timing. Loop is faster!
Your code doesn't work because (eg) .Range("B:B").Value returns a 2-dimensional array, and you can't concatenate the contents of two arrays using &
You can use the worksheet's Evaluate method:
Sub tester()
Dim t, i As Long, arr1, arr2, arr3, x As Long
t = Timer
With ActiveSheet
.Range("J:J").Value = .Evaluate("=B:B&D:D")
End With
Debug.Print "Evaluate", Timer - t
t = Timer
With ActiveSheet
arr1 = .Range("B:B").Value 'read input values
arr2 = .Range("D:D").Value
ReDim arr3(1 To UBound(arr1), 1 To 1) 'size array for output
For i = 1 To UBound(arr1, 1) 'loop and concatenate
arr3(i, 1) = arr1(i, 1) & arr2(i, 1)
Next i
.Range("J:J").Value = arr3 'populate output to sheet
End With
Debug.Print "Loop", Timer - t
End Sub
Maybe don't run it on the whole column unless you really need that though.

#Tim Williams It really depends on the number of rows, if you are working with larger amounts of data loops start to slow slightly and evaluate will jump ahead in processing speed just slightly. But there is another option that is faster than both, the .formula method. See Tim's code with it added in below using > 1 million rows with .formula added.
Average for me:
Formula 1.664063
Evaluate 3.675781
Loop 3.824219
Sub tester()
Dim t, i As Long, arr1, arr2, arr3, x As Long
t = Timer
With ActiveSheet
.Range("C:C").Formula = "=A:A&B:B"
End With
Debug.Print "Formula", Timer - t
t = Timer
With ActiveSheet
.Range("C:C").Value = .Evaluate("=A:A&B:B")
End With
Debug.Print "Evaluate", Timer - t
t = Timer
With ActiveSheet
arr1 = .Range("A:A").Value 'read input values
arr2 = .Range("B:B").Value
ReDim arr3(1 To UBound(arr1), 1 To 1) 'size array for output
For i = 1 To UBound(arr1, 1) 'loop and concatenate
arr3(i, 1) = arr1(i, 1) & arr2(i, 1)
Next i
.Range("C:C").Value = arr3 'populate output to sheet
End With
Debug.Print "Loop", Timer - t
End Sub

Related

Excel VBA - Execution Time Of Macro Slower After Each Execution

I have a code that works as following:
Refresh a query that has roughly 10.000 rows.
Split the data in various 2D Arrays, depending on some criteria (I have in total a dozen different 2D Arrays).
Paste each 2D Array in a different sheet.
When I run the macro the first time it takes ~18 seconds, on the second run ~30 seconds, on the third run ~35 seconds, on the fourth run ~45 seconds and so on. The data from the query is exactly the same at every run (it doesn't change so frequently, or it can change of at most one or two lines).
Can somebody explain me if there are some kind of memory issues I'm not aware of? I also tried to set the matrix equal to Nothing after is pasted on the sheet but this didn't change the slowdown at every run. Any help would be appreciated.
P.S. the code is long and I feel it would be useless to paste it all here. It works more or less as follows:
Sub GetMatrix()
Dim Matrix As Variant, IndexMatrix As Long, i As Long, NoRows As Long
IndexMatrix = 0
ReDim Matrix(IndexMatrix, 2)
NoRows = Application.CountA(Range("A:A"))
For i = 2 To NoRows
If Cells(i, 1) = "Something" Then
Matrix(IndexMatrix, 0) = "Something"
Matrix(IndexMatrix, 1) = "Something"
Matrix(IndexMatrix, 2) = "Something"
IndexMatrix = IndexMatrix + 1
ReDim Preserve Matrix(IndexMatrix, 2)
End If
Next
GetMatrix = Matrix
End Sub
Try the next adapted function, please:
Function GetMatrix() As Variant
Dim sh As Worksheet, arr As Variant, Matrix As Variant
Dim IndexMatrix As Long, i As Long, LastRow As Long
Set sh = ActiveSheet
LastRow = sh.Range("A" & Rows.count).End(xlUp).Row
arr = sh.Range("A1:A" & LastRow).Value
ReDim Matrix(2, UBound(arr)) 'to admit redim preserve (only on the last dimension) at the end
For i = 2 To LastRow
If arr(i, 1) = "Something" Then
Matrix(0, IndexMatrix) = "Something"
Matrix(1, IndexMatrix) = "Something"
Matrix(2, IndexMatrix) = "Something"
IndexMatrix = IndexMatrix + 1
End If
Next
ReDim Preserve Matrix(2, IndexMatrix - 1)
GetMatrix = Matrix
End Function

Subscript out of range when passing argument to array

I'm setting up a subroutine to perform matches between two worksheets. The arrays are one dimensional going from the first cell of data to the last, which is held within a variable.
The data in the arrays are not numerical, but if I ReDim them as strings I get a type mismatch in the initialization.
SheetOneLastRow and SheetTwoLastRow are subroutines which find the last row in each sheet to be held in the variables FirstLastRow and SecondLastRow which are declared globally because they are used in other subs.
EDIT 1: The error is on the line:
If search(i) = arr(j) Then
Value of FirstLastRow is 9589 and SecondLastRow is 20750.
The search and arr have only been declared here with ReDim.
Sub Match()
SheetOneLastRow
SheetTwoLastRow
Dim i, j As Integer
ReDim arr(SecondLastRow - 2) As Variant
ReDim search(FirstLastRow - 2) As Variant
search = Range(wksv.Cells(2, 11), wksv.Cells(FirstLastRow, 11))
arr = Range(wkst.Cells(2, 6), wkst.Cells(SecondLastRow, 6))
For i = 2 To FirstLastRow
For j = 2 To SecondLastRow
If search(i-2) = arr(j-2) Then
wkst.Cells(j, 3) = wksv.Cells(i, 3)
End If
Next j
Next i
End Sub
Search() is a 2D array, and the code is using it as a 1D array.
In general, passing range to arrays is not complicated, but there are a few tricks, you should be aware of. First trick - whenever the range is passed like this:
search = Range(wksv.Cells(2, 11), wksv.Cells(FirstLastRow, 11)) it is passed to a 2-dimensional array. See the blue highlighted line at the screenshot:
The problem with the 2-dimensional arrays is that they are of two dimensions. E.g., you should be looking for Search(2,1) instead of Search(2). Or in the code above it should be: If Search(i,1) = arr(j,1) Then
There are probably better ways to solve the problem, e.g. passing the range to a single dimensional array, as in the example here - https://stackoverflow.com/a/52467171/5448626
This is what would happen, if you force the range to be a 1D array:
Sub Match()
Dim i, j As Integer
FirstLastRow = 9589
SecondLastRow = 20750
ReDim arr(SecondLastRow - 2) As Variant
ReDim Search(FirstLastRow - 2) As Variant
With Worksheets(1) 'put wksv
Search = Application.Transpose(.Range(.Cells(2, 11), .Cells(FirstLastRow, 11)))
End With
With Worksheets(2) 'put wkst
arr = Application.Transpose(.Range(.Cells(2, 6), .Cells(SecondLastRow, 6)))
End With
For i = 2 To FirstLastRow - 2 '-2 is needed because of ReDim arr(SecondLastRow - 2)
For j = 2 To SecondLastRow - 2
If Search(i) = arr(j) Then
Worksheets(1).Cells(j, 3) = Worksheets(2).Cells(i, 3)
End If
Next j
Next i
End Sub

Nested loops causing Excel crash

I am attempting to run a VBA macro that iterates down about 67,000 rows with 100 columns in each row. For each of the cells in these rows, the value is compared against a column with 87 entries in another sheet. There are no errors noted when the code is run but Excel crashes every time. The odd thing is that the code seems to work; I have it set to mark each row in which a match is found and it does so before crashing. I have attempted to run it many times and it has gotten through between 800 and 11,000 rows before crashing, depending on the attempt.
My first suspect was memory overflow due to the volume of calculations but my system shows CPU utilization at 100% and memory usage around 50% while running this code:
Sub Verify()
Dim codes As String
Dim field As Object
For i = 2 To Sheets("DSaudit").Rows.Count
For Each field In Sheets("Dsaudit").Range(Cells(i, 12), Cells(i, 111))
r = 1
While r <= 87
codes = ThisWorkbook.Sheets("287 Denominator CPT").Cells(r, 1).Value
If field = codes Then
Cells(i, 112).Value = "True"
r = 88
Else
r = r + 1
End If
Wend
Next field
i = i + 1
Next i
End Sub
It should also be noted that I am still very new to VBA so it's likely I've made some sort of egregious rookie mistake. Can I make some alterations to this code to avoid a crash or should I scrap it and take a more efficient approach?
When ever possible iterate variant arrays. This limits the number of times vba needs to access the worksheet.
Every time the veil between vba and Excel is pierced cost time. This only pierces that veil 3 times not 9,031,385,088
Sub Verify()
With Sheets("DSaudit")
'Get last row of Data
Dim lastrow As Long
lastrow = .Cells(.Rows.Count, 12).End(xlUp).Row 'if column 12 ends before the last row of data change to column that has them all.
'Load Array with input Values
Dim rng As Variant
rng = .Range(.Cells(2, 12), .Cells(lastrow, 111)).Value
'Create output array
Dim outpt As Variant
ReDim outpt(1 To UBound(rng, 1), 1 To 1)
'Create Match array
Dim mtch As Variant
mtch = Worksheets("287 Denominator CPT").Range("A1:A87").Value
'Loop through first dimension(Row)
Dim i As Long
For i = LBound(rng, 1) To UBound(rng, 1)
'Loop second dimension(Column)
Dim j As Long
For j = LBound(rng, 2) To UBound(rng, 2)
'Loop Match array
Dim k As Long
For k = LBound(mtch, 1) To UBound(mtch, 1)
'If eqaul set value in output and exit the inner loop
If mtch(k, 1) = rng(i, j) Then
outpt(i, 1) = "True"
Exit For
End If
Next k
'If filled true then exit this for
If outpt(i, 1) = "True" Then Exit For
Next j
Next i
'Assign the values to the cells.
.Cells(2, 112).Resize(UBound(outpt, 1), 1).Value = outpt
End With
End Sub

EXCEL - Fuse text cells and split into different lines

I have a file that looks like this, containing a huge amount of data
>ENSMUSG00000020333|ENSMUST00000000145|Acsl6
AGCTCCAGGAGGGCCCGTCTCAGTCCGATGAACTTTGCAGCAATATTATAGTTATTCGTG
GTTCACAGAATTCCATTAAACATAAAGAAAAAACATAA
>ENSMUSG00000000001|ENSMUST00000000001|Gnai3
GAGGATGGCATAGTAAAAGCTATTACAGGGAGGAGTGTTGAGACCAGATGTCATCTACTG
CTCTGTAATCTAATGTTTAGGGCATATTGAAGTTGAGGTGCTGCCTTCCAGAACTTAAAC
the columns should be transformed so that lines always contain:
ENSMUSG*** ENSMUST*** GeneName Sequence (four separate columns)
the Sequence column should be the lines starting with either A,C,G,or T fused into one text cell, the number of cells to fuse varies from gene to gene.
does anyone have advice how to solve this?
thank you so much for your help!
best wishes
kk
Use the Text to Columns button on the Data tab. Choose Delimited , click Next, then select Other and in the box type the pipe symbol |. Then click Next and Finish.
I believe it is only those with Office 365 subscriptions that have the worksheet function CONCAT, which might be useful in this situation. So I would do this with a VBA macro.
First line -- split using the pipe | delimiter
Then concatenate the next lines until we get to one that does not start with "A", "C", "G", "T"
Store the results in a Collection object
Write the results back to the worksheet.
Since you have a large database, the "work" is done in VBA arrays as this will process much more rapidly.
It is assumed that your data is in Column A, starting in A1; and that your results will be written in columns B:E
If your database is clean, and formatted as you show, it should work OK. If it does not fall into the format you have presented, some error-checking might need to be added.
Option Explicit
Sub Organize()
Dim COL As Collection
Dim vSrc As Variant, vRes As Variant
Dim WS As Worksheet, rRes As Range
Dim V As Variant, W As Variant, S As String
Dim I As Long, J As Long
Set WS = ActiveSheet
With WS
Set rRes = .Cells(1, 2)
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp))
End With
Set COL = New Collection
For J = 1 To UBound(vSrc, 1)
ReDim vRes(0 To 3)
W = Split(vSrc(J, 1), "|") 'First line
For I = 0 To 2
vRes(I) = W(I)
Next I
S = ""
'Concatenate subsequent lines
'Could look for the "<" but OP gave specifice starting letters
' So will use that
Do
Select Case Left(vSrc(J + 1, 1), 1)
Case "A", "C", "G", "T"
S = S & vSrc(J + 1, 1)
Case Else
Exit Do
End Select
J = J + 1
Loop Until J = UBound(vSrc, 1)
vRes(3) = S
COL.Add vRes
Next J
ReDim vRes(1 To COL.Count, 1 To 4)
I = 0
For Each W In COL
I = I + 1
For J = 1 To 4
vRes(I, J) = W(J - 1)
Next J
Next W
Set rRes = rRes.Resize(rowsize:=UBound(vRes, 1), columnsize:=UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
.EntireColumn.AutoFit
End With
End Sub

Can you do something to the effect of "For each x in jaggedArray () (0)" in order to compare values of a nested array?

The purpose of this code is to be able to track multiple properties of undefined "IDs," like name, total time, and occurrences while going through ton of data. As of right now I am using a jagged array to achieve this.
idArray = array of all gathered ID's in data
idProp = array of properties of each ID
I had everything working perfectly till I added the nested array.
I need to be able to check while going through the data if the ID is a new or not. with the name of each ID being held in the nested array i can't seem to be able to do this. Here is what i have so far:
For Each ID In idArray()(1) '<-- does not target nested array correctly
If ID = wbData.Sheets(1).Range("C" & Count) Then
idPrs = True
End If
Next ID
'adding ID if not present
If idPrs = False Then
idCount = idCount + 1
MsgBox "new ID: " & wbData.Sheets(1).Range("C" & Count) & " on row " & Count
ReDim Preserve idArray(0 To idCount)
ReDim idProp(0 To 2)
'adding starting values
idProp(0) = wbData.Sheets(1).Range("C" & Count)
idProp(1) = wbData.Sheets(1).Range("h" & Count)
idProp(2) = 1
idList(gidCount) = idProp
'double array second "()" referrs to second array
'MsgBox idArray(1)(0)
End If
any ideas are welcome, even if they are done a completely different way.
It's not entirely clear how you are declaring or popuplating your idArray.
This small demo of jagged arrays might give you some hints:
Sub Demo()
Dim i As Long, j As Long
Dim arr1 As Variant, arr2 As Variant
Dim v As Variant
' Declare and populate a jagged array
ReDim arr1(0 To 9)
For i = LBound(arr1) To UBound(arr1)
ReDim arr2(0 To i)
For j = LBound(arr2) To UBound(arr2)
arr2(j) = j
Next
arr1(i) = arr2
Next
' Option 1 to iterate the array
For i = LBound(arr1) To UBound(arr1)
For j = LBound(arr1(i)) To UBound(arr1(i))
Debug.Print arr1(i)(j)
Next
arr2 = arr1(i)
For j = LBound(arr2) To UBound(arr2)
Debug.Print arr2(j)
Next
Next
' Option 2 to iterate the array
For Each arr2 In arr1
For Each v In arr2
Debug.Print v
Next
Next
End Sub
Dictionaries could better tend to your problem. Here is a solution with a dictionary of dictionaries:
Dim probsById As New Scripting.Dictionary
Sub processId(id As String)
If Not probsById.Exists(id) Then
Dim props As New Scripting.Dictionary
Call props.Add("id", id)
Call props.Add("name", "Miller")
Call props.Add("status", 1)
Call probsById.Add(id, props)
End If
End Sub
Sub test()
Call processId("bar")
Debug.Print probsById("bar").Item("name")
End Sub

Resources