-1

Edit: This question has been re-worked to provide better clarity of my problem.

There's 2 factors to my question.

First Factor: I have a validation list in a sheet called "Admin Sheet". In that list are 'Tasks'. I would like to cross reference those tasks in the "list", against those contained in a range (rangeString) taken from another sheet and count the number of 'Occurrences' for each item.

i.e. Task 1 appears 3 times, Task 2 appears 1 time, etc etc..

Factor 2: For each item within the list I would also like to gather the number of 'Hours' spent on that task.

For example: Task 1 may appear 3 times on 3 different rows within the range. On each row in another column are the hours spent on that particular task. I would like to 'Sum' those hours from the 3 rows and I'd like to do this for all the 'Tasks'.

Note: The range is variable and will change daily. Note: The columns that contain the info are: 'F' - Tasks and 'K' for Hours.

My current attempt at just capturing 'one' Task and its Hours associated with it:

 Dim PaintWWArray() As Variant
 Dim PHoursCnt As Long

Set srchRng = ActiveSheet.Range(rangeString)
Set rngfindValue = srchRng.find(what:="AD PAINTING W/W", Lookat:=xlPart)

'Find all the Tasks and Hours
If Not rngfindValue Is Nothing Then
   rngFirstAddress = rngfindValue.Address
    Do
        PaintWWCnt = PaintWWCnt + 1
        PHoursCnt = rngfindValue.Offset(0, 4).Value

         ReDim Preserve PaintWWArray(PHoursCnt)
         PaintWWArray(PHoursCnt) = PHoursCnt

         Set rngfindValue = srchRng.FindNext(rngfindValue)


    Loop Until rngfindValue Is Nothing Or rngfindValue.Address = rngFirstAddress

     PWWSum = Application.WorksheetFunction.Sum(PaintWWArray)
     MsgBox PWWSum

End If    

Once I have collected the number of 'Occurrences' for each Task and the Sum of the hours for each task, I want to pass them into another sheet.

 Worksheets("Weekly Data").Range("C6").Value = PaintWWCnt
 Worksheets("Weekly Data").Range("D6").Value = PWWSum

I hope this is clearer...

J4C3N-14
  • 686
  • 1
  • 13
  • 32
  • Can't you use a dictionary? See https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/dictionary-object and https://stackoverflow.com/questions/1296225/iterate-over-vba-dictionaries – Peter Pesch May 09 '20 at 10:58
  • Are you trying to replicate what a pivot table does in VBA code? – freeflow May 09 '20 at 10:58
  • What do you want to do with the int value? – Peter Pesch May 09 '20 at 11:17
  • When you say 'in an array' are you sure that not wanting to say in a `Range`? Otherwise, `addDuty.Address` what meaning should have? Anyhow, like it is used it should return an error. Then, does the 'Array'/`Range` contain two columns (since you are talking about 'adiacent cells'), or more? Can you show us how the `arr` variable has been declared and how did you obtain it? – FaneDuru May 09 '20 at 11:21
  • I suppose it is very difficult to read our comments... To say something, should be even more difficult. And we must help you solving an issue you presented in a „personal” way... Very interesting. Thank you for offering the opportunity to guess. I, at least, didn't have something else to do and now I am not bored any more. – FaneDuru May 09 '20 at 12:16
  • @FaneDuru I do indeed mean Range very sorry. Edited. Yes the Range spans F to K of varying size. – J4C3N-14 May 09 '20 at 13:55
  • When you say "he Range spans F to K of varying size", should we understand that the range to be analysed can be somewhere between F and K columns, but it will contains two columns? I asked about the number of columns in my previous comment...Or the column keeping the strings is F:F and the one where the values are kept is K:K? If you answer in a reasonable period of time, I can prepare an answer for one of the above two scenarios... But, which of them to be? – FaneDuru May 09 '20 at 15:14
  • I posted an answer and stipulate the assumptions I had in mind. If something to be improved, or unclear, I will be available later... Please confirm that you tested it and with which result. If my assumptions are wrong, I can adapt it according to your reality, but please, try to better describe it... – FaneDuru May 09 '20 at 15:26
  • Hi, I have a range which spans from 'F' to 'K', the two columns that contain the data I want are in 'F' and 'K'. I want to match the 'Duties' with the 'Hours' that are in the same row. I then also want to count how many duties of each 'Type' there were and also how many 'Total' hours were spent on each Duty. Again, I'm very sorry if this isn't making sense, I can't think of how to describe it any better. – J4C3N-14 May 10 '20 at 08:40
  • 1
    Thank you for revising your question. Your code looks to me like an expensive COUNTIF() function: 16 lines of code instead of one. Please disclose the full address of your variable `rangeString`. You have said that tasks and duties are in "adjacent" cells. You have mentioned columns F and K. Your code has relevant data 4 columns apart, and you have also said the sheet format changes daily. If you can't describe where your source data are, post a picture. Please also post a picture of the "other sheet" on which you want to display the counting and summing results. – Variatus May 11 '20 at 02:34
  • Your updated question is not so clear, too. I mean, there are three columns to be returned: 'Tasks', 'Tasks count', 'Hours'.. I adapted the code to return in "Weekly Data" worksheet, starting from C6 (header). Is that what you want? – FaneDuru May 11 '20 at 07:30
  • 1
    Aren't these "Factors" just `WorksheetFunction.CountIfs` and `WorksheetFunction.Sumifs`? – Chronocidal May 11 '20 at 10:50

