Remove duplicates but with case sensitive - excel

I am trying to remove duplicates but with case sensitivity.
For example, ABC123 is not the same as abc123, hence, do not remove it.
But ABC123 and ABC123 is the same, hence, remove them.
This is my current code:
Dim oDic As Object, vData As Variant, r As Long
Set oDic = CreateObject("Scripting.Dictionary")
With worksheets(4).Range("A7:A" & lastRow)
vData = .Value
.ClearContents
End With
With oDic
.comparemode = 0
For r = 1 To UBound(vData, 1)
If Not IsEmpty(vData(r, 1)) And Not .Exists(vData(r, 1)) Then
.Add vData(r, 1), Nothing
End If
Next r
Range("A7").Resize(.Count) = Application.Transpose(.keys)
End With
Some background:
The entire dataset has about 800k records
The script has no error, but the result is wrong. When I remove duplicate (regardless of case sensitivity, I have 400k left) but running this script, 450k (which sounds legit), but only 60k records have data, 390k shows #N/A. So I have no idea where went wrong.
Thanks in advance!

As stated in the first comment, Application.Transpose has a limitation of 65,536 array rows. Please, try the next function able to transpose without such a limitation:
Function TranspKeys(arrK) As Variant
Dim arr, i As Long
ReDim arr(1 To UBound(arrK) + 1, 1 To 1)
For i = 0 To UBound(arrK)
arr(i + 1, 1) = arrK(i)
Next i
TranspKeys = arr
End Function
After copying the functionin the same module where your existing code exists, only modify it as:
Range("A7").Resize(.Count,1) = TranspKeys(.keys)

