• Hi All

    Please note that at the Chandoo.org Forums there is Zero Tolerance to Spam

    Post Spam and you Will Be Deleted as a User

    Hui...

  • When starting a new post, to receive a quicker and more targeted answer, Please include a sample file in the initial post.

Unique List of Names and Count Type of Project

DangerMan88

New Member
Hey,

I am trying to create a unique list of names from multiple columns, in which each row contains the names of everyone on a project team, and the level of the project they are working on. Since an individual can be working on more than 1 team, there may be duplicate names when trying to make a list.

I want to make a list of names without duplicates from all of these columns, which I have found formulas to do this, but they do not seem to be working. I then want to create a count of how many of each type of project these people are involved in.

I have uploaded a sample file to hopefully help explain this further.

Regards,

Dan
 

Attachments

  • KaizenLog.xlsx
    15.4 KB · Views: 8
Hi, DangerMan88!

Give a look at the uploaded file. This is the involved code:
Code:
Option Explicit

Sub RollingSetback()
    '
    ' constants
    Const ksWSInput = "Kaizen Log"
    Const ksTeamMembersInput = "TeamMembersTable"
    Const ksTypeInput = "TypeList"
    Const ksMembersInput = "MembersTable"
    Const ksWSOutput = "Kaizens Per Person"
    Const ksNamesOutput = "NamesTable"
    Const ksDictKey = ",Quick,Standard,Major"
    Const ksDictValue = ",4,2,1"
    '
    ' declarations
    Dim rngTMI As Range, rngTI As Range, rngMI As Range, rngNO As Range
    Dim sDictKey() As String, sDictValue() As String
    Dim arrTMI As Variant, arrTI As Variant, arrMI As Variant, arrNO As Variant
    Dim sdObj As Scripting.Dictionary, sdType As Scripting.Dictionary
    Dim I As Long, J As Long, K As Integer, L As Integer, M As Integer, A As String, B As String
    '
    ' start
    With Worksheets(ksWSInput)
        ' ranges
        Set rngTMI = .Range(ksTeamMembersInput)
        Set rngTI = .Range(ksTypeInput)
        Set rngMI = .Range(ksMembersInput)
        ' arrays
        arrTMI = .Range(ksTeamMembersInput).Value
        arrTI = .Range(ksTypeInput).Value
        arrMI = .Range(ksMembersInput).Value
    End With
    With Worksheets(ksWSOutput)
        ' ranges
        Set rngNO = .Range(ksNamesOutput)
        ' arrays
        arrNO = .Range(ksNamesOutput).Value
    End With
    With rngNO
        If .Rows.Count > 1 Then Range(.Rows(2), .Rows(.Rows.Count)).ClearContents
    End With
    '  objects
    '  2d
    Set sdObj = New Scripting.Dictionary
    '  1d
    sDictKey() = Split(ksDictKey, ",")
    sDictValue() = Split(ksDictValue, ",")
    Set sdType = New Scripting.Dictionary
    With sdType
        For I = 1 To UBound(sDictKey)
            .Add sDictKey(I), Val(sDictValue(I))
        Next I
    End With
    '
    ' process
    '  ranges
    sdObj.RemoveAll
    Debug.Print "rng", rngTMI.Rows.Count, rngTMI.Columns.Count, Now(),
    With rngMI
        For I = 1 To .Rows.Count
            ' type
            B = rngTI.Cells(I, 1).Value
            K = sdType(B)
            ' teams
            For J = 1 To .Columns.Count
                A = .Cells(I, J).Value
                If A <> "" Then
                    If Not sdObj.Exists(A) Then
                        ' new entry
                        sdObj.Add A, K
                    Else
                        ' check if update entry
                        L = sdObj(A)
                        M = L And K
                        If M <> K Then sdObj(A) = (L Or K)
                    End If
                End If
            Next J
        Next I
    End With
    Debug.Print Now()
    '  arrays
    sdObj.RemoveAll
    Debug.Print "arr", UBound(arrMI, 1), UBound(arrMI, 2), Now(),
    For I = 1 To UBound(arrMI, 1)
        ' type
        B = arrTI(I, 1)
        K = sdType(B)
        ' teams
        For J = 1 To UBound(arrMI, 2)
            A = arrMI(I, J)
            If A <> "" Then
                If Not sdObj.Exists(A) Then
                    ' new entry
                    sdObj.Add A, K
                Else
                    ' check if update entry
                    L = sdObj(A)
                    M = L And K
                    If M <> K Then sdObj(A) = (L Or K)
                End If
            End If
        Next J
    Next I
    Debug.Print Now()
    '  output from dictionary
    With sdObj
        For I = 0 To .Count - 1
            ' member
            rngNO.Cells(I + 2, 1).Value = .Keys(I)
            For J = 2 To 0 Step -1
                ' team
                If (.Items(I) And (2 ^ J)) = (2 ^ J) Then rngNO.Cells(I + 2, 4 - J).Value = "X"
            Next J
        Next I
    End With
    '
    ' end
    '  objects
    Set sdObj = Nothing
    '  ranges
    Set rngNO = Nothing
    Set rngMI = Nothing
    Set rngTI = Nothing
    Set rngTMI = Nothing
    '
End Sub

There are 2 versions embedded into the code, 1st using ranges, 2nd using arrays. I tend to mostly use range versions but depending on the number of records involved using arrays will reduce running time to 1/6 approximately.

In the "process" section you'd find that the dictionary is cleared and first filled from ranges and then cleared again and filled from arrays. In the Immediate window you'd see the running times for both versions.

Since the created dictionary is the same in both cases, I simply used the last one to generate the output.