5 Answers5

1

I would suggest using a Dictionary.

Assuming you want to count all words:

Dim myDict
Set myDict = CreateObject("Scripting.Dictionary")
' Go through the array
For Each addDuty In arr
    ' If you only want to count specific words, add in IF statement here
    myDict(addDuty) = myDict(addDuty) + 1
Next addDuty 

If you only want to count words in an exiting set, it becomes slightly more elaborate.

Peter Pesch
  • 643
  • 5
  • 14
  • Basically I have a number of re-occurrences of different values. I want to count how many times each of these values appear and in a cell on the same row i want to count a 'number' that is related to the value. At the end I'd like to know the number of occurrences for each value and the total count of the numbers for each value. If you get me?? – J4C3N-14 May 09 '20 at 12:39
  • Not quite clear yet. Suppose there's a single text occurring 3 times. Would you expect the values to be 3, 3, 3? Or 1, 2, 3? Or something else? Sorry. I still do not understand what you want exactly ... – Peter Pesch May 09 '20 at 12:42
  • Sorry, I'll try to be clearer. So for example: if I have 3 different texts and each text occurs once, I'd expect that the value for each to be 1. If one of those Texts occurs twice, but the others once then for that one text that occurs twice id expect the count to be 2 and the others 1. Now the tricky part for me is I would now also like to know and count the value of another cell in a different column (K) in the same row that each text occurs on. So say row 10 & 15 have an occurrence of the same text in column (F), I'd like to be able to also count the value in column K. – J4C3N-14 May 09 '20 at 12:50
  • So that in the end I end up with a number of occurrences for each value and also a total count of the number in column K. – J4C3N-14 May 09 '20 at 12:51
  • Still not clear. Could you give an example? And please, edit that information into your original question. In that case, Variatus will (probably) be notified, so he can might be able to add to his very good answer. – Peter Pesch May 09 '20 at 12:56
  • Ok. what about this: I have different text in sequential rows, the text is a 'Task', related to this task is a number of 'Hours' spent on it in another column on the same row. I want to count the number of times certain text appears and also count the total number of hours spent on that task throughout the total number of occurrences. – J4C3N-14 May 09 '20 at 13:36
  • Please see my most recent Update to the question. Thank you. – J4C3N-14 May 09 '20 at 15:13
1

It's not entirely clear what you want to achieve but the code below should give you the data you need. It's very fast. Please try it.

Private Sub STO_Answer()
    ' 024
    ' this procedure requires a reference to be set to
    ' Microsoft Scripting Runtime

    Dim Counter         As Scripting.Dictionary     ' store task names and their count
    Dim Arr             As Variant                  ' an array of the data in Rng
    Dim CellVal         As Variant                  ' temporary storage of each cell value
    Dim R               As Long                     ' row counter
    Dim Key             As Variant                  ' a dictionary Key

    Arr = ActiveSheet.Range("C2:D27").Value         ' change to name the sheet
                                                    ' adjust the range to suit
    Set Counter = New Scripting.Dictionary
    With Counter
        For R = 1 To UBound(Arr)                    ' loop through all rows
            AddToCounter Arr(R, 1), Counter         ' first column of cell range
            AddToCounter Arr(R, 2), Counter         ' second column of cell range
        Next R

        For Each Key In Counter.Keys
            Debug.Print Key, Counter.Item(Key)
        Next Key
    End With
End Sub

Private Sub AddToCounter(CellVal As Variant, _
                         Counter As Scripting.Dictionary)
    ' 024

    With Counter
        If .Exists(CellVal) Then
            .Item(CellVal) = .Item(CellVal) + 1
        Else
            .Add CellVal, 1
        End If
    End With
End Sub

A Dictionary is a data structure which holds two related values. Here it's used to hold the task name and the number of times it occurs. Make sure you enable the reference to Microsoft Scripting Runtime in Tools > References. You don't specify if there is any relationship- between the tasks in the first column and the second. The above code counts both independently for now.