Unique Values Case-Sensitive
Transpose has its limitations and is best avoided (what's a few more lines).
Option Explicit
Sub DictWith()
With Worksheets(4)
Dim LastRow As Long: LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
If LastRow < 7 Then Exit Sub
With .Range("A7:A" & LastRow)
Dim Data As Variant
If .Rows.Count = 1 Then
ReDim Data(1 To 1, 1 To 1)
Data(1, 1).Value = .Value
Else
Data = .Value
End If
With CreateObject("Scripting.Dictionary")
.CompareMode = vbBinaryCompare
Dim Key As Variant
Dim r As Long
For r = 1 To UBound(Data, 1)
Key = Data(r, 1)
If Not IsError(Key) Then
If Len(Key) > 0 Then
.Item(Key) = Empty
End If
End If
Next r
Dim rCount As Long: rCount = .Count
If rCount = 0 Then Exit Sub
ReDim Data(1 To rCount, 1 To 1)
r = 0
For Each Key In .Keys
r = r + 1
Data(r, 1) = Key
Next Key
End With
.Resize(rCount).Value = Data
.Resize(.Worksheet.Rows.Count - .Row - rCount + 1) _
.Offset(rCount).ClearContents ' clear below
End With
End With
End Sub

Related

Excel VBA Passing Variables

I need to pass the variables max, min, and their respective locations to another sub where it will format each max and min in their respective column. I am trying to create an array that will store the locations and the values but its not working.
I was told to first identify the number of columns used and the number of rows, which is the beginning.
Rows = wsData.UsedRange.Rows.Count
Columns = wsData.UsedRange.Col.Count
j = 1
ReDim Min(j)
With wsData.Range("A3:A19")
For j = 1 To 19 'colum
Min(j) = WorksheetFunction.Min(Range(.Offset(1, j), .Offset(Row, j)))
Max = WorksheetFunction.Max(Range(.Offset(1, j), .Offset(Row, j)))
Min(j) = Min
j = j + 1
ReDim Preserve Min(j) 'saves variables
Next 'next column
End With
The code below uses the ActiveSheet which you need to change to reference the worksheet for your data. Additionally, it assumes that your data starts with Row 1. The code looks at each column in the range and stores the minimum/maximum (it does not account for multiple cells which may share the min or max value) value found in the column as well as the cell's address, in an array and then passes the array to two different subs, one which simply displays the information in a message and one which formats the the background color of the cells. This code does not perform any kind of error handling, but should get you where you want to go.
the line Option Explicit requires that all of the variables be defined using a Dim statement
the line Option Base 1 makes the default starting point for arrays 1 instead of 0
Option Explicit
Option Base 1
Sub GatherData()
Dim iRows As Long
Dim iCols As Long
Dim j As Long
Dim iMin() As Variant
Dim iMax() As Variant
Dim R As Range
iRows = ActiveSheet.UsedRange.Rows.Count
iCols = ActiveSheet.UsedRange.Columns.Count
ReDim iMin(iCols, 2)
ReDim iMax(iCols, 2)
For j = 1 To iCols
Set R = Range(Cells(1, j), Cells(iRows, j)).Find(WorksheetFunction.Min(Range(Cells(1, j), Cells(iRows, j))), LookIn:=xlValues)
iMin(j, 1) = R.Value
iMin(j, 2) = R.Address
Set R = Range(Cells(1, j), Cells(iRows, j)).Find(WorksheetFunction.Max(Range(Cells(1, j), Cells(iRows, j))), LookIn:=xlValues)
iMax(j, 1) = R.Value
iMax(j, 2) = R.Address
Next j
ListMinMax iMax(), True
ListMinMax iMin(), False
FormatMinMax iMax, "green"
FormatMinMax iMin, "yellow"
Set R = Nothing
End Sub
Sub ListMinMax(ByRef Arr() As Variant, ByVal MinMax As Boolean)
Dim strOutput As String
Dim i As Long
If MinMax = True Then
strOutput = "Maximums:" & vbCrLf & vbCrLf
Else
strOutput = "Minimums:" & vbCrLf & vbCrLf
End If
For i = 1 To UBound(Arr, 1)
strOutput = strOutput & "Cell: " & Arr(i, 2) & " = " & Arr(i, 1) & vbCrLf
Next i
MsgBox strOutput, vbOKOnly
End Sub
Sub FormatMinMax(ByRef Arr() As Variant, ByVal BGColor As String)
Dim i As Long
Select Case UCase(BGColor)
Case "GREEN"
For i = 1 To UBound(Arr, 1)
ActiveSheet.Range(Arr(i, 2)).Interior.Color = vbGreen
Next i
Case "YELLOW"
For i = 1 To UBound(Arr, 1)
ActiveSheet.Range(Arr(i, 2)).Interior.Color = vbYellow
Next i
Case Else
MsgBox "Invalid Option", vbCritical
End Select
End Sub
======================================================================
The code below does away with the need for the arrays and formats the color of the min/max values as it finds them
Sub GatherData2()
Dim iRows As Long
Dim iCols As Long
Dim j As Long
Dim R As Range
iRows = ActiveSheet.UsedRange.Rows.Count
iCols = ActiveSheet.UsedRange.Columns.Count
For j = 1 To iCols
Set R = Range(Cells(1, j), Cells(iRows, j)).Find(WorksheetFunction.Min(Range(Cells(1, j), Cells(iRows, j))), LookIn:=xlValues)
R.Interior.Color = vbYellow
Set R = Range(Cells(1, j), Cells(iRows, j)).Find(WorksheetFunction.Max(Range(Cells(1, j), Cells(iRows, j))), LookIn:=xlValues)
R.Interior.Color = vbGreen
Next j
Set R = Nothing
End Sub

Finding Largest repeated letter between two columns a/o rows

I try to find largest consecutive letter between two dynamic colums.
Below code find largest consequent letter below same column (like C10:C50) however I want to check different range like "D13:D23;E9:E12". Below code brings me fault. Also can anyone help me to how I can convert it to row defined.
Function CountConsVal(r As Range)
Dim i As Long, s As Long
Rng = r.Value
For i = LBound(Rng, 1) To UBound(Rng, 1) - 1
If Rng(i, 1) = Rng(i + 1, 1) Then
s = s + 1
Rng(i, 1) = ""
Else
Rng(i, 1) = s + 1
s = 0
End If
Next i
Rng(UBound(Rng), 1) = s + 1
CountConsVal = Rng
End Function
Counting Consecutive Group Members
This is the same function you provided a little more readable with some minor changes.
Range("B1:B6").Value = GetGroupCountCols(Range("A1:A6"))
A B
1 a
2 a
3 a 3
4 b
5 b 2
6 c 1
Function GetGroupCountCols(ByVal rg As Range) As Variant
If rg Is Nothing Then Exit Function
Dim drCount As Long: drCount = rg.Rows.Count
Dim cData As Variant
If drCount = 1 Then ' one cell
ReDim cData(1 To 1, 1 To 1): cData(1, 1) = 1
Else ' multiple cells
cData = rg.Columns(1).Value ' ensure one column
Dim r As Long, rCount As Long
For r = 1 To drCount - 1
If cData(r, 1) = cData(r + 1, 1) Then
cData(r, 1) = Empty: rCount = rCount + 1
Else
cData(r, 1) = rCount + 1: rCount = 0
End If
Next r
cData(drCount, 1) = rCount + 1
End If
GetGroupCountCols = cData
End Function
This is the same function but for rows.
Range("A2:F2").Value = GetGroupCountRows(Range("A1:F1"))
A B C D E F
1 a a a b b c
2 3 2 1
Function GetGroupCountRows(ByVal rg As Range) As Variant
If rg Is Nothing Then Exit Function
Dim dcCount As Long: dcCount = rg.Columns.Count
Dim rData As Variant
If dcCount = 1 Then ' one cell
ReDim rData(1 To 1, 1 To 1): rData(1, 1) = 1
Else ' multiple cells
rData = rg.Rows(1).Value ' ensure one row
Dim c As Long, cCount As Long
For c = 1 To dcCount - 1
If rData(1, c) = rData(1, c + 1) Then
rData(1, c) = Empty: cCount = cCount + 1
Else
rData(1, c) = cCount + 1: cCount = 0
End If
Next c
rData(1, dcCount) = cCount + 1
End If
GetGroupCountRows = rData
End Function
This is a test for the following two functions:
Sub GetGroupColumnsCountTEST()
Dim rg As Range: Set rg = Range("I10:I12,F4:F6,G7:G9")
Debug.Print rg.Address
Dim Data As Variant: Data = GetGroupColumnsCount(GetMultiColumns(rg))
Dim rg2 As Range: Set rg2 = Range("K4").Resize(UBound(Data, 1))
rg2.Value = Data
End Sub
This function will return the values of multiple column ranges in a 2D one-based one-column array.
Range("F1:F6").Value = GetMultiColumns(Range("E6,A1:A3,C4:C5")) ' see previous procedure
A B C D E F
1 a a
2 a a
3 b b
4 c c
5 c c
6 d d
Function GetMultiColumns(ByVal mrg As Range) As Variant
If mrg Is Nothing Then Exit Function
' Write data to a jagged array.
Dim aCount As Long: aCount = mrg.Areas.Count
Dim aData As Variant: ReDim aData(1 To aCount, 1 To 3)
Dim Help As Variant: ReDim Help(1 To 1, 1 To 1)
Dim a As Long
Dim arCount As Long, drCount As Long
For a = 1 To aCount
With mrg.Areas(a)
aData(a, 1) = .Row
aData(a, 2) = .Rows.Count
drCount = drCount + aData(a, 2)
If aData(a, 2) = 1 Then
aData(a, 3) = Help: aData(a, 3)(1, 1) = .Value
Else
aData(a, 3) = .Value
End If
End With
Next a
' Bubble sort the array by its first column (first rows) ascending.
ReDim Help(1 To 1)
Dim b As Long, c As Long
For a = 1 To aCount - 1
For b = a To aCount
If aData(a, 1) > aData(b, 1) Then
For c = 1 To 3
Help(1) = aData(a, c)
aData(a, c) = aData(b, c)
aData(b, c) = Help(1)
Next c
End If
Next b
Next a
' Write result.
Dim dData As Variant: ReDim dData(1 To drCount, 1 To 1)
Dim dr As Long
For a = 1 To aCount
Dim r As Long, rCount As Long
For r = 1 To aData(a, 2)
dr = dr + 1
dData(dr, 1) = aData(a, 3)(r, 1)
Next r
Next a
GetMultiColumns = dData
End Function
This is the same as your function but it takes a 2D one-based one-column array instead of a one-column range as the argument.
Function GetGroupColumnsCount(ByVal sData As Variant) As Variant
If IsEmpty(sData) Then Exit Function
Dim drCount As Long: drCount = UBound(sData, 1)
Dim cData As Variant: ReDim cData(1 To drCount, 1 To 1)
If drCount = 1 Then
cData(1, 1) = sData(1, 1)
Else
Dim r As Long, rCount As Long
For r = 1 To drCount - 1
If sData(r, 1) = sData(r + 1, 1) Then
rCount = rCount + 1
Else
cData(r, 1) = rCount + 1: rCount = 0
End If
Next r
cData(drCount, 1) = rCount + 1
End If
GetGroupColumnsCount = cData
End Function
To conclude, the last two functions do what you primarily requested. The only job for you is to combine them into one if necessary.
It seems you want to determine the maximum consecutive "r" values in each of many rows, one row at a time.
I suggest a User Defined Function with a one-row argument
Optionally check that the range argument is valid
read the range into a variant array for faster processing
Use a dictionary to collect each consecutive group of r's
Iterate through the dictionary to find the longest
Divide the final count by two to convert to hours
I used early-binding for the Dictionary object, but you can use late-binding if you prefer. Early-binding may execute slightly faster.
'Set reference to Microsoft Scripting Runtime
Option Explicit
Option Compare Text 'case insensitive
Function LongestConsecutiveRestingHrs(rw As Range) As Double
Dim vRw As Variant, v As Variant
Dim dict As Dictionary
Dim lCount As Long
Dim I As Long
'Optional sanity check: eg:
'confirm rw is 48 columns x 1 row
'If Not rw.Rows.Count = 1 Or Not rw.Columns.Count = 48 Then
' MsgBox "Invalid Range: " & rw.Address & vbLf & "Please enter valid range"
' Exit Function
'read into variant array for faster processing
vRw = rw
'count consecutive "r" groups
Set dict = New Dictionary
I = 0
For Each v In vRw 'can do this since vRw will be a 1D array
If v <> "r" Then
I = I + 1
Else
dict(I) = dict(I) + 1
End If
Next v
'find max r
I = 0
For Each v In dict.Keys
I = IIf(I > dict(v), I, dict(v))
Next v
LongestConsecutiveRestingHrs = I / 2
End Function

How to split cell contents from multiple columns into rows by delimeter?

The code I have takes cells containing the delimiter (; ) from a column, and creates new rows (everything except the column is duplicated) to separate those values.
What I have
I need this for multiple columns in my data, but I don't want the data to overlap (ex: for 3 columns, I want there to be only one value per row in those 3 columns). It would be ideal if I could select multiple columns instead of only one as my code does now.
What I want
Sub splitByCol()
Dim r As Range, i As Long, ar
Set r = Worksheets("Sheet").Range("J2000").End(xlUp)
Do While r.Row > 1
ar = Split(r.Value, "; ")
If UBound(ar) >= 0 Then r.Value = ar(0)
For i = UBound(ar) To 1 Step -1
r.EntireRow.Copy
r.Offset(1).EntireRow.Insert
r.Offset(1).Value = ar(i)
Next
Set r = r.Offset(-1)
Loop
End Sub
Try this code
Sub Test()
Dim a, x, e, i As Long, ii As Long, iii As Long, k As Long
a = Range("A1").CurrentRegion.Value
ReDim b(1 To 1000, 1 To UBound(a, 2))
For i = LBound(a) To UBound(a)
For ii = 2 To 3
x = Split(a(i, ii), "; ")
For Each e In x
k = k + 1
b(k, 1) = k
b(k, 2) = IIf(ii = 2, e, Empty)
b(k, 3) = IIf(ii = 3, e, Empty)
b(k, 4) = a(i, 4)
Next e
Next ii
Next i
Range("A5").Resize(UBound(b, 1), UBound(b, 2)).Value = b
End Sub
I'd go this way
Sub SplitByCol()
With Worksheets("Sheet")
With .Range("B2", .Cells(.Rows.Count, "B").End(xlUp))
Dim firstColValues As Variant
firstColValues = .Value
Dim secondColValues As Variant
secondColValues = .Offset(, 1).Value
Dim thirdColValues As Variant
thirdColValues = .Offset(, 2).Value
.Offset(, -1).Resize(, 4).ClearContents
End With
Dim iRow As Long
For iRow = LBound(firstColValues) To UBound(firstColValues)
Dim currFirstColValues As Variant
currFirstColValues = Split(firstColValues(iRow, 1), "; ")
Dim currSecondColValues As Variant
currSecondColValues = Split(secondColValues(iRow, 1), "; ")
With .Cells(.Rows.Count, "C").End(xlUp).Offset(1, -1)
With .Resize(UBound(currFirstColValues) + 1)
.Value = currFirstColValues
.Offset(, 2).Value = thirdColValues(iRow, 1)
End With
End With
With .Cells(.Rows.Count, "B").End(xlUp).Offset(1, 1)
With .Resize(UBound(currSecondColValues) + 1)
.Value = currSecondColValues
.Offset(, 1).Value = thirdColValues(iRow, 1)
End With
End With
Next
End With
End Sub
Follow the code step by step by pressing F8 while the cursor is in any code line in the VBA IDE and watch what happens in the Excel user interface
EDIT
adding edited code for a more "parametric" handling by means of a helper function
Sub SplitByCol()
With Worksheets("Sheet")
With .Range("B2", .Cells(.Rows.Count, "B").End(xlUp))
Dim firstColValues As Variant
firstColValues = .Value
Dim secondColValues As Variant
secondColValues = .Offset(, 1).Value
Dim thirdColValues As Variant
thirdColValues = .Offset(, 2).Value
.Offset(, -1).Resize(, 4).ClearContents
End With
Dim iRow As Long
For iRow = LBound(firstColValues) To UBound(firstColValues)
Dim currFirstColValues As Variant
currFirstColValues = Split(firstColValues(iRow, 1), "; ")
Dim currSecondColValues As Variant
currSecondColValues = Split(secondColValues(iRow, 1), "; ")
WriteOne .Cells(.Rows.Count, "C").End(xlUp).Offset(1), _
currFirstColValues, thirdColValues(iRow, 1), _
-1, 2
WriteOne .Cells(.Rows.Count, "B").End(xlUp).Offset(1), _
currSecondColValues, thirdColValues(iRow, 1), _
1, 1
Next
End With
End Sub
Sub WriteOne(refCel As Range, _
currMainColValues As Variant, thirdColValue As Variant, _
mainValuesOffsetFromRefCel As Long, thirdColValuesOffsetFromRefCel As Long)
With refCel.Offset(, mainValuesOffsetFromRefCel)
With .Resize(UBound(currMainColValues) + 1)
.Value = currMainColValues
.Offset(, thirdColValuesOffsetFromRefCel).Value = thirdColValue
End With
End With
End Sub
Please, use the next code. It uses arrays and should be very fast for big ranges to be processed, working mostly in memory:
Sub testSplitInsert()
Dim sh As Worksheet, lastR As Long, arr, arrSp, arrFin, i As Long, j As Long, k As Long
Set sh = ActiveSheet
lastR = sh.Range("B" & sh.rows.count).End(xlUp).row
arr = sh.Range("B1:D" & lastR).Value
ReDim arrFin(1 To UBound(arr) * 10, 1 To 3) 'maximum to keep max 10 rows per each case
k = 1 'initialize the variable to load the final array
For i = 1 To UBound(arr)
arrSp = Split(Replace(arr(i, 1)," ",""), ";") 'trim for the case when somebody used Red;Blue, instead of Red; Blue
For j = 0 To UBound(arrSp)
arrFin(k, 1) = arrSp(j): arrFin(k, 3) = arr(i, 3): k = k + 1
Next j
arrSp = Split(Replace(arr(i, 1)," ",""), ";")
For j = 0 To UBound(arrSp)
arrFin(k, 2) = arrSp(j): arrFin(k, 3) = arr(i, 3): k = k + 1
Next j
Next
sh.Range("G1").Resize(k - 1, 3).Value = arrFin
End Sub
It processes the range in columns "B:D" and returns the result in columns "G:I". It can be easily adapted to process any columns range and return even overwriting the existing range, but this should be done only after checking that it return what you need...

VBA: completing a matrix

I have a 3 by 3 matrix, where elements (1,1), (2,1), (2,2), (3,1), (3,2), (3,3) are given:
X . .
X X .
X X X
I need to write a program that writes out the missing elements, where (1,2)=(2,1), (1,3)=(3,1) and (2,3)=(3,2). I have written the following code:
Function kiegeszito(a)
For i = 1 To 3
For j = 1 To 3
If i < j Then
a(i, j) = a(j, i)
Else
a(i, j) = a(i, j)
End If
Next j
Next i
kiegeszito = a
End Function
However, this does not seem to work, could anybody help me why is this not working?
Just remove the Else condition:
Function kiegeszito(a)
For i = 1 To 3
For j = 1 To 3
If i < j Then a(i, j) = a(j, i)
Next j
Next i
kiegeszito = a
End Function
Get twin data in 2-dim matrix avoiding extra n*(n-1)/2 condition checks
The following approach
reduces the number of unnecessary condition checks by incrementing the 2nd loop starts
accepts any wanted base of 2-dim data:
Sub CompleteMatrix(ByRef data)
'count row|=column elements
Dim cnt As Long: cnt = UBound(data) - LBound(data) + 1
'fill missing twin data (identified by inverted indices)
Dim i As Long, j As Long
For i = LBound(data) To cnt - 1
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'next column starts from incremented row index
'(thus avoiding n*(n-1)/2 IF-conditions)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For j = i + 1 To UBound(data, 2)
data(i, j) = data(j, i) ' assign twin data
Next j
Next i
End Sub
An example call creating e.g. a 1-based 2-dim datafield array might be
Sub ExampleCall()
Dim v: v = Tabelle3.Range("A1:C3").Value
CompleteMatrix v
End Sub
Further link
A practical example using such a mirrored array might be a distance array; a related post demonstrates how to apply the FilterXML() function thereon.
Fill Array
Using a method (fillArray) you could modify the array 'in place':
The Code
Option Explicit
Sub fillArrayTEST()
Dim Data As Variant: Data = Range("A1:C3").Value
debugPrint2D Data
fillArray Data
debugPrint2D Data
End Sub
Sub fillArray(ByRef Data As Variant)
Dim cCount As Long: cCount = UBound(Data, 2)
Dim i As Long, j As Long
For i = 1 To UBound(Data, 1)
For j = 1 To cCount
If i < j Then Data(i, j) = Data(j, i)
Next j
Next i
End Sub
Sub debugPrint2D(ByVal Data As Variant)
Dim i As Long, j As Long
For i = LBound(Data, 1) To UBound(Data, 1)
For j = LBound(Data, 2) To UBound(Data, 2)
Debug.Print "[" & i & "," & j & "]", Data(i, j)
Next j
Next i
End Sub
A Homage to T.M.'s Brilliant Solution
Sub completeMatrix(ByRef Data As Variant)
Dim rLower As Long: rLower = LBound(Data, 1)
Dim cLower As Long: cLower = LBound(Data, 2)
Dim iDiff As Long: iDiff = cLower - rLower
Dim cStart As Long: cStart = iDiff + 1
Dim cUpper As Long: cUpper = UBound(Data, 2)
Dim r As Long, c As Long
For r = rLower To UBound(Data, 1) - rLower
For c = cStart + r To cUpper
Data(r, c) = Data(c - iDiff, r + iDiff)
Next c
Next r
End Sub
Sub completeMatrixTEST()
Dim Data As Variant: ReDim Data(0 To 2, 2 To 4)
Data(0, 2) = 1
Data(1, 2) = 2
Data(1, 3) = 3
Data(2, 2) = 4
Data(2, 3) = 5
Data(2, 4) = 6
debugPrint2D Data
completeMatrix Data
'Range("G1").Resize(UBound(Data, 1) - LBound(Data, 1) + 1, _
UBound(Data, 2) - LBound(Data, 2) + 1).Value = Data
Debug.Print
debugPrint2D Data
End Sub