Just advise if any issue.

Regards!

PS: Idea borrowed without permission from @Chihiro's post in this @PaulF's thread:
http://chandoo.org/forum/threads/matrix-to-list-database.35258/#post-210871
 

Attachments

  • KaizenLog.xlsm
    30.6 KB · Views: 6
Glad my code was of some use ;)

FYI - You don't need to set objects to nothing at the end, since VBA will purge object once process terminates. If clearing objects, I'd do so during the process when the object isn't needed.

See link for an example where I used "Erase arrayname" to release memory by clearing array after all necessary steps are completed and put into dictionary.
https://www.excelforum.com/excel-pr...1193432-vba-sumifs-very-slow.html#post4701989
 
Formula solution way,

1] A3, array formula copied down :

=IFERROR(INDIRECT("'Kaizen Log'!"&TEXT(MIN(IF((COUNTIF(A$1:A1,'Kaizen Log'!$C$3:$H$24)=0)*ISTEXT('Kaizen Log'!$C$3:$H$24),ROW('Kaizen Log'!$3:$24)/1%+{3,4,5,6,7,8})),"R0C00"),),"")

2] B3, array formula copied across right and all copied down :

=IF($A3="","",IF(COUNT(FIND(B$2,INDEX('Kaizen Log'!$B$3:$B$24,N(IF({1},0+LEFT(SMALL(IF(('Kaizen Log'!$C$3:$H$24=$A3)*ISTEXT('Kaizen Log'!$C$3:$H$24),ROW($3:$24)/1%+{3,4,5,6,7,8}),{1,2,3}),LEN(SMALL(IF(('Kaizen Log'!$C$3:$H$24=$A3)*ISTEXT('Kaizen Log'!$C$3:$H$24),ROW($3:$24)/1%+{3,4,5,6,7,8}),{1,2,3}))-2)-2))))),"X",""))

3] Please refer to attached file.

p.s. array formula to be confirmed by pressing SHIFT+CTRL+ENTER 3 keystrokes together instead of just ENTER.

Regards
Bosco
 

Attachments

  • KaizenLog(formula way).xlsx
    20 KB · Views: 5
Hi, Chihiro!
FYI - You don't need to set objects to nothing at the end, since VBA will purge object once process terminates. If clearing objects, I'd do so during the process when the object isn't needed.
AFAIK this is true from Excel versions 2010+, but for older ones -2007 in particular, which is still highly deployed- it is strongly recommended.
VBA garbage collector uses a reference count that automatically destroys objects when it reaches zero. From 2010 in advance it's supposed to behave as expected... even if with Redmond guys nothing is fully assured.
Regards!
 
........
2] B3, array formula copied across right and all copied down :
=IF($A3="","",IF(COUNT(FIND(B$2,INDEX('Kaizen Log'!$B$3:$B$24,N(IF({1},0+LEFT(SMALL(IF(('Kaizen Log'!$C$3:$H$24=$A3)*ISTEXT('Kaizen Log'!$C$3:$H$24),ROW($3:$24)/1%+{3,4,5,6,7,8}),{1,2,3}),LEN(SMALL(IF(('Kaizen Log'!$C$3:$H$24=$A3)*ISTEXT('Kaizen Log'!$C$3:$H$24),ROW($3:$24)/1%+{3,4,5,6,7,8}),{1,2,3}))-2)-2))))),"X",""))..........

In formula [2], if you wanted "Number of Name count" instead of "X", then changed it to :

=IF($A3="","",IFERROR(1/(1/SUMPRODUCT(0+IFERROR(INDEX('Kaizen Log'!$B$3:$B$24,N(IF({1},0+LEFT(SMALL(IF(('Kaizen Log'!$C$3:$H$24=$A3)*ISTEXT('Kaizen Log'!$C$3:$H$24),ROW($3:$24)/1%+{3,4,5,6,7,8}),ROW($A$1:$A$10)),LEN(SMALL(IF(('Kaizen Log'!$C$3:$H$24=$A3)*ISTEXT('Kaizen Log'!$C$3:$H$24),ROW($3:$24)/1%+{3,4,5,6,7,8}),ROW($A$1:$A$10)))-2)-2)))=B$2,0))),""))

Regards
Bosco
 

Attachments

  • KaizenLog(formula way2).xlsx
    19.6 KB · Views: 3
In formula [2], if you wanted "Number of Name count" instead of "X", then changed it to :
=IF($A3="","",IFERROR(1/(1/SUMPRODUCT(0+IFERROR(INDEX('Kaizen Log'!$B$3:$B$24,N(IF({1},0+LEFT(SMALL(IF(('Kaizen Log'!$C$3:$H$24=$A3)*ISTEXT('Kaizen Log'!$C$3:$H$24),ROW($3:$24)/1%+{3,4,5,6,7,8}),ROW($A$1:$A$10)),LEN(SMALL(IF(('Kaizen Log'!$C$3:$H$24=$A3)*ISTEXT('Kaizen Log'!$C$3:$H$24),ROW($3:$24)/1%+{3,4,5,6,7,8}),ROW($A$1:$A$10)))-2)-2)))=B$2,0))),""))

The revised formula [2] can be simplified to :

=IFERROR(1/(1/SUMPRODUCT(('Kaizen Log'!$B$3:$B$24=B$2)*('Kaizen Log'!$C$3:$H$24=$A3)*ISTEXT('Kaizen Log'!$C$3:$H$24))),"")

Regards
Bosco
 

Attachments

  • KaizenLog(formula way3).xlsx
    18.6 KB · Views: 1
Back
Top