Dim pos As Range, range1 As Range, range2 As Range
Dim x As Variant, Y As Variant
Set pos = Sheets("Sheet1").Cells(5, 6)
For Each y In range1
Set pos = pos.Offset(3, 0) 'Currently setting 3 spaces between each change of Y
For Each x In range2
If y = x Then
x.Cells(, 5).Copy pos
Set pos = pos.Offset(1, 0)
End If
Next x
Next y
Instead of putting spaces of 3, I want to know how many times X is copied for each time that Y changes, how would you do this?
I was thinking a counter, but how would you reset it?
Counting Using an Array or a Dictionary
All solutions are case-insensitive, i.e. A=a.
These are 3 pairs of solutions where each first is using a second loop, while each second is using Application.CountIf as a more efficient way instead.
The second pair (solutions 3 and 4) should be the most efficient, but can only be used if the values in range1 are unique, while the others can be used in any case, the first pair (solutions 1 and 2) probably being more efficient.
Also consider the differences between retrieving the values from the array and retrieving the values from the dictionary.
The Code
Option Explicit
Sub testUniqueDictionaryLoop()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim x As Range
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
For Each y In range1.Cells
If Not dict.Exists(y.Value) Then
dict(y.Value) = 0
For Each x In range2.Cells
If StrComp(x.Value, y.Value, vbTextCompare) = 0 Then
dict(y.Value) = dict(y.Value) + 1
End If
Next x
End If
Next y
' Write to the Immediate window.
Dim Key As Variant
For Each Key In dict.keys
Debug.Print Key, dict(Key)
Next Key
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Columns(1).Resize(dict.Count).Value = Application.Transpose(dict.keys)
.Columns(2).Resize(dict.Count).Value = Application.Transpose(dict.Items)
End With
End Sub
Sub testUniqueDictionaryCountIf()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
For Each y In range1.Cells
If Not dict.Exists(y.Value) Then
dict(y.Value) = Application.CountIf(range2, y.Value)
End If
Next y
' Write to the Immediate window.
Dim Key As Variant
For Each Key In dict.keys
Debug.Print Key, dict(Key)
Next Key
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Columns(1).Resize(dict.Count).Value = Application.Transpose(dict.keys)
.Columns(2).Resize(dict.Count).Value = Application.Transpose(dict.Items)
End With
End Sub
Sub testArrayLoop()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim x As Range
Dim Data As Variant: ReDim Data(1 To range1.Cells.CountLarge, 1 To 2)
Dim i As Long
For Each y In range1.Cells
i = i + 1
Data(i, 1) = y.Value
Data(i, 2) = 0
For Each x In range2.Cells
If StrComp(x.Value, y.Value, vbTextCompare) = 0 Then
Data(i, 2) = Data(i, 2) + 1
End If
Next x
Next y
' Write to the Immediate window.
For i = 1 To UBound(Data, 1)
Debug.Print Data(i, 1), Data(i, 2)
Next i
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Resize(UBound(Data, 1)).Value = Data
End With
End Sub
Sub testArrayCountIf()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim x As Range
Dim Data As Variant: ReDim Data(1 To range1.Cells.CountLarge, 1 To 2)
Dim i As Long
For Each y In range1.Cells
i = i + 1
Data(i, 1) = y.Value
Data(i, 2) = Application.CountIf(range2, Data(i, 1))
Next y
' Write to the Immediate window.
For i = 1 To UBound(Data, 1)
Debug.Print Data(i, 1), Data(i, 2)
Next i
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Resize(UBound(Data, 1)).Value = Data
End With
End Sub
Sub testUniqueArrayLoop()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim x As Range
Dim Data As Variant: ReDim Data(1 To range1.Cells.CountLarge, 1 To 2)
Dim i As Long
For Each y In range1.Cells
If IsError(Application.Match(y.Value, Data, 0)) Then
i = i + 1
Data(i, 1) = y.Value
Data(i, 2) = 0
For Each x In range2.Cells
If StrComp(x.Value, y.Value, vbTextCompare) = 0 Then
Data(i, 2) = Data(i, 2) + 1
End If
Next x
End If
Next y
Dim k As Long: k = i
' Write to the Immediate window.
For i = 1 To k
Debug.Print Data(i, 1), Data(i, 2)
Next i
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Resize(k).Value = Data
End With
End Sub
Sub testUniqueArrayCountIf()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim x As Range
Dim Data As Variant: ReDim Data(1 To range1.Cells.CountLarge, 1 To 2)
Dim i As Long
For Each y In range1.Cells
If IsError(Application.Match(y.Value, Data, 0)) Then
i = i + 1
Data(i, 1) = y.Value
Data(i, 2) = Application.CountIf(range2, Data(i, 1))
End If
Next y
Dim k As Long: k = i
' Write to the Immediate window.
For i = 1 To k
Debug.Print Data(i, 1), Data(i, 2)
Next i
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Resize(k).Value = Data
End With
End Sub
I made a collection object that will add a new counter for every y. In the second loop it increments the last element of the collection by one. At the end if you look at each element in the collection it should contain the number of times x=y per y element.
Dim pos As Range
Dim range1 As Range
Dim range2 As Range
Dim x As Range
Dim y As Range
Dim countXinY As New Collection
Set pos = Sheets("Sheet1").Cells(5, 6)
For Each y In range1
countXinY.Add 0
Set pos = pos.Offset(3, 0) 'Currently setting 3 spaces between each change of Y
For Each x In range2
If y = x Then
countXinY(countXinY.Count) = countXinY(countXinY.Count) + 1
x.Cells(, 5).Copy pos
Set pos = pos.Offset(1, 0)
End If
Next x
Next y
Related
I currently have two spreadsheets: one that pulls data from a SQL Server and is a data dump and one that needs to have those values populated into them. For the sake of simplicity, I've compiled a mini prototype to use for the purpose of my question. Things to note, the data dump sheet will have a varying amount of rows, however the columns will be static which should hopefully make for easy mapping. What I need my macro to be able to accomplish is to
Check if an ID value matches the one directly below it, if so
Check if the Spouse_Indicator field has an "N" or "Y" value
If the indicator is an "N" value then I need the corresponding rows from the employer and title fields to be populated into the student table
If the indicator is a "Y" value then I need the corresponding rows from the employer and title fields to be populated into the spouse table
If there is a sequence where the ID does not match the one directly below it, the data automatically gets populated into the student table
The problem that I am having with the way that my macro is set up is that only the most recent ID with a "N" indicator is getting populated into every cell of the student table whereas I need only unique values to be populated until the last ID has been read. The image attached shows a small sample size of the data, the first table shows what my macro is producing while the last table shows my target. I am also including my code to show what I've gotten so far. Let me know if I need to clarify anything, thanks a bunch.
Sub test2()
Dim wb As Workbook
Dim ws As Worksheet
Dim id As Range
Dim cell As Range
Dim student_employer As Range
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet1")
Set id = ws.Range("A2:A" & Cells(Rows.Count, "A").End(xlUp).Row)
Set student_employer = ws.Range("G3:G8")
For Each cell In id
If cell.Value = cell.Offset(1, 0).Value And cell.Offset(0, 1).Value = "N" Then
cell.Offset(0, 2).Copy student_employer.Cells
End If
Next
MsgBox ("DONE")
End Sub
I've edited my code and it is somewhat capturing what I am trying to accomplish, however I need the values to be pasted into the next empty cell, while mine currently skips the amount of cells depending on when the next copy-paste takes place.
Sub test2()
Dim id As Long
Dim x As Long
Dim emp As Range
Set emp = Range("G3:G8")
id = Cells(Rows.Count, "A").End(xlUp).Row
For x = 2 To id
If Cells(x, 1).Value = Cells(x, 1).Offset(1, 0) Then
Cells(x, 1).Offset(0, 2).Copy Cells(x, 6).Offset(1, 0)
End If
Next x
MsgBox ("DONE")
End Sub
Copy Unique Rows
Adjust the values in the constants section. If you feel like it, use the out-commented constants and sCols to get rid of the 'magic' numbers (1, 2, 3, 4).
For the sake of simplicity, it is assumed that the source data starts in cell A1 (CurrentRegion).
Option Explicit
Sub test1()
Const sName As String = "Sheet1"
'Const sCritCol As Long = 2
'Const sColsList As String = "1,3,4"
'Const scCount As Long = 4
Const sCrit1 As String = "N"
Const sCrit2 As String = "Y"
Const dName As String = "Sheet1"
Const dFirst As String = "F1"
Dim wb As Workbook: Set wb = ThisWorkbook
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim srg As Range: Set srg = sws.Range("A1").CurrentRegion
If srg.Columns.Count <> 4 Then Exit Sub
Dim srCount As Long: srCount = srg.Rows.Count
If srCount = 1 Then Exit Sub
Dim sData As Variant: sData = srg.Value
'Dim sCols() As String: sCols = Split(sColsList, ",")
Dim dict1 As Object: Set dict1 = CreateObject("Scripting.Dictionary")
dict1.CompareMode = vbTextCompare
Dim dict2 As Object: Set dict2 = CreateObject("Scripting.Dictionary")
dict2.CompareMode = vbTextCompare
Dim cDat As Variant: ReDim cDat(1 To 3)
Dim Key As Variant
Dim sCrit As Variant
Dim r As Long
For r = 2 To srCount
sCrit = sData(r, 2)
Select Case UCase(CStr(sCrit))
Case sCrit1
Key = sData(r, 1)
If Not dict1.Exists(Key) Then
cDat(1) = sData(r, 1)
cDat(2) = sData(r, 3)
cDat(3) = sData(r, 4)
dict1(Key) = cDat
End If
Case sCrit2
Key = sData(r, 1)
If Not dict2.Exists(Key) Then
cDat(1) = sData(r, 1)
cDat(2) = sData(r, 3)
cDat(3) = sData(r, 4)
dict2(Key) = cDat
End If
End Select
Next r
Dim drCount As Long: drCount = dict1.Count + dict2.Count + 4
Dim dData As Variant: ReDim dData(1 To drCount, 1 To 3)
r = 1
dData(r, 1) = "student"
r = r + 1
dData(r, 1) = sData(1, 1)
dData(r, 2) = sData(1, 3)
dData(r, 3) = sData(1, 4)
Dim n As Long
If dict1.Count > 0 Then
For Each Key In dict1.Keys
r = r + 1
For n = 1 To 3
dData(r, n) = dict1(Key)(n)
Next n
Next Key
End If
r = r + 1
dData(r, 1) = "spouse"
r = r + 1
dData(r, 1) = sData(1, 1)
dData(r, 2) = sData(1, 3)
dData(r, 3) = sData(1, 4)
If dict2.Count > 0 Then
For Each Key In dict2.Keys
r = r + 1
For n = 1 To 3
dData(r, n) = dict2(Key)(n)
Next n
Next Key
End If
Application.ScreenUpdating = False
' Write.
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim drg As Range: Set drg = dws.Range(dFirst).Resize(r, 3)
drg.Clear ' because of merged cells
drg.Value = dData
' Clear below.
Dim crg As Range
Set crg = drg.Resize(dws.Rows.Count - drg.Row - r + 1).Offset(r)
crg.Clear
' Format 'student'
With drg.Rows(1)
.Cells(1).Font.Bold = True
.MergeCells = True
.HorizontalAlignment = xlCenter
.BorderAround xlContinuous, xlThick
End With
' Format 'spouse'.
With drg.Rows(dict1.Count + 3)
.Cells(1).Font.Bold = True
.MergeCells = True
.HorizontalAlignment = xlCenter
.BorderAround xlContinuous, xlThick
End With
' Format all.
drg.EntireColumn.AutoFit
Application.ScreenUpdating = True
MsgBox ("DONE")
End Sub
I have a table ("horiz") with following values
and table ("data") that shows different values per column
I want to make a VBA code that will save table "data" as following.
Basically looking for a code, which can do it in the following way:
1)load "horiz" values as an array
2)load "data" as a range
3)delete all zero values from "horiz" array
4)save the "data" table with column indexes that follow the values from array "horiz"
I tried the following code, however, the saving part is not working properly and do not know how to delete zeros in 3) step (I read that something should be done with If condition and ReDim function)
Sub sample()
Dim DirArray As Variant
DirArray = Range("horiz").Value
Dim rng As Range
Set rng = Range("data")
Worksheets("Sheet1").Range("L1").Cells.Value = rng.Cells(, DirArray).Value
End Sub
Copy 'Selected' Columns
Option Explicit
Sub copySelectedColumns()
Dim srg As Range: Set srg = Range("horiz") ' Select Range
Dim cCount As Long: cCount = Application.CountIf(srg, ">0") ' Columns Count
Dim sData As Variant: sData = srg.Value ' Select Data (Array)
Dim Data As Variant: Data = Range("data").Value ' Data
Dim ColData As Variant: ReDim ColData(1 To cCount) ' Column Data (Array)
Dim n As Long, c As Long
For n = 1 To UBound(sData, 2)
If sData(1, n) > 0 Then
c = c + 1
ColData(c) = sData(1, n)
End If
Next n
Dim rCount As Long: rCount = UBound(Data, 1)
Dim Result As Variant: ReDim Result(1 To rCount, 1 To cCount) ' Result
Dim r As Long
For r = 1 To rCount
For c = 1 To cCount
Result(r, c) = Data(r, ColData(c))
Next c
Next r
Worksheets("Sheet1").Range("L1").Resize(rCount, cCount).Value = Result
End Sub
EDIT
The improvement is about not allowing impossible columns (greater than the number of columns in the Data Range (0 was previously included)) and clearing the contents of a previous result.
The small range study is about writing the addresses of the four ranges to the Immediate window (CTRL+G).
An Improvement feat. a Small Range Study
Sub copySelectedColumns()
Debug.Print "***** The Ranges *****"
Dim srg As Range: Set srg = Range("horiz") ' Select Range
Debug.Print "Select Range: " & srg.Address(0, 0)
Dim sData As Variant: sData = srg.Value ' Select Data (Array)
Dim sCount As Long: sCount = UBound(sData, 2) ' Select Columns Count
Dim drg As Range: Set drg = Range("data") ' Data Range
Debug.Print "Data Range: " & drg.Address(0, 0)
Dim Data As Variant: Data = drg.Value ' Data
Dim dCount As Long: dCount = UBound(Data, 2) ' Data Columns Count
Dim ColData As Variant: ReDim ColData(1 To sCount) ' Column Data (Array)
Dim n As Long, c As Long
For n = 1 To sCount
If sData(1, n) > 0 And sData(1, n) <= dCount Then
c = c + 1
ColData(c) = sData(1, n)
End If
Next n
If c > 0 Then
Dim cCount As Long: cCount = c
Dim rCount As Long: rCount = UBound(Data, 1)
Dim Result As Variant: ReDim Result(1 To rCount, 1 To cCount) ' Result
Dim r As Long
For r = 1 To rCount
For c = 1 To cCount
Result(r, c) = Data(r, ColData(c))
Next c
Next r
With Worksheets("Sheet1").Range("L2")
' Clear contents of previous result.
Dim crg As Range ' Clear Range
Set crg = .Resize(.Worksheet.Rows.Count - .Row + 1, sCount)
Debug.Print "Clear Range: " & crg.Address(0, 0)
crg.ClearContents
' Write result.
Dim rrg As Range: Set rrg = .Resize(rCount, cCount) ' Result Range
Debug.Print "Result Range: " & rrg.Address(0, 0)
rrg.Value = Result
End With
Else
' all values in Select Range are invalid
' (0 or greater than Data Columns Count (dCount))
Debug.Print "The Select Range '" & srg.Address(0, 0) & "' contains " _
& "only invalid data."
End If
End Sub
Try:
Sub cut_paste_delete()
Dim ArrayHeader As Variant
Dim ArrayData As Variant
Dim FinalArray As Variant
Dim i As Long
Dim ZZ As Long
Dim vColumn As Long
ArrayHeader = Range("horiz").Value
ArrayData = Range("data").Value
i = Application.WorksheetFunction.CountIf(Range("horiz"), "<>0") 'how many valid columns
ReDim FinalArray(1 To UBound(ArrayData), 1 To i) As Variant
For i = 1 To 5 Step 1
If ArrayHeader(1, i) <> 0 Then
vColumn = vColumn + 1
For ZZ = 1 To UBound(ArrayData) Step 1
FinalArray(ZZ, vColumn) = ArrayData(ZZ, i)
Next ZZ
End If
Next i
'paste final array somewhere, in my case in P1
Range(Cells(1, 16), Cells(1 + ZZ - 2, 16 + vColumn - 1)).Value = FinalArray
Erase ArrayHeader, ArrayData, FinalArray
End Sub
The output i get afcter executing code:
Another approach could be
Sub CopyRg()
Dim rgKeep As Range
Dim rgData As Range
Dim rgResult As Range
Set rgKeep = Range("B2").CurrentRegion
Set rgData = Range("D7").CurrentRegion
Dim i As Long
i = 1
Dim sngColumn As Range
For Each sngColumn In rgData.Columns
If rgKeep.Columns(i).Value <> 0 Then
If rgResult Is Nothing Then
Set rgResult = sngColumn
Else
Set rgResult = Union(rgResult, sngColumn)
End If
End If
i = i + 1
Next sngColumn
rgResult.Copy
Range("B12").PasteSpecial
End Sub
with the following data (input and output)
The code does not transfer the data into arrays which could be slow for large datasets but on the other hands it only loops through the columns.
I have an Excel Sheet where some rows may contain the same data as other rows. I need a macro to sum all the values in that column and delete all the duplicates rows, except for the first one, which contains the sum of the rest.
I have tried multiple versions of code and the code that produces the results closest to what I need looks like this, but this code contains one problem is: infinite loop.
Sub delet()
Dim b As Integer
Dim y As Worksheet
Dim j As Double
Dim k As Double
Set y = ThisWorkbook.Worksheets("Sheet1")
b = y.Cells(Rows.Count, 2).End(xlUp).Row
For j = 1 To b
For k = j + 1 To b
If Cells(j, 2).Value = Cells(k, 2).Value Then
Cells(j, 3).Value = (Cells(j, 3).Value + Cells(k, 3).Value)
Rows(k).EntireRow.Delete
k = k - 1
ElseIf Cells(j, 2).Value <> Cells(k, 2).Value Then
k = k
End If
Next
Next
End Sub
I would recommend getting the data in an array and then do the relevant operation. This is a small range and it may not affect the performance but for a larger dataset it will matter.
Is this what you are trying?
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long, i As Long, j As Long
Dim MyAr As Variant, outputAr As Variant
Dim col As New Collection
Dim itm As Variant
Dim totQty As Double
'~~> Change this to the relevant sheet
Set ws = Sheet1
With ws
'~~> Find last row of col A
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Get those value in an array
MyAr = .Range("A2:C" & lRow).Value2
'~~> Get unique collection of Fam.
For i = LBound(MyAr) To UBound(MyAr)
If Len(Trim(MyAr(i, 2))) <> 0 Then
On Error Resume Next
col.Add MyAr(i, 2), CStr(MyAr(i, 2))
On Error GoTo 0
End If
Next i
'~~> Prepare array for output
ReDim outputAr(1 To col.Count, 1 To 3)
i = 1
For Each itm In col
'~~> Get Product
For j = LBound(MyAr) To UBound(MyAr)
If MyAr(i, 2) = itm Then
outputAr(i, 1) = MyAr(i, 1)
Exit For
End If
Next j
'~~> Fam.
outputAr(i, 2) = itm
totQty = 0
'~~> Qty
For j = LBound(MyAr) To UBound(MyAr)
If MyAr(j, 2) = itm Then
totQty = totQty + Val(MyAr(j, 3))
End If
Next j
outputAr(i, 3) = totQty
i = i + 1
Next itm
'~~> Copy headers
.Range("A1:C1").Copy .Range("G1")
'~~> Write array to relevant range
.Range("G2").Resize(UBound(outputAr), 3).Value = outputAr
End With
End Sub
Output
If VBA isn't essential and you've got 365:
In cell G2 enter the formula =UNIQUE(A2:B11)
In cell I2 enter the formula =SUMIFS(C2:C11,A2:A11,INDEX(G2#,,1),B2:B11,INDEX(G2#,,2))
Remove Duplicates with Sum
Adjust the values in the constants section.
Note that if you choose the same worksheets and "A1", you will overwrite.
The Code
Option Explicit
Sub removeDupesSum()
Const sName As String = "Sheet1"
Const dName As String = "Sheet1"
Const dFirst As String = "G1"
Dim wb As Workbook: Set wb = ThisWorkbook
' Write values from Source Range to Data Array.
Dim Data As Variant
Data = wb.Worksheets(sName).Cells(1).CurrentRegion.Value
' Write unique values from Data Array to Unique Sum Dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim arr As Variant: ReDim arr(2 To UBound(Data, 1)) ' for first column
Dim n As Long: n = 1
Dim i As Long
For i = 2 To UBound(Data, 1)
If dict.Exists(Data(i, 2)) Then
dict(Data(i, 2)) = dict(Data(i, 2)) + Data(i, 3)
Else
n = n + 1
arr(n) = Data(i, 1)
dict(Data(i, 2)) = Data(i, 3)
End If
Next i
Dim Result As Variant: ReDim Result(1 To dict.Count + 1, 1 To 3)
' Write headers.
For i = 1 To 3
Result(1, i) = Data(1, i)
Next i
Erase Data
' Write 'body'.
Dim Key As Variant
i = 1
For Each Key In dict.Keys
i = i + 1
Result(i, 1) = arr(i)
Result(i, 2) = Key
Result(i, 3) = dict(Key)
Next Key
' Write values from Result Array to Destination Range.
With wb.Worksheets(dName).Range(dFirst).Resize(, 3)
.Resize(i).Value = Result
.Resize(.Worksheet.Rows.Count - .Row - i + 1).Offset(i).ClearContents
End With
End Sub
I have a code which goes trough a big database and checks for first 2 letters , if they answer a certain term they are getting replaced with certain integer ...
i want to duplicate the row which has "RE" as string in the first 2 letters and replace the "RE" to "821" and the duplicated row to "841"
Here is the code i have now..
Sub Swap()
Dim i As Long, N As Long, v As Variant, frst As String
Call deletes
Application.ScreenUpdating = False
N = Cells(Rows.Count, "A").End(xlUp).Row
For i = 1 To N
v = Cells(i, "A").Value
If Len(v) >= 2 Then
frst = Left(v, 2)
If frst = "RE" Then
Cells(i, "A").Value = Replace(v, "RE", "821")
ElseIf frst = "NI" Then
Cells(i, "A").Value = Replace(v, "NI", "801")
ElseIf frst = "NF" Then
Cells(i, "A").Value = Replace(v, "NF", "831")
ElseIf frst = "NV" Then
Cells(i, "A").Value = Replace(v, "NV", "571")
End If
End If
Next i
End Sub
Multiple Replacements
The code is written in this way to allow different lengths of the strings to be searched for (Lens - Lengths Array). It is understood that the replacements can be of any 'allowed' length.
Adjust the values in the constants section. Also, rather qualify the range and its worksheet e.g.:
With ThisWorkbook.Worksheets("Sheet1").Range(FirstCellAddress)
The Code
Option Explicit
Sub Swap()
Const FirstCellAddress As String = "A2"
Const SrcList As String = "RE,NI,NF,NV"
Const FstList As String = "821,801,831,571"
Const SndList As String = "841,811,851,591"
' Define range.
Dim rg As Range
With Range(FirstCellAddress)
Set rg = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If rg Is Nothing Then Exit Sub
Set rg = .Resize(rg.Row - .Row + 1)
End With
' Write values from range to Data Array.
Dim rCount As Long: rCount = rg.Rows.Count
Dim Data As Variant
If rCount > 1 Then
Data = rg.Value
Else
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
End If
' Write lists to arrays.
Dim Src() As String: Src = Split(SrcList, ",")
Dim Fst() As String: Fst = Split(FstList, ",")
Dim Snd() As String: Snd = Split(SndList, ",")
' Write lenghts of values in Search Array to Lengths Array.
Dim sUpper As Long: sUpper = UBound(Src)
Dim n As Long
Dim Lens() As Long: ReDim Lens(0 To sUpper)
For n = 0 To sUpper
Lens(n) = Len(Src(n))
Next n
' Define Unique Dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' '1' is used in 'StrComp' and 'Replace'.
Dim i As Long
Dim cValue As Variant
' Modify (replace) values in Data Array (using Unique Dictionary,
' test if first or second replacement is to be used).
For i = 1 To rCount
cValue = Data(i, 1)
If Not IsError(cValue) Then
For n = 0 To sUpper
If StrComp(Left(cValue, Lens(n)), Src(n), 1) = 0 Then
If dict.Exists(cValue) Then
Data(i, 1) = Replace(cValue, Src(n), Snd(n), , 1, 1)
Else
dict.Add cValue, Empty
Data(i, 1) = Replace(cValue, Src(n), Fst(n), , 1, 1)
End If
Exit For
End If
Next n
Else ' error value
End If
Next i
Set dict = Nothing
' Write modified values from Data Array to range.
rg.Value = Data
End Sub
I have a list of lists in Excel. There is some specifications (name, age, country, etc) in the first column and values in the second column. I don't want to repeat the same specifications over and over again. What I want to show in the picture. I tried =VLOOKUP() but it did not work perfectly because the lists do not include the same specifications. How can I achieve this?
A VBA macro can generate the results, and also the list of parameters for the first column of results.
To enter this Macro (Sub), alt-F11 opens the Visual Basic Editor.
Ensure your project is highlighted in the Project Explorer window.
Then, from the top menu, select Insert/Module and
paste the code below into the window that opens.
Be sure to set the Reference as stated in the Note in the macro
To use this Macro (Sub), alt-F8 opens the macro dialog box. Select the macro by name, and RUN.
This macro generates the list with the parameter list in the first column. It could be easily rewritten to have the parameter list in the first row, if that is preferable.
Option Explicit
'Set Reference to Microsoft Scripting Runtime
Sub GroupLists()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim dictParams As Dictionary
Dim sParam As String
Dim I As Long, J As Long, K As Long
Dim V As Variant
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet1")
Set rRes = wsRes.Cells(1, 5)
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=2)
End With
'Get unique list of Parameters with row number
'Also count the number of entries for number of columns in final result
J = 0
Set dictParams = New Dictionary
K = 0 'row number for parameter
For I = 1 To UBound(vSrc, 1)
J = J + 1 'column count
Do
If Not dictParams.Exists(vSrc(I, 1)) Then
K = K + 1
dictParams.Add Key:=vSrc(I, 1), Item:=K
End If
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = ""
If I > UBound(vSrc) Then Exit For
Next I
'Create results array
ReDim vRes(1 To dictParams.Count, 1 To J + 1)
'Populate Column 1
For Each V In dictParams.Keys
vRes(dictParams(V), 1) = V
Next V
'Populate the data
J = 1 'column number
For I = 1 To UBound(vSrc, 1)
J = J + 1
Do
sParam = vSrc(I, 1)
vRes(dictParams(sParam), J) = vSrc(I, 2)
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = ""
If I > UBound(vSrc) Then Exit For
Next I
'Write the results
Set rRes = rRes.Resize(UBound(vRes, 1), UBound(vRes, 2))
rRes.EntireColumn.Clear
rRes = vRes
End Sub
EDIT: Macro modified to reflect the "real data"
Please note: You will need to add a second worksheet for the results. I named it "Sheet2"
Option Explicit
'Set Reference to Microsoft Scripting Runtime
Sub GroupLists()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim dictParams As Dictionary
Dim sParam As String
Dim I As Long, J As Long, K As Long
Dim V As Variant
Dim sDelim As String 'Differentiates each record
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet2")
Set rRes = wsRes.Cells(1, 1)
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=2)
sDelim = vSrc(1, 1)
End With
'Get unique list of Parameters with row number
'Also count the number of entries for number of columns in final result
J = 0
Set dictParams = New Dictionary
K = 0 'row number for parameter
For I = 1 To UBound(vSrc, 1)
J = J + 1 'column count
Do
If Not dictParams.Exists(vSrc(I, 1)) Then
K = K + 1
dictParams.Add Key:=vSrc(I, 1), Item:=K
End If
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = sDelim
If I > UBound(vSrc) Then
Exit For
Else
I = I - 1
End If
Next I
'Create results array
ReDim vRes(1 To dictParams.Count, 1 To J + 1)
'Populate Column 1
For Each V In dictParams.Keys
vRes(dictParams(V), 1) = V
Next V
'Populate the data
J = 1 'column number
For I = 1 To UBound(vSrc, 1)
J = J + 1
Do
sParam = vSrc(I, 1)
vRes(dictParams(sParam), J) = vSrc(I, 2)
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = sDelim
If I > UBound(vSrc) Then
Exit For
Else
I = I - 1
End If
Next I
'Write the results
Set rRes = rRes.Resize(UBound(vRes, 1), UBound(vRes, 2))
rRes.EntireColumn.Clear
rRes = vRes
End Sub
EDIT2: This macro is a modification of the above, which lists the results in the opposite orientation. It may be more useful.
Option Explicit
'Set Reference to Microsoft Scripting Runtime
Sub GroupListsVertical()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim dictParams As Dictionary
Dim sParam As String
Dim I As Long, J As Long, K As Long
Dim V As Variant
Dim sDelim As String 'Differentiates each record
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet3")
Set rRes = wsRes.Cells(1, 1)
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=2)
sDelim = vSrc(1, 1)
End With
'Get unique list of Parameters with row number
'Also count the number of entries for number of columns in final result
J = 0
Set dictParams = New Dictionary
K = 0 'column number for parameter
For I = 1 To UBound(vSrc, 1)
J = J + 1 'row count
Do
If Not dictParams.Exists(vSrc(I, 1)) Then
K = K + 1
dictParams.Add Key:=vSrc(I, 1), Item:=K
End If
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = sDelim
If I > UBound(vSrc) Then
Exit For
Else
I = I - 1
End If
Next I
'Create results array
ReDim vRes(1 To J + 1, 1 To dictParams.Count)
'Populate row 1
For Each V In dictParams.Keys
vRes(1, dictParams(V)) = V
Next V
'Populate the data
J = 1 'row number
For I = 1 To UBound(vSrc, 1)
J = J + 1
Do
sParam = vSrc(I, 1)
vRes(J, dictParams(sParam)) = vSrc(I, 2)
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = sDelim
If I > UBound(vSrc) Then
Exit For
Else
I = I - 1
End If
Next I
'Write the results
Set rRes = rRes.Resize(UBound(vRes, 1), UBound(vRes, 2))
rRes.EntireColumn.Clear
rRes = vRes
rRes.EntireColumn.AutoFit
End Sub
Use following ARRAY formulas.
Cell F2 formula
=IFERROR(INDEX($B$1:$B$20,SMALL(IF($A$1:$A$20=$E2,ROW($B$1:$B$20),""),COLUMN(A:A))),"")
Cell E19 formula
=IFERROR(INDEX($B$1:$B$20,SMALL(IF($A$1:$A$20=$E2,ROW($B$1:$B$20),""),COLUMN(A:A))),"")
Press CTRL+SHIFT+ENTER to evaluate the formula as it is an array formula.