The following code does not remove any duplicate, what am I missing ?
LastColumn = 10
ws.Range(ws.Cells(1, ws.Range("AY1").Column + LastColumn - 1).Address(), ws.Cells(1, "AY").Address()).RemoveDuplicates
I replaced RemoveDuplicates by .Select to check if the excepted range was selected and it was.
Please, test the next way. It will keep only the first occurrences and replace with empty cells the next duplicates. The processed result is returned on the next (second) row (for testing reason). If it works as you need, you can simple replace ws.Range("AY2").Resize with ws.Range("AY1").Resize:
Sub removeDuplicatesOnRow()
Dim ws As Worksheet, lastColumn As Long, arrCol, i As Long
lastColumn = 10
Set ws = ActiveSheet
arrCol = ws.Range(ws.cells(1, ws.Range("AY1").Column + lastColumn - 1), ws.cells(1, "AY")).value
arrCol = removeDuplKeepEmpty(arrCol)
ws.Range("AY2").Resize(1, UBound(arrCol, 2)).value = arrCol
End Sub
Function removeDuplKeepEmpty(arr) As Variant
Dim ar, dict As Object, i As Long
ReDim ar(1 To 1, 1 To UBound(arr, 2))
Set dict = CreateObject("Scripting.Dictionary")
For i = 1 To UBound(arr, 2)
If Not dict.Exists(arr(1, i)) Then
dict(arr(1, i)) = 1
ar(1, i) = arr(1, i)
Else
ar(1, i) = ""
End If
Next i
removeDuplKeepEmpty = ar
End Function
If you need to keep only unique values/strings in consecutive columns, the function can be adapted to do it. You did not answer my clarification question on the issue and I assumed that you do not want ruining the columns below the processed row. But, if my supposition is wrong, I can post a code doing the other way...
Remove Row Duplicates
Option Explicit
Sub RemoveRowDuplicates()
Dim ws As Worksheet: Set ws = ActiveSheet ' be more specific
Dim fCell As Range: Set fCell = ws.Range("AY1")
Dim lCell As Range: Set lCell = ws.Cells(1, ws.Columns.Count).End(xlToLeft)
If lCell.Column < fCell.Column Then Exit Sub ' no data in row range
Dim rg As Range: Set rg = ws.Range(fCell, lCell)
Dim cCount As Long: cCount = rg.Columns.Count
If cCount < 2 Then Exit Sub ' only one column
Dim sData As Variant: sData = rg.Value ' Source
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' ignore case i.e. 'A = a'
Dim dData As Variant: ReDim dData(1 To 1, 1 To cCount) ' Dest. (Result)
Dim sValue As Variant
Dim sc As Long
Dim dc As Long
For sc = 1 To cCount
sValue = sData(1, sc)
If Not IsError(sValue) Then ' is not an error value
If Len(sValue) > 0 Then ' is not blank
If Not dict.Exists(sValue) Then ' not found in dictionary
dict(sValue) = Empty
dc = dc + 1
dData(1, dc) = sValue
'Else ' found in dictionary
End If
'Else ' is blank
End If
'Else ' is error value
End If
Next sc
rg.Value = dData
MsgBox "Found " & dc & " unique values.", vbInformation
End Sub
Related
I have two macros that I would like to combine but somehow its not going well...
I want a macro that will get only unique values from a range and input them into another sheet every second row starting from row no 3
Could anyone tell me how should I combine those two macros?
I have tried to change .Font.Size = 20 with Application.Transpose(objDict.keys) but it didn't work.
Sub UniqueValue()
Dim X
Dim objDict As Object
Dim lngRow As Long
Set objDict = CreateObject("Scripting.Dictionary")
X = Application.Transpose(Range([a1], Cells(Rows.Count, "A").End(xlUp)))
For lngRow = 1 To UBound(X, 1)
objDict(X(lngRow)) = 1
Next
Range("F1:F" & objDict.Count) = Application.Transpose(objDict.keys)
End Sub
Sub EverySecond()
Dim EndRow As Long
EndRow = Range("A" & Rows.Count).End(xlUp).Row
For ColNum = 5 To EndRow Step 2
Range(Cells(ColNum, 2), Cells(ColNum, 2)).Font.Size = 20
Next ColNum
End Sub
Copy Unique Values to Every Other Row
Option Explicit
Sub UniqueEveryOther()
Const sName As String = "Sheet1"
Const sFirstCellAddress As String = "A2"
Const dName As String = "Sheet2"
Const dFirstCellAddress As String = "A2"
Dim wb As Workbook: Set wb = ThisWorkbook
' Reference the source range.
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim srg As Range
Dim srCount As Long
With sws.Range(sFirstCellAddress)
Dim lCell As Range: Set lCell = .Resize(sws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Sub
srCount = lCell.Row - .Row + 1
Set srg = .Resize(srCount)
End With
' Write the values from the source range to an array.
1
Dim Data As Variant
If srCount = 1 Then ' one cell
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = srg.Value
Else ' multiple cells
Data = srg.Value
End If
' Write the unqiue values from the array to a dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim Key As Variant
Dim r As Long
For r = 1 To srCount
Key = Data(r, 1)
If Not IsError(Key) Then
If Len(Key) > 0 Then
dict(Key) = Empty
End If
End If
Next r
If dict.Count = 0 Then Exit Sub
' Write the unqiue values from the dictionary to the array.
ReDim Data(1 To 2 * dict.Count - 1, 1 To 1)
r = -1
For Each Key In dict.Keys
r = r + 2
Data(r, 1) = Key
Next Key
' Write the unique values from the array to the destination range.
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
With dws.Range(dFirstCellAddress)
.Resize(r).Value = Data
.Resize(dws.Rows.Count - .Row - r + 1).Offset(r).Clear
'.EntireColumn = AutoFit
End With
'wb.Save
MsgBox "Uniques populated.", vbInformation
End Sub
My Intention:
I wanna select all values in Rows "B" and "C" and move these 1 and 2 steps up.
The Example for what I have:
A
B
C
AA
Two
AA
Three
Two
AA
Three
Two
Three
X
yy
CC
The Example for what I would: If in Column-A find "X" should YY and CC delet
A
B
C
AA
Two
Three
AA
Two
Three
AA
Two
Three
My Code:
Sub test()
ActiveSheet.Select
Range("B:B").Select Shift:=xlUp, CopyOrigin:=xlFormatFromLeftOrAbove
Range("C:C").Select Shift:=xlUp, CopyOrigin:=xlFormatFromLeftOrAbove
End Sub
I would be happy if somebody help me
Align Data
Option Explicit
Sub AlignData()
Const Cols As String = "A:C"
Const fRow As Long = 1
Const ExceptionsList As String = "XX" ' comma-separated, no spaces!
Const Gap As Long = 1 ' number of empty rows in-between
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
' Reference the source range.
Dim srg As Range
Dim srCount As Long
With ws.Rows(fRow).Columns(Cols).Resize(ws.Rows.Count - fRow + 1)
Dim lrCell As Range
Set lrCell = .Find("*", , xlFormulas, , xlByRows, xlPrevious)
If lrCell Is Nothing Then Exit Sub ' no data
srCount = lrCell.Row - fRow + 1
Set srg = .Resize(srCount)
End With
Dim cCount As Long: cCount = srg.Columns.Count
' 1 to hold each column array
' 2 to hold a collection of each column's matching values
Dim jArr As Variant: ReDim jArr(1 To cCount, 1 To 2)
Dim Exceptions() As String: Exceptions = Split(ExceptionsList, ",")
Dim crg As Range
Dim c As Long
Dim r As Long
Dim sValue As Variant
' Write the column arrays to the jagged array.
For c = 1 To cCount
jArr(c, 1) = srg.Columns(c).Value ' column arrays
Set jArr(c, 2) = New Collection ' to hold the matching values
Next c
' Use a dictionary to hold the indexes of (unwanted) exception matches.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim dStep As Long: dStep = Gap + 1
Dim dData As Variant
Dim drCount As Long
Dim dr As Long
Dim sr As Long
For c = 1 To cCount
dr = 0
For sr = 1 To srCount
sValue = jArr(c, 1)(sr, 1)
If Not IsEmpty(sValue) Then ' exclude empty values
dr = dr + 1
If c = 1 Then ' 1st array
If IsError(Application.Match(sValue, Exceptions, 0)) Then
jArr(c, 2).Add sValue
Else ' found in exceptions
dict(dr) = Empty ' add the index
End If
Else ' all but the 1st array
If Not dict.Exists(dr) Then
jArr(c, 2).Add sValue
End If
End If
End If
Next sr
' Write the values from the collection to the destination array.
If c = 1 Then
drCount = jArr(c, 2).Count * dStep - 1
ReDim dData(1 To drCount, 1 To cCount)
End If
For sr = 1 To drCount Step dStep
dData(sr, c) = jArr(c, 2)(Int(sr / dStep) + 1)
Next sr
Set jArr(c, 2) = Nothing
Next c
' Write the values from the destination array to the range and clear below.
With srg.Resize(drCount)
.Value = dData
.Resize(ws.Rows.Count - .Row - drCount + 1).Offset(drCount).Clear
End With
End Sub
select all cells (A1:C5) and start the following macro
Sub FillCells()
Dim rngCell As Range
Do Until Application.WorksheetFunction.CountBlank(Selection) = 0
For Each rngCell In Selection
If rngCell.Value = "" Then
rngCell.Value = rngCell.Offset(1, 0).Value
End If
Next rngCell
Loop
End Sub
Best regards
Bernd
I have some code that works okay on a small data set, however, I'm looking for the most efficient way to handle this over in 100k+ rows.
The data is in two columns. In column B, wherever "Orange" is listed, I would like to copy/paste "Orange" into column A and replace "Citrus" for that row.
Here is my current code. I think it has some unnecessary bits in it now since I was trying to find a way to copy and paste all of the found cells at once.
SearchStr = "Orange"
Set SearchRng = Range("b2:b11)
With SearchRng
Set FoundCell = .Find(SearchStr, LookIn:=xlValues, LookAt:=xlPart)
If Not FoundCell Is Nothing Then
FirstAdd = FoundCell.Address
Do
If Not AllFoundCells Is Nothing Then
Set AllFoundCells = Union(AllFoundCells, FoundCell)
Else
Set AllFoundCells = FoundCell
End If
FoundCell.Copy Destination:=FoundCell.Offset(0, -1)
Set FoundCell = .FindNext(FoundCell)
Loop While FoundCell.Address <> FirstAdd
End If
End With
Replace If Match in Column
If a string (sString) is found in a column (sCol), then write another string (dString (in this case dString = sString)) to another column (dCol).
On my sample data of 1M rows (>200k of matches), it took less than 2s for the 'AutoFilter' solution and it took about 4s for the 'Array Loop' solution (3s for writing back to the range: drg.Value = dData).
Option Explicit
Sub UsingAutoFilter()
' Source
Const sCol As String = "B"
Const sString As String = "Orange"
' Destination
Const dCol As String = "A"
Const dString As String = "Orange"
' Both
Const hRow As Long = 1 ' Header Row
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
If ws.AutoFilterMode Then ws.AutoFilterMode = False
Dim lRow As Long: lRow = ws.Cells(ws.Rows.Count, sCol).End(xlUp).Row
If lRow < hRow + 1 Then Exit Sub ' no data or just headers
Dim rCount As Long: rCount = lRow - hRow + 1
Dim srg As Range: Set srg = ws.Cells(hRow, sCol).Resize(rCount)
Dim sdrg As Range: Set sdrg = srg.Resize(srg.Rows.Count - 1).Offset(1)
srg.AutoFilter 1, sString
Dim sdvrg As Range
On Error Resume Next
Set sdvrg = sdrg.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
ws.AutoFilterMode = False
If sdvrg Is Nothing Then Exit Sub ' no match found
Dim ddvrg As Range
Set ddvrg = sdvrg.Offset(, ws.Columns(dCol).Column - srg.Column)
ddvrg.Value = dString
End Sub
Sub UsingArrayLoop()
' Source
Const sCol As String = "B"
Const sString As String = "Orange"
' Destination
Const dCol As String = "A"
Const dString As String = "Orange"
' Both
Const fRow As Long = 2 ' First Data Row
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
Dim lRow As Long: lRow = ws.Cells(ws.Rows.Count, sCol).End(xlUp).Row
If lRow < fRow Then Exit Sub ' no data
Dim rCount As Long: rCount = lRow - fRow + 1
Dim srg As Range: Set srg = ws.Cells(fRow, sCol).Resize(rCount)
Dim drg As Range: Set drg = srg.EntireRow.Columns(dCol)
Dim sData As Variant
Dim dData As Variant
If rCount = 1 Then
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = srg.Value
ReDim dData(1 To 1, 1 To 1): dData(1, 1) = drg.Value
Else
sData = srg.Value
dData = drg.Value
End If
Dim r As Long
For r = 1 To rCount
If StrComp(CStr(sData(r, 1)), sString, vbTextCompare) = 0 Then
dData(r, 1) = dString
End If
Next r
Erase sData
drg.Value = dData
End Sub
Should be quicker than copy-paste:
Sub Tester()
Dim rw As Long, f As String
With ActiveSheet
rw = .Cells(.Rows.Count, "B").End(xlUp).Row
f = Replace("=IF(B2:B<rw>=""Orange"",B2:B<rw>,A2:A<rw>)", "<rw>", rw)
.Range("A2:A" & rw).value = .Evaluate(f) 'edited to remove `Application`
End With
End Sub
About 0.2sec for 100k rows
Evaluate() takes a worksheet function and evaluates it in the context of either the ActiveSheet (if you use the Application.Evaluate form) or a specific worksheet (if you use the WorkSheet.Evaluate form). It handles array formulas (no need to add the {}), and can return an array as the result (which here we just assign directly to the ColA range)
Imagine we have a Listbox and a number of visible cells after applying a filter.
I want to display the visible cells in the listbox.
I tried to copy those cells in some array then use this array to fill the listbox using .list property.
With Worksheets("Sheet1")
'' LastRow = 101 because I have a table of data with 101 rows (including headers )and 6 columns
LastRow = Cells(Rows.count, 1).End(xlUp).Row
'' lastCol = 6 because I have a table of data with 101 rows and 6 columns
lastCol = Cells(1, Columns.count).End(xlToLeft).Column
Dim arr1()
i = 0
j = 0
Dim s As Range
Set s = .Range("A2:F" & LastRow).SpecialCells(xlCellTypeVisible)
'' s contains the visible cells after autofilter
ncol = s.Columns.count
nrow = Application.WorksheetFunction.Subtotal(2, .Range("A2:A" & .Rows(.Rows.count).End(xlUp).Row))
'' nrow is the number of visible rows in the s range
MsgBox "lastrow " & LastRow
MsgBox "ncol is " & ncol
MsgBox "nrow" & nrow
ReDim arr1(1 To nrow, 1 To ncol)
'' counters to loop through the array arr1
Dim Currentrow
Dim Currentcol
Currentrow = 1
Currentcol = 1
On Error Resume Next
For Each cell In .Range("A2:F" & LastRow).SpecialCells(12)
While (Currentrow < nrow)
For Currentcol = 1 To ncol
arr1(Currentrow, Currentcol) = cell
MsgBox arr1(Currentrow, Currentcol)
Next
Currentrow = Currentrow + 1
Wend
Next
On Error GoTo 0
''arr1 = s.Value
UserForm1.ListBox2.list = arr1
End With
Get Filtered Data
Standard Module e.g. Module1
Option Explicit
Sub LoadData()
Const CritCol As Long = 1
Const Criteria As String = "No"
Dim wb As Workbook: Set wb = ThisWorkbook
Dim ws As Worksheet: Set ws = wb.Worksheets("Sheet1")
Dim dData As Variant: dData = GetFilteredData(ws, CritCol, Criteria)
If IsEmpty(dData) Then Exit Sub
Dim cCount As Long: cCount = UBound(dData, 2) ' - LBound(dData, 2) + 1
With UserForm1.ListBox2
.Clear
.ColumnCount = cCount
.List = dData
End With
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a filtered range in a 2D one-based array.
' Remarks: The range has to be a contiguous range starting in cell 'A1'.
' Any filters in the worksheet may be permanently removed.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetFilteredData( _
ByVal ws As Worksheet, _
ByVal CriteriaColumn As Long, _
ByVal Criteria As String) _
As Variant
If ws Is Nothing Then Exit Function ' no worksheet
If CriteriaColumn < 1 Then Exit Function ' allow only positive
If ws.AutoFilterMode Then
ws.AutoFilterMode = False
End If
Dim trg As Range: Set trg = ws.Range("A1").CurrentRegion
If trg.Rows.Count = 1 Then Exit Function ' only one row
Dim cCount As Long: cCount = trg.Columns.Count
If CriteriaColumn > cCount Then Exit Function ' too few columns
Dim crg As Range: Set crg = trg.Columns(CriteriaColumn)
Dim frg As Range: Set frg = trg.Resize(trg.Rows.Count - 1, 1).Offset(1)
crg.AutoFilter 1, Criteria
On Error Resume Next
Dim ffrg As Range: Set ffrg = frg.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
ws.AutoFilterMode = False
If ffrg Is Nothing Then Exit Function ' no match
Dim rCount As Long: rCount = ffrg.Cells.Count
Dim jData As Variant: ReDim jData(1 To rCount)
Dim ffCell As Range
Dim r As Long
For Each ffCell In ffrg.Cells
r = r + 1
jData(r) = ffCell.Resize(, cCount).Value
Next ffCell
Dim dData As Variant: ReDim dData(1 To rCount, 1 To cCount)
Dim c As Long
For r = 1 To rCount
For c = 1 To cCount
dData(r, c) = jData(r)(1, c)
Next c
Next r
GetFilteredData = dData
End Function
UserForm Module e.g. UserForm1
Option Explicit
Private Sub UserForm_Initialize()
LoadData
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.