The result is printed to the Immediate Window. Of course, you might use this result in any other way in your code. Your question doesn't cover your intentions.

Variatus
  • 14,293
  • 2
  • 14
  • 30
  • Hi, there is a special relationship between them. I will describe more in the original question. Thanks. – J4C3N-14 May 09 '20 at 11:47
  • This looks amazing. So for example, Say I have 10 different Variables that I want to count the occurrences of, I want to have figure of how many times each variable occurred and in relation to each variable, on each row that the variable occurs there is a number 5 columns to the right that i want to count. I then pass the total count of occurrences for each variable along with the sum of the 'numbers' for each variable. I'm tripping over myself in trying to explain it sorry. – J4C3N-14 May 09 '20 at 12:12
  • So basically I have a 'Range' from F to K, the data I want are in column F and K. I would like to be able to associate the 'Duty' to the 'Hours' and get a total count for both. So total number of occurrences of the same duty and total hours spent of that duty. There are approx 20 different duties. Am I making any sense? Thank you. – J4C3N-14 May 10 '20 at 08:45
1

You won't be able to escape from the necessity to present your count in some way forever. As it turns out, there is only one efficient way to do it. This one:-

enter image description here

All duties are in column A and all added duties are in row 2.

Of course, you might use rather elaborate VBA to do the counting but Excel has a better way using a worksheet function. In order to set up COUNTIF() to work I created two named ranges as follows.

["Duties"] =OFFSET(Sheet2!$C$2,0,0,COUNTA(Sheet2!$C:$C)-1)
and
["AddDuties"] =OFFSET(Duties,0,1)

Sheet2!$C$2 is where my data started. Replace with the first cell of the first column of your data range. COUNTA(Sheet2!$C:$C)-1 makes this range dynamic. The function counts how many entries there are in that same column, -1 because the count would include a caption (modify if you have more or fewer headers).

AddDuties is simply defined as "same as Duties" but removed by one column to the right. You could move it elsewhere. As you add or delete rows in the column of Duties, AddDuties expands or contracts right along.

Now the formula in B3 is shown below. It's copied down and across as required. Please observe the $ signs.

[B3] =COUNTIFS(Duties,$A3,AddDuties,B$2)

This will probably generate a lot of zeroes. It did in my example and I didn't like them. Therefore I formatted B3 with the Custom cell format 0;; before copying to the other cells, which hides them.

Now this list would automatically update as you make entries in your data. You will never have to run code and the list will always be ready.

Finally, one recommendation. All your added duties, like "AD PAINITNG H/R", are hard to type correctly. Therefore the user should select them from a validation drop-down when entering them in the data. Most probably, you already have a list somewhere which feeds such drop-downs. The captions in the count list must be taken from the same source. But that creates redundancy. The better way is to make the list in B2:H2 of the count list the "original". Name the range and make it dynamic and you will never have to think about this subject again.

Variatus
  • 14,293
  • 2
  • 14
  • 30
  • Thank you for the excel version but I am ideally looking for a VBA solution. – J4C3N-14 May 10 '20 at 08:42
  • Now, for the first time, you are saying that you want to sum hours, not count tasks. I think you need to re-work your question. And when you do, it's absolutely essential that you show us the format of the output you have in mind. Moreover, I have already indicated that you should have a list of the additional duties that feeds a validation drop-down. Any solution here will need that list. If it exists, tell us where it is. If you don't have one you should make it now because it's easier for you to do than any of us. Add the info to your question. – Variatus May 10 '20 at 11:50
  • Point taken, I have re-worked the question, Thank you. – J4C3N-14 May 10 '20 at 12:18
0

i think a better approach would be to use for each loops, this way you won't have to hardcode the conditions via IfElse. If you have the values in column A of a sheet and wants to go through those values and get their adjacent value in column B, you can use For Each looping to go through each values defined in A to get B.

just to add, regarding on counting of occurrence, you can define a counter that would add up for each occurrence of a unique value in column A.

bakaneko
  • 1
  • 2
0

I do not have time to wait for clarifications I asked... I prepared a piece of code, starting from the assumption that your strings to be counted are in column "F:F", and the value to be calculated is in column "K:K". The processing result is dropped on the last available column of the active pages, starting from row 2. If you prefer some relevant headers for the two involved columns, this can be easily automated. I used "Tasks and "Time...

It is able to deal with as many 'task' strings you will have in the future.

I commented the code lines, where I thought you do not understand what they do:

Sub CountOccurrencesAndValues()
  Dim sh As Worksheet, rngF As Range, arrOcc As Variant, lastRow As Long, lastCol As Long
  Dim arr As Variant, arrFin As Variant, countI As Long, valH As Double, j As Long, k As Long, i As Long

  Set sh = ActiveSheet
  lastRow = sh.Range("F" & Rows.count).End(xlUp).Row
  lastCol = sh.UsedRange.Columns.count + 1
  Set rngF = sh.Range("F2:F" & lastRow) 'the range where from to extract the unique values
  arr = sh.Range("F2:K" & lastRow)      'the array to be processed

  'Extract the unique values. Use for that a not used column:
  rngF.AdvancedFilter Action:=xlFilterCopy, CopyToRange:=sh.Cells(1, lastCol), Unique:=True
  'Put the unique values (sttrings) in an array:
  arrOcc = sh.Range(sh.Cells(1, lastCol), sh.Cells(sh.Cells(Rows.count, lastCol).End(xlUp).Row, lastCol)).value
  'Clear the temporary used array:
  sh.Range(sh.Cells(1, lastCol), sh.Cells(sh.Cells(Rows.count, lastCol).End(xlUp).Row, lastCol)).Clear
  ReDim arrFin(1 To UBound(arrOcc, 1), 1 To 3)

  k = 1
  'Processing the range by iteration:
  For i = 1 To UBound(arrOcc, 1)
    For j = 1 To UBound(arr, 1)
      If arr(j, 1) = arrOcc(i, 1) Then
         'count the occurrences and the value
         countI = countI + 1: valH = valH + arr(j, 6)
      End If
    Next j
    'put the data in the final array
    arrFin(k, 1) = arrOcc(i, 1): arrFin(k, 2) = countI: arrFin(k, 3) = valH
    countI = 0: valH = 0: k = k + 1
  Next i

  'Drop the data from array in the last available column:
  'sh.Cells(1, lastCol).value = "Tasks": sh.Cells(1, lastCol + 1).value = "Count": sh.Cells(1, lastCol + 2).value = "Time"
  'sh.Cells(2, lastCol).Resize(UBound(arrFin, 1), UBound(arrFin, 2)).value = arrFin

  Dim ws As Worksheet
  Set ws = Worksheets("Weekly Data")
  'Drop the data from array in "Weekly Data" worksheet:
  ws.Range("C6").value = "Tasks": ws.Range("D6").value = "Count": ws.Range("E6").value = "Time"
  ws.Range("C7").Resize(UBound(arrFin, 1), UBound(arrFin, 2)).value = arrFin
End Sub
FaneDuru
  • 38,298
  • 4
  • 19
  • 27
  • Thank you. I haven't tried it yet. To explain my issue further. The range I have has been created by myself and is of a particular height. (7 days/multiple rows) this changes the 'Starting' point of the range (Row) each day. So within that defined range I need to match the 'Duty' to the Number of 'Hour' spent on it. Then I would like to get a total count of the number of occurrences of each 'Duty' and then also a Total 'Hours' spent on each duty..... – J4C3N-14 May 10 '20 at 08:51
  • @J4C3N: I would like to believe that this is what the code does. No important what the 'Duty` means. If it is a string, the code works. What was important for you to confirm was: **Is the column keeping 'Duty` F:F?** If yes, is the column, keeping the number of hours K:K? If yes, please just try the code as it is. If Not, **please specify which one is!**.The code can easily adapted for any column. I will be home after some hours. If you want testing the code for a different (hours) column, it is enough to change `("F2:K" & lastRow`, using it instead of 'k' and also adapt ` valH + arr(j, 6)`. – FaneDuru May 10 '20 at 10:33
  • You must use instead of 6, which means the sixth column **of the F:K range**, the appropriate column for the one you use. For instance, 2 for G:G, 3 for H:H, 4 for I:I and 5 for J:J... – FaneDuru May 10 '20 at 10:35
  • @J4C3N I used F and K according to my understanding of your comment saying something about K column to return total hours... – FaneDuru May 10 '20 at 10:41
  • Did you test my code? If not, why? The single difference will be that it returns the processed data on the first two unused columns, on the same sheet. Droping them in another sheet will not be a problem, but test the code, please! Your way of asking a question looks a kind of never ending story... I connot adapt the code on mobile phone, each tine you bring something new... – FaneDuru May 10 '20 at 13:19
  • Will test now...how should I include the range I use, I don't need the entire sheet just my range named "stringRange"? – J4C3N-14 May 10 '20 at 13:31
  • My code calculates the last row. Use the code like it is, please! – FaneDuru May 10 '20 at 14:10
  • It looks very complicated to copy paste the code and press F5... If the code does not return what you need, it is at least polite to give us some feedback. Somebody spent time to solve your problem and he deserves at least that... No offence, but before learning programming you must learn asking questions and propper behave in such a community. – FaneDuru May 11 '20 at 07:05
  • May I know why my answer has been voted down (once)? Of course, I am asking the one voting it down... – FaneDuru May 11 '20 at 20:41