How to use loop to SUM categories?

I am trying to use a loop with vba to sum values from one worksheet to another. I am struggling with writing my code to match values from Sheet 4 and if the value matches then sum the categories from Sheet 1, if not then skip to the next office. I would also like to exclude certain categories from being included in the SUM loop for example, exclude "Book". Currently my macro is writing to Sheet3. Here is my code:
Option Explicit
Sub test()
Dim a, i As Long, ii As Long, dic As Object
Set dic = CreateObject("Scripting.Dictionary")
dic.CompareMode = 1
a = Sheets("sheet1").Cells(1).CurrentRegion.Value
With CreateObject("Scripting.Dictionary")
.CompareMode = 1
For i = 2 To UBound(a, 1)
If Not dic.Exists(a(i, 1)) Then dic(a(i, 2)) = dic.Count + 2
If Not .Exists(a(i, 1)) Then
Set .Item(a(i, 1)) = CreateObject("Scripting.Dictionary")
.Item(a(i, 1)).CompareMode = 1
End If
.Item(a(i, 1))(a(i, 2)) = .Item(a(i, 1))(a(i, 2)) + a(i, 3)
Next
ReDim a(1 To .Count + 1, 1 To dic.Count + 1)
a(1, 1) = Sheets("sheet1").[a1]
For i = 0 To dic.Count - 1
a(1, i + 2) = dic.Keys()(i)
Next
For i = 0 To .Count - 1
a(i + 2, 1) = .Keys()(i)
For ii = 2 To UBound(a, 2)
a(i + 2, ii) = .items()(i)(a(1, ii)) + 0
Next
Next
End With
With Sheets("sheet3").Cells(1).Resize(UBound(a, 1), UBound(a, 2))
.EntireColumn.ClearContents
Sheets("sheet1").[a1].Copy .Rows(1)
.Value = a: .Columns.AutoFit: .Parent.Activate
End With
End Sub
This is how the data looks
and this is the output that is desired
In this example, we will use arrays to achieve what you want. I have commented the code so that you shall not have a problem understanding it. However if you still do then simply ask :)
Input
Output
Logic
Find last row and last column of input sheet
Store in an array
Get unique names from Column A and Row 1
Define output array
Compare array to store sum
Create new sheet and output to that sheet
Code
Option Explicit
Sub Sample()
Dim ws As Worksheet, wsNew As Worksheet
Dim tempArray As Variant, OutputAr() As Variant
Dim officeCol As New Collection
Dim productCol As New Collection
Dim itm As Variant
Dim lrow As Long, lcol As Long, totalsum As Long
Dim i As Long, j As Long, k As Long
'~~> Input sheet
Set ws = Sheet1
With ws
'~~> Get Last Row and last column
lrow = .Range("A" & .Rows.Count).End(xlUp).Row
lcol = .Cells(1, Columns.Count).End(xlToLeft).Column
'~~> Store it in a temp array
tempArray = .Range(.Cells(2, 1), .Cells(lrow, lcol)).Value
'~~> Create a unique collection using On error resume next
On Error Resume Next
For i = LBound(tempArray) To UBound(tempArray)
officeCol.Add tempArray(i, 1), CStr(tempArray(i, 1))
productCol.Add tempArray(i, 2), CStr(tempArray(i, 2))
Next i
On Error GoTo 0
End With
'~~> Define you new array which will hold the desired output
ReDim OutputAr(1 To officeCol.Count + 1, 1 To productCol.Count + 1)
'~~> Store the rows and columns in the array
i = 2
For Each itm In officeCol
OutputAr(i, 1) = itm
i = i + 1
Next itm
i = 2
For Each itm In productCol
OutputAr(1, i) = itm
i = i + 1
Next itm
'~~> Calculate sum by comparing the arrays
For i = 2 To officeCol.Count + 1
For j = 2 To productCol.Count + 1
totalsum = 0
For k = LBound(tempArray) To UBound(tempArray)
If OutputAr(i, 1) = tempArray(k, 1) And _
OutputAr(1, j) = tempArray(k, 2) Then
totalsum = totalsum + tempArray(k, 3)
End If
Next k
OutputAr(i, j) = totalsum
Next j
Next i
'~~> Create a new sheet
Set wsNew = ThisWorkbook.Sheets.Add
'~~> Outout the array
wsNew.Range("A1").Resize(officeCol.Count + 1, productCol.Count + 1).Value = OutputAr
End Sub

Resources