I have a dataset in which one of the columns needs to be filled conditionally. The conditions are that for equal lot numbers, the dates that are older (and equal) would be filled with 123ABC while dates that are newer (and equal) would be filled with 789XYZ. In the case of only one available date then it should be filled with 123ABC.
I tried the following code but it is filling the first 3 cells as 123ABC and rest of the cells as 789XYZ.
Please help.
Dim F As Worksheet
Set F = ThisWorkbook.Worksheets("Sheet1")
Dim i As Long: i = 0
Dim j As Long
Do While F.Range("C2").Offset(i, 0) <> ""
If F.Range("A2").Offset(i, 0) = "" Then
j = 0
Do While F.Range("C2").Offset(j, 0) <> ""
If (Abs(DateDiff("d", F.Range("C2").Offset(i, 0).Value, F.Range("C2").Offset(j, 0).Value)) <= 5) And (F.Range("B2").Offset(i, 0) = F.Range("B2").Offset(j, 0)) Then
F.Range("A2").Offset(i, 0).Value = "123ABC"
Else
F.Range("A2").Offset(i, 0).Value = "789XYZ"
GoTo Next_Blank
End If
j = j + 1
Loop
End If
Next_Blank:
i = i + 1
Loop
End Sub
Please, try the next approach. It should be very fast even for large ranges. It uses a dictionary to create "Lot" unique keys, keeping the value as the most recent Date. Then it uses arrays and works only in memory, dropping the processed array content at one, at the end of the code:
Sub FillColumn()
Dim sh As Worksheet, lastR As Long, arr, i As Long, arrFin, dict As Object
Const beforeD As String = "123ABC", maxD As String = "789XYZ"
Set sh = ActiveSheet
lastR = sh.Range("B" & sh.rows.count).End(xlUp).row 'last row in "B:B"
arr = sh.Range("A1:C" & lastR).value 'place the range in an array for faster iteration
'fill a dictionary with unique lots and most recent Date:
Set dict = CreateObject("scripting.Dictionary") 'set the necessary dictionary
For i = 2 To UBound(arr) 'iterate between the array row
'create dictionary unique keys with most recent Date as item:
dict(arr(i, 2)) = IIf(CDate(arr(i, 3)) > CDate(dict(arr(i, 2))), CDate(arr(i, 3)), CDate(arr(i, 3)))
Next i
arrFin = arr 'initialize arrFin as the initial one
For i = 2 To UBound(arr) 'iterate between the arr items
If CDate(arr(i, 3)) < dict(arr(i, 2)) Then 'for a Date before existing one in column B:B:
arrFin(i, 1) = beforeD 'place the string beforeD
Else
arrFin(i, 1) = maxD 'place the string maxD
End If
Next i
'drop the array content at once:
sh.Range("A1").Resize(UBound(arrFin), UBound(arrFin, 2)).value = arrFin
End Sub
Edited:
Please, test the next version, which place "123ABC" if a single Date is found for the same "Lot", as required in your comment:
Sub FillColumn2()
Dim sh As Worksheet, lastR As Long, arr, i As Long, arrFin, dict As Object
Const beforeD As String = "123ABC", maxD As String = "789XYZ"
Set sh = ActiveSheet
lastR = sh.Range("B" & sh.rows.count).End(xlUp).row
arr = sh.Range("A1:C" & lastR).value 'place the range in an array for faster iteration
'fill a dictionary with unique lots and most recent Date:
Set dict = CreateObject("scripting.Dictionary") 'set the necessary dictionary
Dim arrExist
For i = 2 To UBound(arr) 'iterate between the array row
'create dictionary unique keys with most recent Date as item, and False for only one Date found:
If Not dict.Exists(arr(i, 2)) Then
dict.Add arr(i, 2), Array(CDate(arr(i, 3)), False) 'False means only one Date
Else
If CDate(arr(i, 3)) > dict(arr(i, 2))(0) Then
arrExist = dict(arr(i, 2)) 'place existing dictionary item in an array (to be changed)
arrExist(0) = CDate(arr(i, 3)): arrExist(1) = True 'True means that a second graiter Date has been found
dict(arr(i, 2)) = arrExist
End If
End If
Next i
arrFin = arr 'initialize arrFin as the initial one
For i = 2 To UBound(arr) 'iterate between the arr items
If CDate(arr(i, 3)) < dict(arr(i, 2))(0) Or dict(arr(i, 2))(1) = False Then 'check also the second item array element (boolean)
arrFin(i, 1) = beforeD 'place the string beforeD, also for the case of the same date
Else
arrFin(i, 1) = maxD 'place the string maxD
End If
Next i
'drop the array content at once:
sh.Range("A1").Resize(UBound(arrFin), UBound(arrFin, 2)).value = arrFin
End Sub
A dictionary can keep any data type, but it has a peculiarity: if the dictionary item is an array it cannot be modified directly in the item. That's why the code uses arrExist to take the dictionary item, modify it and place it back.
It is also good to know that Excel keeps a Date as a Long number. That's why comparing the existing dictionary item (when empty) with a lower number would never change the item. No date less then zero can be supplied...
Related
I'm trying to print the contents of a dictionary of arrays in one fell swoop to an Excel sheet.
The dictionary structure may be something like this:
dict(company_name) = employee
Where employee is an array of three values, e.g., name, surname, and age.
As long as the items are single value, I can print the dictionary with a statement like
Cells(1, 1).Resize(dict.Count, 1).Value2 = Application.Transpose(dict.Keys)
Cells(1, 2).Resize(dict.Count, 1).Value2 = Application.Transpose(dict.Items)
I cannot come up with a solution when I have array as item.
You've got an error. dict.Keys - it's an array of the keys! You can't set the cell value as array
You need to set the string variable and collect all keys in it
Dim str1 as String
Dim str2 as String
For i=1 to count 'qty of elements in dictionary
str1=str1 & dict.Keys()(i)
str2=str2 & dict.Items()(i)
Next i
Here is the link to the article about dictionaries
http://www.snb-vba.eu/VBA_Dictionary_en.html
With a loop you can do something like this - should still be fast.
Sub DictOutput()
Dim dict As Object, i As Long, r As Long, cols As Long, col As Long, arr, data, k
Set dict = CreateObject("scripting.dictionary")
'load some test data
For i = 1 To 100
dict.Add "Key_" & Format(i, "000"), Split("A,B,C,D", ",")
Next i
arr = dict.Items()(0) 'get the first value
cols = 1 + (UBound(arr) - LBound(arr)) 'number of items in array (assumed all the same size)
ReDim data(1 To dict.Count, 1 To (1 + cols)) 'size the output array
r = 0
For Each k In dict 'loop and fill the output array
r = r + 1
data(r, 1) = k
arr = dict(k)
i = 2
For col = LBound(arr) To UBound(arr) 'loop array and populate output row
data(r, i) = arr(col)
i = i + 1
Next col
Next k
'put the data on the sheet
ActiveSheet.Range("A1").Resize(UBound(data, 1), UBound(data, 2)).Value = data
End Sub
I have a set of matching values as shown:
The input is a table with Order number in the first column and dates in the seventh column.
I would like to extract all the matching dates from the seventh column and display only the 'unique dates' in the columns against each matching order value.
If there are no matching values in the input, it should return blank values in output.
I use Excel 2016. The inputs are in sheet 2.
I managed to get the dates with array index formula but it is slow with large data.
If you have access to the new array functions UNIQUE & FILTER then:
Using the sample data below
In cell E1: =UNIQUE(A1:A10)
In cell F1: =TRANSPOSE(UNIQUE(FILTER(B1:B10,A1:A10=E1)))
Then drag the formula from F1 down to the last cell which will populate your desired table.
Please, try the next VBA solution. It should be very fast, using two dictionaries and arrays, mostly working in memory. It will return the processed result starting from "J2" cell. It can return anywhere, you should only change "J2" cell with the cell range you need, even being in another sheet:
Sub extractUniqueValues_Dat()
Dim sh As Worksheet, lastR As Long, arr, arrIt, arrFin, Z As Long
Dim dict As Object, dictI As Object, i As Long, k As Long
Set sh = ActiveSheet
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row
arr = sh.Range("A2:G" & lastR).value 'place the range to be processed in an array, for faster iteration
Set dict = CreateObject("Scripting.Dictionary") 'set first necessary dictionary
For i = 1 To UBound(arr) 'iterate between the array rows and load the dictionary:
If Not dict.Exists(arr(i, 1)) Then 'if the key does not exist:
Set dictI = CreateObject("Scripting.Dictionary") 'set a new dictionary
dictI.Add arr(i, 7), vbNullString 'create a key of the new dictionary using first Date occurrence
dict.Add arr(i, 1), dictI 'create a dictionary key as Value and add the new dictionary as item
If dictI.count > Z Then Z = dictI.count 'extract maximum number of Date occurrences
Else
dict(arr(i, 1))(arr(i, 7)) = vbNullString 'if the key of the item dictionary does not exist it is added, with an empty item
If dict(arr(i, 1)).count > Z Then Z = dict(arr(i, 1)).count 'extract maximum number of Date occurrences
End If
Next i
ReDim arrFin(1 To dict.count, 1 To Z + 1) '+ 1, to make place for the dictionary key (in first column)
'fill the arrFin array:
For i = 0 To dict.count - 1
arrFin(i + 1, 1) = dict.Keys()(i) 'place the main dictionary key in the first column of the final array
For k = 1 To dict.Items()(i).count
arrFin(i + 1, 1 + k) = dict.Items()(i).Keys()(k - 1) 'place each date (extracted from the item dictionary keys) in the following columns
Next k
Next i
'build the header:
Dim arrH: arrH = Evaluate("TRANSPOSE(ROW(1:" & Z & "))")
arrH = Split("Match Value|Data " & Join(arrH, "|Data "), "|")
'drop the final aray content and apply a little formatting:
With sh.Range("J2").Resize(UBound(arrFin), UBound(arrFin, 2))
.value = arrFin
With .rows(1).Offset(-1)
.value = arrH
.Font.Bold = True
.HorizontalAlignment = xlCenter
End With
.EntireColumn.AutoFit
End With
MsgBox "Ready..."
End Sub
Please send some feedback after testing it.
Edited:
Please, test the next version. It will work even if the customer orders will not be unique (in K:K column)... This code will also extract only unique values from the mentioned range. It will also check if there are values in the processed sheet which cannot be found in K:K, and returns in the sheet being processed, starting from "M1". Please, use the real sheet where K:K necessary column exists, when set shK sheet!
Private Sub extractUniqueValues_Dat()
Dim shK As Worksheet, lastRK As Long, sh As Worksheet, lastR As Long, arr, arrK, arrIt, arrFin, Z As Long
Dim dict As Object, dictI As Object, dictK As Object, i As Long, k As Long
Set sh = ActiveSheet
lastR = sh.Range("B" & sh.rows.count).End(xlUp).row 'last row in B:B
arr = sh.Range("B2:H" & lastR).Value 'place the range to be processed in an array, for faster iteration
Set shK = Worksheets("sheet KK") 'use here the necessary sheet (with values in K:K)!!!
lastRK = shK.Range("K" & shK.rows.count).End(xlUp).row 'last row in K:K
arrK = shK.Range("K2:K" & lastRK).Value
Set dictK = CreateObject("Scripting.Dictionary") 'set first necessary dictionary
Set dict = CreateObject("Scripting.Dictionary") 'set first necessary dictionary
'place the UNIQUE values in a dictionary, as keys and all unique date, for all accurrences in an item array:
For i = 1 To UBound(arr) 'iterate between the array rows and load the dictionary:
If Not dict.Exists(arr(i, 1)) Then 'if the key does not exist:
Set dictI = CreateObject("Scripting.Dictionary") 'set a new dictionary
dictI.Add arr(i, 7), vbNullString 'create a key of the new dictionary using first Date occurrence
dict.Add arr(i, 1), dictI 'create a dictionary key as Value and add the new dictionary as item
If dictI.count > Z Then Z = dictI.count 'extract maximum number of Date occurrences
Else
dict(arr(i, 1))(arr(i, 7)) = vbNullString 'if the key of the item dictinary does not exist it is added, with an empty item
If dict(arr(i, 1)).count > Z Then Z = dict(arr(i, 1)).count 'extract maximum number of Date occurrences
End If
Next i
'place the UNIQUE vales from K:K column, only as keys:
For i = 1 To UBound(arrK)
dictK(arrK(i, 1)) = vbNullString
Next i
ReDim arrFin(1 To dictK.count, 1 To Z + 3) '+ 1, to make splace for the dictionary key (in first column)
'fill the arrFin array:
For i = 0 To dictK.count - 1
arrFin(i + 1, 1) = dictK.Keys()(i) 'place the main dictionary keyi in the first column of the final array
If dict.Exists(dictK.Keys()(i)) Then
For k = 1 To dict(dictK.Keys()(i)).count
arrFin(i + 1, 3 + k) = dict(dictK.Keys()(i)).Keys()(k - 1) 'place each date (extracted from the item dictionary keys) in the following columns
Next k
End If
Next i
'check if there are missing values from sheet with processed data:
Dim arrMiss, KK As Long, boolMiss As Boolean
ReDim arrMiss(dict.count)
For i = 0 To dict.count - 1
If Not dictK.Exists(dict.Keys()(i)) Then
arrMiss(KK) = dict.Keys()(i): KK = KK + 1
End If
Next i
'build the header:
Dim arrH: arrH = Evaluate("TRANSPOSE(ROW(1:" & Z & "))")
arrH = Split("Match Value|x|y|Data " & Join(arrH, "|Data "), "|")
'drop the final aray content and apply a little formatting:
With sh.Range("M2").Resize(UBound(arrFin), UBound(arrFin, 2))
.CurrentRegion.Value = "" 'if the previous return dropped more rows than the actual one...
.Value = arrFin
With .rows(1).Offset(-1)
.Value = arrH
.Font.Bold = True
.HorizontalAlignment = xlCenter
End With
.EntireColumn.AutoFit
End With
If KK > 0 Then
ReDim Preserve arrMiss(KK - 1)
MsgBox "Missing Values: " & vbCrLf & Join(arrMiss, vbCrLf), vbInformation, "Please, check..."
boolMiss = True
End If
If Not boolMiss Then MsgBox "Ready..."
End Sub
Send some feedback after testing it, please...
Wondering if it was possible to sort chart data display in descending order :
I've no Idea of how to do so.
The only thing I know is how to browse series values :
Set s = cht.FullSeriesCollection(1)
For i = 1 To s.Points.Count
If s.Values(i) < 0 Then 'JustAnExample
'WhateverIwant
End If
Next i
In addition, the above chart is built with data from a worksheet :
Please, test the next solution. Since you did not post your chart creation code, I imagined something doing that:
Sub createStackedColChart_Arrays()
Dim sh As Worksheet, arr1, arr2, arrN, arrD
Dim chartName As String, arrSort, i As Long
Set sh = ActiveSheet 'use here the necessary sheet
chartName = "MyChartSorted"
arr1 = sh.Range("A2:D2").value 'first series array
arr2 = sh.Range("A3:D3").value 'second series array
arrN = sh.Range("A1:D1").value 'X axes values array
'Create the reference array of summarized values per column:
ReDim arrSort(1 To UBound(arr1, 2))
For i = 1 To UBound(arr1, 2)
arrSort(i) = arr1(1, i) + CLng(arr2(1, i))
Next i
'_______________________________________________
'sort arrays according to reference one (arrSort):
sortArrs arrSort, arrN, arr1, arr2
'if the (testing) chart exists, delete it:
On Error Resume Next
ActiveSheet.ChartObjects(chartName).Delete
On Error GoTo 0
'create the necessary chart:
With ActiveSheet.ChartObjects.Add(left:=100, width:=375, top:=75, height:=225).Chart
.Parent.Name = chartName 'name it to have a reference when delete it
.SeriesCollection.NewSeries.Values = arr1 'add first series
.SeriesCollection.NewSeries.Values = arr2 'add first series
.HasTitle = True 'set it to allow a Title
.chartTitle.text = "My Sorted Chart" 'set the Title
.ChartType = xlColumnStacked 'set the chart type
.SeriesCollection(1).XValues = arrN 'add values to X axis
End With
End Sub
Sub sortArrs(arrS, arrN, arr1, arr2) 'being passed byRef, the initial arrays are filtered
Dim i As Long, nxtEl As Long, tmp, tmpN, tmp1, tmp2
For i = LBound(arrS) To UBound(arrS) - 1 'iterate between the arrS elements (except the last):
For nxtEl = i + 1 To UBound(arrS) 'iterate between the arrS elements (starting from the second one):
If arrS(i) < arrS(nxtEl) Then 'sort the arrays according to the element values (< means descending)
tmp = arrS(i): tmpN = arrN(1, i): tmp1 = arr1(1, i): tmp2 = arr2(1, i)
arrS(i) = arrS(nxtEl): arrN(1, i) = arrN(1, nxtEl)
arr1(1, i) = arr1(1, nxtEl): arr2(1, i) = arr2(1, nxtEl)
arrS(nxtEl) = tmp: arrN(1, nxtEl) = tmpN
arr1(1, nxtEl) = tmp1: arr2(1, nxtEl) = tmp2
End If
Next nxtEl
Next i
End Sub
Please, send some feedback after testing it.
If you need the chart being dynamic, meaning to refresh it in case of any value changed in the referenced range (A1:D3, in your example), sheet Change event can be used. If a change in the above mentioned range, the event will call the above function. If need it, please copy the next code in the involved sheet code module:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Me.Range("A1:D3")) Is Nothing Then
createStackedColChart_Arrays 'if need to change the Sub name, please adapt it here...
End If
End Sub
Edited:
A more elaborated, dynamic version using all existing rows/columns in the sheet. The last column is calculated on the first row (column Headers):
Sub createStackedColChart_Arrays_Dynamic()
Dim sh As Worksheet, lastR As Long, lastCol As String, arrN, arrSort
Dim chartName As String, dict As Object, i As Long, j As Long
Set sh = ActiveSheet 'use here the necessary sheet
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'last row of A:A column
lastCol = Split(sh.cells(1, sh.Columns.count).End(xlToLeft).Address, "$")(1) 'extract the last column Letter
chartName = "MyChartSorted"
Set dict = CreateObject("Scripting.Dictionary") 'create the necessary dictionary object
For i = 2 To lastR
dict.Add i - 1, sh.Range("A" & i & ":" & lastCol & i).value 'place in the dictionary the ranges to become chart series
Next i
arrN = sh.Range("A1:" & lastCol & 1).value 'X axes values (names array)
'Create the reference array of summarized values per column:
ReDim arrSort(1 To UBound(arrN, 2))
For i = 1 To UBound(arrN, 2)
For j = 1 To dict.count
arrSort(i) = arrSort(i) + dict(j)(1, i) 'add each column value to summarize
Next j
Next i
'_______________________________________________
'Debug.Print Join(arrSort, "|"): Stop
sortDArrs arrSort, arrN, dict 'sort the involved arrays (ranges) according to arrSort sorted descending
'if the (testing) chart exists, delete it:
On Error Resume Next
ActiveSheet.ChartObjects(chartName).Delete
On Error GoTo 0
'create the necessary chart:
With ActiveSheet.ChartObjects.Add(left:=100, width:=375, top:=80, height:=225).Chart
.Parent.Name = chartName 'name it to have a reference when delete it
For i = 1 To dict.count 'add a new series from the dictionary (sorted) items:
.SeriesCollection.NewSeries.Values = dict(i) 'add the series
Next i
.HasTitle = True 'set it to allow a Title
.chartTitle.text = "My Sorted Chart" 'set the Title
.ChartType = xlColumnStacked 'set the chart type
.SeriesCollection(1).XValues = arrN 'add values to X axis
End With
End Sub
Sub sortDArrs(arrS, arrN, dict As Object) 'sort descending all involved arrays/ranges
Dim i As Long, nxtEl As Long, tmp, tmpN, arrTemp, arrT, k As Long, j As Long
ReDim arrTemp(dict.count - 1): ReDim arrT(1 To 1, 1 To UBound(arrN, 2))
For i = LBound(arrS) To UBound(arrS) - 1 'iterate between the arrS elements (except the last):
For nxtEl = i + 1 To UBound(arrS) 'iterate between the arrS elements (starting from the second one):
If arrS(i) < arrS(nxtEl) Then 'sort the arrays according to the element values (< means descending)
tmp = arrS(i): tmpN = arrN(1, i) 'memorize the element temporaty walue
For k = 0 To UBound(arrTemp): arrTemp(k) = dict(k + 1)(1, i): Next k 'do the same for each dictionary item
arrS(i) = arrS(nxtEl): arrN(1, i) = arrN(1, nxtEl)
For k = 1 To dict.count - 1 'the arrays content of a dictionary item cannot be changed directly!!!
arrT = dict(k): arrT(1, i) = dict(k)(1, nxtEl): dict(k) = arrT 'it cam be changed in this way
Next k
arrS(nxtEl) = tmp: arrN(1, nxtEl) = tmpN 'switch the array element value to the memorized one
For k = 1 To dict.count 'do the same in each dictionary item array:
arrT = dict(k): arrT(1, nxtEl) = arrTemp(k - 1): dict(k) = arrT
Next k
End If
Next nxtEl
Next i
End Sub
The sheet Change event should have the necessary manually adapted. It can be automatically determined, but, in order to avoid running the code for each added header or value on the last row, a special cell should be also targeted and the event to skip the Sub running when the new range is added. Let us say the word "STOP". When deleted, everything should work as it should (automatically calculating the lastR and LastCol similarly as in the above code).
I have a table of hours against weeks (start of the week is a Sunday). The weekly data goes up to 12-16 months dependent on user input. I want to create a VBA macro which will iterate through this table of weekly hours data and convert the columns into monthly data.
Example:
All October 2021 related columns will collapse into 1 column called Oct-21. This will also combine the hours. 2nd row in the image below would equal 4+3+4+0= therefore value would be 11 in the new combined column's 2nd row.
My current thinking was calculating the Sundays between the start date and the last date which is below:
Dim d As Date, format As String, w As Long, FirstSunday As String
format = format(lastMonth, "Medium Date")
d = DateSerial(Year(format), Month(format), 1)
w = Weekday(d, vbSunday)
FirstSunday = d + IIf(w <> 1, 8 - w, 0)
Any ideas on how to do this?
Not sure how you want to group the weeks into months as some months will have 5 weeks. This code inserts a column when the month changes and then fills it with a sum formula for the relevant week columns. It assumes the dates are on row 1 , the task numbers in column 1 and the first week is in column 2.
Option Explicit
Sub ByMonth()
Dim wb As Workbook, ws As Worksheet
Dim LastCol As Long, LastRow As Long, c As Long, n As Long
Dim dt As Date
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet1")
LastCol = ws.Cells(1, Columns.Count).End(xlToLeft).Column
LastRow = ws.Cells(Rows.Count, 1).End(xlUp).Row
' scan cols from right to left insert new columns
Application.ScreenUpdating = False
For c = LastCol + 1 To 3 Step -1
' add columns on month change
If Month(ws.Cells(1, c)) <> Month(ws.Cells(1, c - 1)) Then
ws.Columns(c).Insert
With ws.Columns(c)
.HorizontalAlignment = xlCenter
'.Interior.Color = RGB(255, 255, 200)
.Font.Bold = True
.Cells(1).NumberFormat = "#"
End With
End If
Next
' scan left to right filling new cols with sum() formula
' hide weekly columns
LastCol = ws.Cells(1, Columns.Count).End(xlToLeft).Column
n = 0
For c = 2 To LastCol + 1
If ws.Cells(1, c) = "" Then
dt = ws.Cells(1, c - 1)
ws.Cells(1, c) = MonthName(Month(dt), True) & " " & Year(dt)
ws.Cells(2, c).Resize(LastRow - 1).FormulaR1C1 = "=SUM(RC[-" & n & "]:RC[-1])"
n = 0
Else
ws.Columns(c).EntireColumn.Hidden = True
n = n + 1
End If
Next
' copy visible month columns to sheet2
ws.Cells.SpecialCells(xlCellTypeVisible).Copy
With wb.Sheets("Sheet2")
.Activate
.Range("A1").PasteSpecial xlPasteValues
.Range("A1").Select
End With
' end
ws.Columns.Hidden = False
Application.ScreenUpdating = True
Application.CutCopyMode = False
MsgBox "Done"
End Sub
Please, try the next code. It assumes that in column A:A, starting from the 6th row, there are (not sorted) tasks. If they are sorted, the code will run without problem, too. It uses arrays and a dictionary and mostly working in memory, should be very fast for big ranges:
Sub SumWeeksMonths()
Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, arrWk, arrMonths, arrTasks
Dim i As Long, k As Long, j As Long, El, arr, arrFin, dict As New Scripting.Dictionary
Set sh = ActiveSheet 'use there the sheet to be processed
Set sh1 = sh.Next 'use here the sheet where the processed result to be returned
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'last row (in column A:A)
arrWk = sh.Range(sh.Range("B5"), sh.cells(5, sh.Columns.count).End(xlToLeft)).Value 'place the Week headers in a 2D array
ReDim arrMonths(UBound(arrWk, 2) - 1)'redim the 1D array to keep the unique munths, at a maximum size
For i = 1 To UBound(arrWk, 2) - 1 'create the array of (only) months:
If month(DateValue(arrWk(1, i))) <> month(DateValue(arrWk(1, i + 1))) Then
k = k + 1: arrMonths(k) = Format(DateValue(arrWk(1, i + 1)), "mmm-yyyy")
Else
arrMonths(k) = Format(DateValue(arrWk(1, i)), "mmm-yyyy")
End If
Next i
ReDim Preserve arrMonths(k) 'preserve only the existing Date elements
For Each El In sh.Range("A4:A" & lastR).Value
dict(El) = 1 'extract the unique tasks (only to count them for ReDim the necessary array)
Next El
'place all the range to be processed in an array (for faster iteration):
arr = sh.Range("A5", sh.cells(lastR, sh.cells(5, sh.Columns.count).End(xlToLeft).Column)).Value
ReDim arrFin(1 To UBound(dict.Keys) + 1, 1 To UBound(arrMonths) + 2) 'reDim the final array to keep processed data
ReDim arrTasks(UBound(arrMonths)) 'redim the array to temporarily keep the array of each task summ
dict.RemoveAll: k = 0 'clear the dictionary and reitinialize the K variable
For i = 2 To UBound(arr) 'iterate between the main array elements:
If Not dict.Exists(arr(i, 1)) Then 'if the Task key does not exist:
For Each El In arrMonths 'iterate between each month in arrMonths:
For j = 2 To UBound(arr, 2) 'iterate between all arr columns for the i row:
If month(DateValue(arr(1, j))) = month(El) Then 'if column months is a specific arrMonths column:
arrTasks(k) = arrTasks(k) + arr(i, j) 'sumarize everything in the arrTask each element
End If
Next j
k = k + 1 'increment k, for the next month
Next El
dict.Add arr(i, 1), arrTasks 'create the dictionary key with the tasks array as item
ReDim arrTasks(UBound(arrMonths)): k = 0 'reinitialize arrTasks and k variable
Else 'if dictionary (task) key exists:
For Each El In arrMonths
For j = 2 To UBound(arr, 2)
If month(DateValue(arr(1, j))) = month(El) Then
arrTasks(k) = dict(arr(i, 1))(k) + arr(i, j) 'add the sum to the allready existing elements
End If
Next j
k = k + 1
Next El
dict(arr(i, 1)) = arrTasks 'make the item the updaded array
ReDim arrTasks(UBound(arrMonths)): k = 0 'reinitialize arrTasks and k variable
End If
Next i
'place the processed values in final array (arrFin):
For i = 0 To UBound(arrMonths) 'firstly the headers:
arrFin(1, i + 2) = arrMonths(i)
Next i
'Extract the tasks value for each month and place in the final array appropriate columns:
For i = 0 To dict.count - 1 'iterate between the dictionary elements:
arrFin(i + 2, 1) = dict.Keys(i) 'place the task in the array first column, starting from the second row
For j = 0 To UBound(dict.items(i)) 'iterate between the dictionary item array elements
arrFin(i + 2, j + 2) = dict.items(i)(j) 'place the appropriate array elements in the final array (arrFin)
Next j
Next i
'drop the final array at once and make some formatting:
With sh1.Range("A1").Resize(UBound(arrFin), UBound(arrFin, 2))
.Value = arrFin
With .rows(1)
.Font.Bold = True
.Interior.ColorIndex = 20
.BorderAround 1
End With
.EntireColumn.AutoFit
.BorderAround 1
End With
sh1.Activate 'to see the processing result...
MsgBox "Ready..."
End Sub
Please, test it and send some feedback.
I am trying to convert the data based on the max repeated values.
I have truck numbers in col A and "Truck types" in column in B col.
For each unique truck number, the truck type should be same.(This is the expected result)
This can be achieved, by counting the maximum no. of truck types for the unique "truck no", and that cell to be updated with the Max. repeated "Truck type".
If there is equal no. of "Truck types" are available, It should be updated with the first available truck type.
Like this, there are thousands of rows to be updated. This can be
better understand by seeing the attached image.
I have attached the image & expected result is in the column C.
I have googled a lot, but I was unable to find the relevant solution.
Please help.
You do not say anything...
Please, test the next code. It works with assumption that the columns are sorted as we can see in the picture. It is very fast, since the result is put in an array and dropped on the sheet at once:
Sub findMaxCountVehType_Array()
Dim sh As Worksheet, lastRow As Long, rngVeh As Range, rngTemp As Range, arrFin As Variant
Dim i As Long, j As Long, w As Long, count As Long, maxCount As Long, ar As Long, maxStr As String
Set sh = ActiveSheet 'use here your sheet
lastRow = sh.Range("A" & Rows.count).End(xlUp).row
Set rngVeh = sh.Range("A2:C" & lastRow)
ReDim arrFin(1 To lastRow, 1 To 1)
arrFin(1, 1) = "Result": ar = 1
For i = 2 To lastRow
If sh.Range("A" & i).Value = sh.Range("A" & i + 1).Value Then
For j = i To j + 1000 'create a range of type cells for the same vehicle no
If sh.Range("A" & j).Value = sh.Range("A" & i).Value Then
If rngTemp Is Nothing Then
Set rngTemp = sh.Range("B" & j)
Else
Set rngTemp = Union(rngTemp, sh.Range("B" & j))
End If
Else
Exit For
End If
Next j
If rngTemp Is Nothing Then
ar = ar + 1: arrFin(ar, 1) = sh.Range("B" & i)
Else
For w = 1 To rngTemp.Cells.count 'determine the max occurrences string
count = WorksheetFunction.CountIf(rngTemp, rngTemp.Cells(w, 1).Value)
If count > maxCount Then maxCount = count: maxStr = rngTemp.Cells(w, 1).Value
Next
For w = 1 To rngTemp.Cells.count
ar = ar + 1: arrFin(ar, 1) = maxStr 'fill the max count in the array
Next
End If
Set rngTemp = Nothing: maxCount = 0: count = 0 'reinitialize variables
i = i + w - 2 'move the iteration to the following vehicle
Else
ar = ar + 1: arrFin(ar, 1) = sh.Range("B" & i)
End If
Next i
'drop the result array at once
sh.Range("C1").Resize(UBound(arrFin, 1), UBound(arrFin, 2)).Value = arrFin
End Sub
Here is a VBA routine that uses:
A class object which has
key:= Vehicle number
item:= dictionary of associated vehicle types
key:= vehicle type
item:= count of the vehicle types
After collecting the information, we merely need to cycle through the dictionary and extract, for any given vehicle ID, the vehicle type that has the largest count.
This routine, since it works entirely with VBA arrays, should run pretty fast, even with large amounts of data.
Also, with this method, no sorting is required.
ASSUMES the data starts in cell A1 (could be changed if necessary)
ASSUMES results are as you show with Desired Result in column C
Be sure to set a reference (Tools/References) to Microsoft Scripting Runtime
Class Module (rename this module cVehicle)
Option Explicit
Private pVehicleType As String
Private pVehicleTypes As Dictionary
Public Property Get VehicleType() As String
VehicleType = pVehicleType
End Property
Public Property Let VehicleType(Value As String)
pVehicleType = Value
End Property
Public Property Get VehicleTypes() As Dictionary
Set VehicleTypes = pVehicleTypes
End Property
Public Function addVehicleTypesItem(Value)
If pVehicleTypes.Exists(Value) Then
pVehicleTypes(Value) = pVehicleTypes(Value) + 1
Else
pVehicleTypes.Add Key:=Value, Item:=1
End If
End Function
Private Sub Class_Initialize()
Set pVehicleTypes = New Dictionary
pVehicleTypes.CompareMode = TextCompare
End Sub
Regular Module
'Set Reference to Microsoft Scripting Runtime
Option Explicit
Sub vehicle()
Dim dV As Dictionary, cV As cVehicle
Dim wsData As Worksheet, vData As Variant, rRes As Range
Dim V As Variant, I As Long, sKey As String, cKey As String, Cnt As Long
'set data worksheet
'read data into vba array
Set wsData = Worksheets("Sheet3")
With wsData
'add extra column for the "desired results"
vData = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=3)
Set rRes = .Cells(1, 1)
End With
'loop through the data and count the types
'no sorting necessary
Set dV = New Dictionary
For I = 2 To UBound(vData, 1)
Set cV = New cVehicle
With cV
sKey = vData(I, 1)
.VehicleType = vData(I, 2)
If Not dV.Exists(sKey) Then
.addVehicleTypesItem .VehicleType
dV.Add sKey, cV
Else
dV(sKey).addVehicleTypesItem .VehicleType
End If
End With
Next I
'Output the data
I = 1
'Header
vData(I, 3) = "Desired Result"
'Data
For I = 2 To UBound(vData, 1)
sKey = vData(I, 1)
With dV(sKey)
'which type has the highest count
Cnt = 0
For Each V In .VehicleTypes.Keys
If .VehicleTypes(V) > Cnt Then
Cnt = .VehicleTypes(V)
cKey = V
End If
Next V
vData(I, 3) = cKey
End With
Next I
'write the results
Set rRes = rRes.Resize(UBound(vData, 1), UBound(vData, 2))
rRes = vData
End Sub