H,
i have an inventory sheet in this format.(provided image below, did not know how to add table over here). The company name and category are not in separate columns. instead the are listed in the product column. i want to add 2 additional columns, one for company and one for category, and add details to every row correspondingly.
original format
desired format
what would be the simplest way to do this?
This should do what you want, it assumes your data starts in column A:
Sub ChangeFormat()
Dim CompanyName As String, Catgory As String, LR As Long
LR = Range("A" & Rows.Count).End(xlUp).Row
For X = 2 To LR
If X > LR Then Exit For
If Left(UCase(Range("A" & X).text), 8) = "COMPANY:" Then
CompanyName = Trim(Right(Range("A" & X).text, Len(Range("A" & X).text) - 8))
Rows(X).Delete
LR = LR - 1
End If
If Left(UCase(Range("A" & X).text), 9) = "CATEGORY:" Then
Category = Trim(Right(Range("A" & X).text, Len(Range("A" & X).text) - 9))
Rows(X).Delete
LR = LR - 1
End If
Range("E" & X).Formula = CompanyName
Range("F" & X).Formula = Category
Next
End Sub
It scans through and when it finds one of the headings it assigns that value to the variable then deletes the row, if it finds neither then it assumes it is data and posts the Company and Category to columns E and F (I think Catagory is spelled wrong but I went with your spelling of it).
Make sure you back up your data before running this as it will modify what you have.
Here's a macro to do the task. You can change the name of the sheet as indicated in the code.
Sub InventoryReformat()
Dim ar
Dim i As Long
Dim wRow As Long
Dim sTxt As String, sCompany As String, sCategory As String
Dim wsS As Worksheet, wsD As Worksheet
Set wsS = Sheets("SOURCE_DATA") 'Change as required
Set wsD = Sheets("DESTINATION") 'Change as required
ar = wsS.Range("A1").CurrentRegion.Value 'Change start cell as required
wRow = 1 'Change first destination row as required
With wsD
.Cells(wRow, 1).Resize(1, 6) = Split("PRODUCT|COST PRICE|SALE PRICE|TAX|CATEGORY|COMPANY", "|")
wRow = wRow + 1
For i = 2 To UBound(ar, 1)
sTxt = ar(i, 1)
If InStr(1, sTxt, "Company") > 0 Then
sCompany = Trim(Split(sTxt, ":")(1))
Else
If InStr(1, sTxt, "Category") > 0 Then
sCategory = Trim(Split(sTxt, ":")(1))
Else
.Cells(wRow, 1) = ar(i, 1)
.Cells(wRow, 2) = ar(i, 2)
.Cells(wRow, 3) = ar(i, 3)
.Cells(wRow, 4) = ar(i, 4)
.Cells(wRow, 5) = sCategory
.Cells(wRow, 6) = sCompany
wRow = wRow + 1
End If
End If
Next i
.Cells(wRow - 1, 1).CurrentRegion.Columns.AutoFit
End With
End Sub
.
Hope this will help you.
Related
I am trying to do a vlookup using vba to look up each country per continent and return a true value on each column. For example, I get a true under Europe Lookup if Belgium is listed as one of the countries. See below for the current code I have. Issues I want to fix are:
First, I want to be able to lookup each country to confirm if they are in the countries tab. That way, I know if there is a new country I need to add. What I have currently looks up to check if at least one of the countries is listed but would like to make sure all countries are listed.
Additionally, I want to make it automated such that if a new country is added to the countries list, I don't have to edit the macros.
Also, is there a way to do the lookup for separate continents one at a time? Currently, it just returns a true if the country is in the list irrespective of the continents. I know this means I'll have separate lines of codes for each continent but that's fine.
Sub Macro1()
Sheets("Sales Table").Select
Range("D2").Select
Dim LastRowColumnD As Long
LastRowColumnD = Cells(Rows.Count, 1).End(xlUp).Row
Range("D2:D" & LastRowColumnD).Formula = "=SUMPRODUCT(--ISNUMBER(SEARCH('Countries'!R2C1:R11C1,RC[-2])))>0"
Range("E2").Select
Dim LastRowColumnE As Long
LastRowColumnE = Cells(Rows.Count, 1).End(xlUp).Row
Range("E2:E" & LastRowColumnE).Formula = "=SUMPRODUCT(--ISNUMBER(SEARCH('Countries'!R2C1:R11C1,RC[-3])))>0"
End Sub
Not sure what kind of output are you trying to get, but something like this may work for you and you'll need to adapt a little:
Sub test()
Dim i As Long, j As Long, k As Long
Dim LR As Long
Dim Mydata As Variant
Dim WKData As Worksheet
Dim rngCountries As Range
Dim MyF As WorksheetFunction
Set MyF = WorksheetFunction
With ThisWorkbook.Worksheets("Countries")
LR = .Range("B" & .Rows.Count).End(xlUp).Row
Set rngCountries = .Range("A2:B" & LR)
End With
Set WKData = ThisWorkbook.Worksheets("Sales Table")
With WKData
LR = .Range("B" & .Rows.Count).End(xlUp).Row
Mydata = .Range("B2:B" & LR).Value
For i = 1 To UBound(Mydata) Step 1
j = UBound(Split(Mydata(i, 1), ", "))
'j = how many countries -1, so j+1= total countries in cell
For k = 0 To j Step 1
'we loop trough each country in cell
If MyF.CountIfs(rngCountries.Columns(1), Split(Mydata(i, 1), ", ")(k), rngCountries.Columns(2), "Europe") <> 0 Then .Range("E" & (i + 1)).Value = .Range("E" & (i + 1)).Value + 1 'Europe Check
If MyF.CountIfs(rngCountries.Columns(1), Split(Mydata(i, 1), ", ")(k), rngCountries.Columns(2), "Africa") <> 0 Then .Range("F" & (i + 1)).Value = .Range("F" & (i + 1)).Value + 1 'Africa Check
If MyF.CountIfs(rngCountries.Columns(1), Split(Mydata(i, 1), ", ")(k), rngCountries.Columns(2), "Asia") <> 0 Then .Range("G" & (i + 1)).Value = .Range("G" & (i + 1)).Value + 1 'Asia Check
Next k
'check all countries: if the sum equals k+1, then all countries in cell are present
.Range("D" & (i + 1)).Value = IIf(MyF.Sum(Range("E" & (i + 1) & ":G" & (i + 1))) = k, "YES", "NO")
Next i
End With
'clean variables
Erase Mydata
Set MyF = Nothing
Set rngCountries = Nothing
Set WKData = Nothing
End Sub
I've used arrays and Splits to create arrays so you can loop trough each individual country:
Notice I added "Portugal" to check the "NO" value in the "All countries" column. Every NO means there is a country in that cell that is not present in your range of countries.
The CurrentRegion property of Range will catch countries added to the Countries tab.
You don't necessarily need a separate line of code for each continent. If you keep a consistent column naming strategy, you can make the code flexible enough to catch added continents.
Sub macroSplitter()
Dim wb As Workbook
Dim salesSheet As Worksheet, Countries As Worksheet, continent As String
Dim j As Long
Set wb = ThisWorkbook
Set salesSheet = wb.Worksheets("Sales Table")
Set Countries = wb.Worksheets("Countries")
'Range.CurrentRegion selects a region contiguous with the designated cell
countryArray = Countries.Range("A2").CurrentRegion.Value2
For Each cell In salesSheet.Range("B2", salesSheet.Range("B2").End(xlDown))
splitCell = Split(cell, ", ")
For Each country In splitCell
If inCountry(country, countryArray) Then
continent = whichcontinent(country, countryArray)
'using match to look for continent column, and End(xlToRight) to allow for additional continents to be added
Cells(cell.Row, WorksheetFunction.Match(continent & " Lookup", salesSheet.Range("A1", salesSheet.Range("A1").End(xlToRight)), 0)).Value2 = True
Else
cell.Offset(0, 2).Value2 = False
End If
Next
If cell.Offset(0, 2).Value2 = vbNullString Then cell.Offset(0, 2).Value2 = True
For j = WorksheetFunction.Match("Europe Lookup", salesSheet.Range("A1", salesSheet.Range("A1").End(xlToRight)), 0) To salesSheet.Range("A1").End(xlToRight).Column
If Cells(cell.Row, j).Value2 = vbNullString Then Cells(cell.Row, j).Value2 = False
Next
Next cell
End Sub
Private Function inCountry(c, arr) As Boolean
Dim i As Long
For i = 2 To UBound(arr, 1)
If c = arr(i, 1) Then inCountry = True
Next
End Function
Private Function whichcontinent(c, arr) As String
Dim i As Long
For i = 2 To UBound(arr, 1)
If c = arr(i, 1) Then whichcontinent = arr(i, 2)
Next
End Function
I have a column with certain values which are also the headers for some columns. I want to check where the column values match and paste the value from the first column into the column with the same column name. I have around 1200 values in the first column. I want to loop through those values and paste the matching values in the corresponding row.
[![Data][1]][1]
Here is my sheet with my data that I want to work on. How I want my final sheet to look like is as follows:
Weeks | W1 | W2 | W3 | W4 | W5 | W6
W1 W1
W3 W3
Any help for the same would be highly appreciated.
Sub Weeks()
Application.ScreenUpdating = False
Dim i As Long, j As Long, Mas As Worksheet
Set Mas = Sheets("Master Sheet")
For i = 5 To 1200
If Mas.Range("B" & i) <> "" Then
If Mas.Range("AO" & i) = "Missing week" Then
Mas.Range("AV" & i) = ""
Mas.Range("AW" & i) = ""
Mas.Range("AX" & i) = ""
Mas.Range("AY" & i) = ""
Mas.Range("AZ" & i) = ""
Mas.Range("BA" & i) = ""
Else
For j = 5 To 1200
If Mas.Range("AO" & i) = "W1" Then
Mas.Range("AV" & j) = "W1"
GoTo Nexti
ElseIf Mas.Range("AO" & i) = "W2" Then
Mas.Range("AW" & j) = "W2"
GoTo Nexti
ElseIf Mas.Range("AO" & i) = "W3" Then
Mas.Range("AX" & j) = "W3"
GoTo Nexti
ElseIf Mas.Range("AO" & i) = "W4" Then
Mas.Range("AY" & j) = "W4"
GoTo Nexti
ElseIf Mas.Range("AO" & i) = "W5" Then
Mas.Range("AZ" & j) = "W5"
GoTo Nexti
ElseIf Mas.Range("AO" & i) = "W6" Then
Mas.Range("BA" & j) = "W6"
GoTo Nexti
End If
Next j
End If
End If
Nexti:
Next i
End Sub
This is the code I tried so far but it does not show any output.
This is how you use the dictionary to achieve your goal:
Option Explicit
Sub Weeks()
Dim Mas As Worksheet: Set Mas = ThisWorkbook.Sheets("Master Sheet")
With Mas
'Calculate thelast row
Dim i As Long
Dim LastRow As Long: LastRow .Cells(.Rows.Count, "AO").End(xlUp).Row
'insert your data into an array
Dim arr As Variant: arr = .Range("AO5:BA" & LastRow).Value
'Generate a dictionary with the headers
'this needs the library Microsoft Scripting Runtime under Tools->References
Dim Headers As Dictionary: Set Headers = LoadHeaders(.Range("AO4:BA4").Value)
'Now loop through the array
For i = 1 To UBound(arr)
If Headers.Exists(arr(i, 1)) Then arr(i, Headers(arr(i, 1))) = arr(i, 1)
arr(i, 1) = vbNullString
Next i
.Range("AO5:BA" & LastRow).Value = arr
End With
End Sub
Private Function LoadHeaders(arr As Variant) As Dictionary
Set LoadHeaders = New Dictionary
Dim i As Long
For i = 1 To UBound(arr, 2)
LoadHeaders.Add arr(1, i), i
Next i
End Function
You won't even need the Application.ScreenUpdating because it does only one operation in Excel, will take a second or two to end this procedure.
Place this code in a module and link it to a button on the sheet.
'data
Cells.Clear
wks = Array("W1", "W2", "W3", "W4", "W5", "W6", "Missing week")
For i = 1 To 30
Cells(i, 2) = wks(Int(Rnd * 7))
Next i
'code
Set weeks = [b:b]
For Each wk In weeks
If Len(wk) = 2 Then Cells(wk.Row, Right(wk, 1) + 2) = wk
Next wk
weeks contains the column that has the list of column headings. The For..Each statement loops through each entry in this list.
Then it checks each entries string length. If it is length 2, it assumes the entry is valid (i.e of the form 'Wx' with x between 1 and 6), and then uses the inbuilt Right function to find the value of x, and then adds the appropriate entry into the appropriate column.
Beforehand, be aware that I just began using VBA, and I have few coding experience prior to it.
I have two sheets:
public
contacts
There is one parameter on column A that is definitely on "contacts" sheet, but may be or not be on column A on "public" sheet.
What I'm doing is:
Checking if the parameter contacts.A2 is on public.A2.
If it is, I need to copy columns, on the exact order:
public: A, C, G.
contacts: E, F.
I've found the following code online, and I'm running some adaptations to it, but I'm stuck.
Sub match()
Dim I, total, frow As Integer
Dim found As Range
total = Sheets("public").Range("A" & Rows.Count).End(xlUp).Row
'MsgBox (total) '(verifica se a contagem está ok)
For I = 2 To total
pesquisa = Worksheets("public").Range("A" & I).Value
Set found = Sheets("contacts").Columns("A:A").Find(what:=pesquisa) 'finds a match
If found Is Nothing Then
Worksheets("result").Range("W" & I).Value = "NO MATCH"
Else
frow = Sheets("contacts").Columns("A:A").Find(what:=pesquisa).Row
Worksheets("result").Range("A" & I).Value = Worksheets("public").Range("A" & frow).Value
Worksheets("result").Range("B" & I).Value = Worksheets("public").Range("C" & frow).Value
Worksheets("result").Range("C" & I).Value = Worksheets("public").Range("G" & frow).Value
Worksheets("result").Range("D" & I).Value = Worksheets("contacts").Range("F" & frow).Value
Worksheets("result").Range("E" & I).Value = Worksheets("contacts").Range("G" & frow).Value
End If
Next I
End Sub
What I expect:
to the code do ignore the line 1, as those are headers;
to eliminate de IF above, since I don't need the "NO MATCH"
to the resulting list to be ordered on ascending order, based on the A column.
Can you help me?
edited to include samples of the data and expected results:
I believe I can simplify my needs with the images above. I want to check a client on the public sheet, grab the manager contacts (emails) from the contacts sheet, and create a list that contains branch, manager, and both e-mails on the results sheet.
Creating those images, I realized I have forgotten to account for the second parameter (manager), as there can be multiple managers on a branch. So this is another parameter to account for.
`Public sheet (image)
Contacts sheet(image)
Result sheet(image)
spreadsheet
`
As per my comments, and your updated question with sample, I do believe that your current results do not match that what you say is required; which is looking for both parameters "Branch" and "Manager". Neither does your expected result look like the columns you wanted to extract according to your question. However, going by your sample data and expected output I tried the following:
Sub BuildList()
'Define your variables
Dim x As Long, y As Long
Dim arr1 As Variant, arr2 As Variant
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
'Fill 1st array variable from sheet Contacts
With Sheet1 'Change accordingly
x = .Cells(.Rows.Count, 1).End(xlUp).Row
arr1 = .Range("A2:D" & x).Value
End With
'Fill dictionary with first array
For x = LBound(arr1) To UBound(arr1)
dict.Add arr1(x, 1) & "|" & arr1(x, 2), arr1(x, 3) & "|" & arr1(x, 4)
Next x
'Fill 2nd array variable from sheet Public
With Sheet2 'Change accordingly
x = .Cells(.Rows.Count, 1).End(xlUp).Row
arr2 = .Range("A2:B" & x).Value
End With
'Compare array against dictionary and fill sheet Results
With Sheet3 'Change accordingly
y = 2
For x = LBound(arr2) To UBound(arr2)
If dict.Exists(arr2(x, 1) & "|" & arr2(x, 2)) Then
.Cells(y, 1).Value = arr2(x, 1)
.Cells(y, 2).Value = arr2(x, 2)
.Cells(y, 3).Value = Split(dict(arr2(x, 1) & "|" & arr2(x, 2)), "|")(0)
.Cells(y, 4).Value = Split(dict(arr2(x, 1) & "|" & arr2(x, 2)), "|")(1)
y = y + 1
End If
Next x
End With
End Sub
This solution makes use of arrays and dictionary which should be fast. It has given me the following result:
As David suggested, it would be better to have an input and output sample. Maybe you can try this:
Option Explicit
Public Sub match()
Dim wsPub As Worksheet
Dim wsCon As Worksheet
Dim wsRes As Worksheet
Dim pubRow As Long
Dim conRow As Long
Dim resRow As Long
Dim i As Long
Dim rng As Range
Dim cel As Range
Dim found As Long
Dim order(1 To 5) As Integer
Set wsPub = ThisWorkbook.Worksheets("public")
Set wsCon = ThisWorkbook.Worksheets("contacts")
Set wsRes = ThisWorkbook.Worksheets("result")
pubRow = wsPub.Cells(wsPub.Rows.Count, 1).End(xlUp).Row
conRow = wsCon.Cells(wsPub.Rows.Count, 1).End(xlUp).Row
resRow = wsRes.Cells(wsRes.Rows.Count, 1).End(xlUp).Row
Set rng = wsPub.Range("A2:A" & pubRow)
order(1) = 1
order(2) = 3
order(3) = 7
order(4) = 6
order(5) = 7
For Each cel In rng
If Not IsError(Application.match(cel.Value, wsCon.Range("A2:A" & conRow), 0)) Then
found = Application.match(cel.Value, wsCon.Range("A2:A" & conRow), 0) + 1
resRow = wsRes.Cells(wsRes.Rows.Count, 1).End(xlUp).Row
For i = 1 To 5
If i < 4 Then
wsRes.Cells(resRow, i).Offset(1, 0).Value _
= cel.Offset(0, order(i) - 1).Value
Else
wsRes.Cells(resRow, i).Offset(1, 0).Value _
= wsCon.Cells(found, order(i)).Value
End If
Next
End If
Next
wsRes.Range("A1").AutoFilter
wsRes.AutoFilter.Sort.SortFields.Clear
wsRes.AutoFilter.Sort.SortFields.Add2 Key:= _
Range("A1:A" & resRow), SortOn:=xlSortOnValues, order:=xlAscending, DataOption:= _
xlSortNormal
wsRes.AutoFilter.Sort.Apply
End Sub
1) Write a statement in excel that will insert rows and fill missing data for days missing any hour. Hours in “DATE_HR” should go from 00-23 (24 hour time).
And
2) For hours that are listed, under “DATE_HR” (DD-MMM-YYYY-HH), that are missing “0” (which is NDG in “Class”) “1-4”, “GR”, and/or “SB” in “CLASSIFICATION”, for any given hour, write a statement that will insert and fill missing rows in all hours that has the missing “CLASSIFICATION”, “Class”, “DATE_HR”, AND “Total” (which missing “TOTAL” row values should be zero since there was no entry for the missing data).
Below is an example of what the program needs to do. The left is the missing data table (before) and the right is the corrected table (after), Yellow is 1 and blue is 2
-Here is my progress up to now:
I have written pseudo code for the issue and have started writing at in excel VBA. Here is the pseudo code:
SR = Selected_row
RA = Row_above
C = Classification
DT = Date & Time
IR=Insert_row
# = Any number 1-4
Start on seleted row
Loop statement:
= IF(SRC = ”GR” AND RAC = 4 AND SRDT== RADT, SR,
OR(SRC = ”SB” AND RAC = “GR” AND SRDT== RADT, SR,
OR(SRC = 0 AND RAC = “SB” AND SRDT== RADT -1day/+22hour, SR,
OR(SRC = # AND RAC = SRC -1 AND SRDT == RADT, SR,
OR(SRC = 0 AND RADT = -1day of SRC/23hour, SRC = “0” AND SRDT= RADT +1day/00hour,IR AND
IF(RAC = ”SB” AND RADT = 23hour, SRC = “0” AND SRDT= RADT +1day/00hour,
OR (RAC = ”SB”, SRC = “0” AND SRDT= RADT +1hour,
OR (RAC = ”GR”, SRC = “SB” AND SRDT= RADT,
OR (RAC = 4, SRC = “GR” AND SRDT= RADT,
OR(RAC = # AND SRC = RAC +1 AND SRDT == RADT, SR *here # = 0-3
)))))))))))))
Move onto next row below previous row
IF(SR=””, END program, continue)
Here is the excel VBA code: (the colors are just it see if it’s doing what it should)
Sub IF_Loop()
Dim i As Long
For i = 2 To 155
If (Range("B" & i).Value = "GR" And Range("B" & i - 1).Value = 4 And Range("C" & i).Value = Range("C" & i - 1).Value) Or _
(Range("B" & i).Value = "SB" And Range("B" & i - 1).Value = "GR" And Range("C" & i).Value = Range("C" & i - 1).Value) Or _
(Range("B" & i).Value = "4" And Range("B" & i - 1).Value = "3" And Range("C" & i).Value = Range("C" & i - 1).Value) Or _
(Range("B" & i).Value = "3" And Range("B" & i - 1).Value = "2" And Range("C" & i).Value = Range("C" & i - 1).Value) Or _
(Range("B" & i).Value = "2" And Range("B" & i - 1).Value = "1" And Range("C" & i).Value = Range("C" & i - 1).Value) Or _
(Range("B" & i).Value = "1" And Range("B" & i - 1).Value = "00" And Range("C" & i).Value = Range("C" & i - 1).Value) Then
Rows(i & ":" & i).Interior.Color = 9359529
Else
'insert row and correct data
Rows(i & ":" & i).EntireRow.Insert shift:=x1Down And _
Rows(i & ":" & i)
End If
Next i
I’m not sure how to write the remaining code. How do you properly write the remaining lines so the code will execute the tasks that are needed?
I would do this differently.
You need to know your starting and ending dates, and you also need to have a list of ALL of the Classifications and the associated Classes. (I hard coded both within the macro, but you can use other schemes).
From that you can create a table with all of the classes and all of the hours for all of the dates.
Once you've done that, you can look up to see if the Totals are available for the classification/date combination, and either write that in, or, if not present, a zero.
I used a class object which contains the information. Each of these objects has a collection (dictionary) of all the date_hr | total combinations available for that classification, and also a method to return the class for a given classification.
Working with VBA arrays is orders of magnitude faster than multiple read/writes to/from worksheets.
I hopefully have commented the code enough so you can understand what is going on.
For an excellent discussion of class objects, see the late Chip Pearsons Introduction to Classes. If this link dies, you'll need to do a web search. There is also an article there on reading/writing arrays to/from worksheet ranges which you will find useful.
Read the comments, especially at the beginning of each module, carefully so as to properly set things up, otherwise, it won't run.
It does assume that your data has a header row, and starts in A1.
The results are placed on the same worksheet, but it should be obvious how to change that.
Class Module
'**Rename this module: cClass**
Option Explicit
Private pClass As String
Private pClassification As String
Private pDate_HR As Date
Private pDate_HRs As Dictionary
Public Property Get class() As String
Select Case Me.Classification
Case "1"
class = "Freshman"
Case "2"
class = "Sophomore"
Case "3"
class = "Junior"
Case "4"
class = "Senior"
Case "GR"
class = "Graduate"
Case "SB"
class = "Second Bachelor"
Case "0"
class = "NDG"
Case Else
class = "N/A"
End Select
End Property
Public Property Get Classification() As String
Classification = pClassification
End Property
Public Property Let Classification(Value As String)
pClassification = Value
End Property
Public Property Get Date_HR() As Date
Date_HR = pDate_HR
End Property
Public Property Let Date_HR(Value As Date)
pDate_HR = Value
End Property
Public Property Get Date_HRs() As Dictionary
Set Date_HRs = pDate_HRs
End Property
Public Function addDate_HRsItem(dtHR As Date, toTAL As Long)
Date_HRs.Add Key:=dtHR, Item:=toTAL
End Function
Private Sub Class_Initialize()
Set pDate_HRs = New Dictionary
pDate_HRs.CompareMode = TextCompare
End Sub
Regular Module
Option Explicit
'set reference to microsoft scripting runtime
Sub fillData()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim I As Long, J As Long
Dim dD As Dictionary, cc As cClass
Dim sKey As String, sDTkey As Date
'set source and results worksheets, range
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet1")
Set rRes = wsRes.Cells(1, 7)
'read source data into vba array
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=4)
End With
'Process the known data
'collect it into a dictionary for fast lookups
Set dD = New Dictionary
dD.CompareMode = TextCompare
For I = 2 To UBound(vSrc, 1)
Set cc = New cClass
With cc
.Classification = vSrc(I, 1)
.Date_HR = convDTHR(vSrc(I, 3))
.addDate_HRsItem .Date_HR, CLng(vSrc(I, 4))
sKey = .class
If Not dD.Exists(sKey) Then
dD.Add sKey, cc
Else
dD(sKey).addDate_HRsItem .Date_HR, CLng(vSrc(I, 4))
End If
End With
Next I
'Create Results Array
'Unclear from your question how many dates you want, so will
' just do Mar 4
Const dtStart As Date = #3/4/2019#
Const dtEnd As Date = #3/5/2019#
'code the list of all Classifications
Dim arrClass
arrClass = Array(0, 1, 2, 3, 4, "GR", "SB")
ReDim vRes(0 To (dtEnd - dtStart + 1) * 24 * (UBound(arrClass) + 1), 1 To 4)
'write the column Headers into a results array
For J = 1 To 4
vRes(0, J) = vSrc(1, J)
Next J
'fill in other columns
For I = 1 To UBound(vRes, 1) Step UBound(arrClass) + 1
For J = 0 To UBound(arrClass)
vRes(I + J, 1) = arrClass(J) 'Classification
vRes(I + J, 2) = convCLASS(arrClass(J)) 'class
vRes(I + J, 3) = Format(dtStart + Int((I + J - 1) / (UBound(arrClass) + 1)) / 24, "dd-mmm-yyyy hh") 'The Date_hr
sKey = vRes(I + J, 2) 'key into dictionary
If dD.Exists(sKey) Then
sDTkey = convDTHR(vRes(I + J, 3)) 'key into collection of date/totals within the dictionary item
If dD(sKey).Date_HRs.Exists(sDTkey) Then
vRes(I + J, 4) = dD(sKey).Date_HRs(sDTkey)
Else
vRes(I + J, 4) = 0
End If
Else
vRes(I + J, 4) = 0
End If
Next J
Next I
Set rRes = rRes.Resize(UBound(vRes, 1) + 1, UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
.Columns(1).HorizontalAlignment = xlCenter
.EntireColumn.AutoFit
End With
End Sub
Private Function convDTHR(strDTHR) As Date
convDTHR = CDate(Left(strDTHR, 11)) + Right(strDTHR, 2) / 24
End Function
Private Function convCLASS(strClassification) As String
Dim cc As cClass
Set cc = New cClass
With cc
.Classification = strClassification
convCLASS = .class
End With
End Function
first time poster, long time reader.
Apologies if this is hard to follow.
I have a spreadsheet which has a list of first names and last names. What I am wanting to do is take all of the first names which have the same last name and place them, evenly(ish) and separated by a comma, into the 3 reference columns in the same spreadsheet for example;
Example of Completed Sheet
I would like to do this in VBA because there are 200+ names and growing, and later the code will use this information to create and populate more workbooks.
So far, what I have works for all last names which have 3 or less first names (ie; one per column) but I cannot get it to work for last names where there are more than 3 first names.
My thought was to read all of the names into an array, split out the elements which have more than 3 names into another array, join these together separated by a comma, to then be transferred to the relevant column on the sheet.
However for some reason, I cannot get it to output more than one name into the column.
I have had a few attempts at this, but this is my latest attempt;
Private Sub cmdUpdate_Click()
Dim i As Long
Dim j As Long
Dim k As Long
Dim l As Long
Dim m As Long
Dim namesPerCol As Long
Dim strLastNameMatches As String
Dim arrNames() As String
Dim arrMultiNames(3) As String
Application.ScreenUpdating = False
With ActiveSheet
'Finds the last row with data in it
lngLastRow = .Cells(.Rows.count, "A").End(xlUp).Row
End With
'Sort the Columns
Columns("A:E").Sort key1:=Range("A1"), Header:=xlYes
'Loop through the LastNames
For i = 2 To lngLastRow
'Second loop through the LastNames
For j = 2 To lngLastRow
'If the last name matches
If Cells(i, 2).Value = Cells(j, 2).Value Then
'If the cell is empty then
If Range("C" & i).Value = "" Then
'Place the name in colA into colC
Range("C" & i).Value = Range("A" & j).Value
Else
'If the cell is not empty, then place a comma and space and then the value from colA
Range("C" & i).Value = Range("C" & i).Value & ", " & Range("A" & j).Value
End If
End If
Next j
Next i
For i = 2 To lngLastRow
strLastNameMatches = Range("C" & i).Value
arrNames = Split(strLastNameMatches, ", ")
If UBound(arrNames) > 2 Then
namesPerCol = UBound(arrNames) / 3
For l = 0 To 1
For k = LBound(arrNames) To namesPerCol
arrMultiNames(l) = arrNames(k) & ", "
Next k
Next l
For m = LBound(arrMultiNames) To UBound(arrMultiNames)
Select Case m
Case 0
Range("C" & i).Value = arrMultiNames(m)
Case 1
Range("D" & i).Value = arrMultiNames(m)
Case 2
Range("E" & i).Value = arrMultiNames(m)
End Select
Next m
Else
For j = LBound(arrNames) To UBound(arrNames)
Select Case j
Case 0
Range("C" & i).Value = arrNames(j)
Case 1
Range("D" & i).Value = arrNames(j)
Case 2
Range("I" & i).Value = arrNames(j)
End Select
Next j
End If
Next i
Application.ScreenUpdating = True
End Sub
Apologies for the poor quality coding, I will work on tiding it up once it is all working.
Any help I can get to get this code splitting out the names evenly across the three columns will be greatly appreciated
This task might be simpler if you could store your data into a more tree-like structure. There are many ways to do this; I've used the Collection object as it's easy to handle an unknown number of items. Basically, there are collections within a collection, ie one collection of first names for each last name.
The sample below uses very rudimentary distribution code (which is also hard-coded to a split of 3), but the point is that iterating through and down the tree is far simpler:
Dim lastList As Collection, firstList As Collection
Dim lastText As String, firstText As String
Dim data As Variant, last As Variant, first As Variant
Dim output() As Variant, dist(1 To 3) As Long
Dim str As String
Dim r As Long, c As Long, i As Long
'Read data into an array
With Sheet1
data = .Range(.Range("A1"), .Cells(.Rows.Count, "B").End(xlUp)).Value2
End With
'Create lists of unique lastnames containing the firstnames
Set lastList = New Collection
For r = 2 To UBound(data, 1)
firstText = CStr(data(r, 1))
lastText = CStr(data(r, 2))
Set firstList = Nothing
On Error Resume Next
Set firstList = lastList(lastText)
On Error GoTo 0
If firstList Is Nothing Then
Set firstList = New Collection
lastList.Add firstList, lastText
End If
firstList.Add firstText
Next
'Write results to sheet
ReDim output(1 To UBound(data, 1) - 1, 1 To 3)
For r = 2 To UBound(data, 1)
lastText = CStr(data(r, 2))
Set firstList = lastList(lastText)
'Calculate the distribution
dist(3) = firstList.Count / 3 'thanks #Comitern
dist(2) = dist(3)
dist(1) = firstList.Count - dist(2) - dist(3)
i = 1: c = 1: str = ""
For Each first In firstList
str = str & IIf(i > 1, ", ", "") & first
i = i + 1
If i > dist(c) Then
output(r - 1, c) = str
i = 1: c = c + 1: str = ""
End If
Next
Next
Sheet1.Range("C2").Resize(UBound(output, 1), UBound(output, 2)).Value = output