2

I've got two ranges in excel.

sample data

I need to do the following:

1). Count how many equal values I have, apart from zero. In my example it should be 2 (1 and 8). I found this formula: SUMPRODUCT(--(A2:E2=A3:E3)), but it will match only B1, B2, ignoring that number 8 appeared two times as well.

2). Separately, I need to have these repeated values in a single cell, separated with comma, just like "1,8".

ArthurV
  • 113
  • 2
  • 8
  • You should resort to a VBA UDF for the latter part of your question. Formulas simply do not efficiently concatenate strings of unknown length. –  Dec 24 '15 at 14:06
  • What if there were two 1's or 2 eights? What would be delimited count and the delimited string look like? –  Dec 24 '15 at 14:31
  • Jeeped It could be like: "2xn, 8xn", where n is a number of times repeated – ArthurV Dec 24 '15 at 14:37
  • But having 1 two times in both ranges, what should it output? – Dirk Reichel Dec 24 '15 at 17:21

3 Answers3

5

Try this SUM over COUNTIFS array¹ formula,

=SUM(COUNTIFS(A2:E2, "<>"&0, A2:E2, A3:E3))

    sum_over_countifs_array

¹ Array formulas need to be finalized with Ctrl+Shift+Enter↵. Once entered into the first cell correctly, they can be filled or copied down or right just like any other formula. Try and reduce your full-column references to ranges more closely representing the extents of your actual data. Array formulas chew up calculation cycles logarithmically so it is good practise to narrow the referenced ranges to a minimum. See Guidelines and examples of array formulas for more information.

For the latter half of your question, I'll offer this rudimentary UDF which strings together the matched values. As a programming enthusiast you should get great pleasure in modifying the code to include a frequency count.

Function stringMatches(rng1 As Range, rng2 As Range, _
                       Optional sDELIM As String = ", ", _
                       Optional bNOZERO As Boolean = True)
    Dim sTMP As String, rng As Range

    stringMatches = vbNullString
    For Each rng In rng1
        If (CBool(Application.CountIf(rng2, rng.Value)) And Not bNOZERO) Or _
           (CBool(Application.CountIfs(rng2, "<>" & 0, rng2, rng.Value)) And bNOZERO) Then
            sTMP = sTMP & rng.Value & sDELIM
        End If
    Next rng

    If CBool(Len(sTMP)) Then _
        stringMatches = Left(sTMP, Len(sTMP) - Len(sDELIM))
End Function

      stringMatches

  • why the `"<>"&0` and not directly `"<>0"`? – Dirk Reichel Dec 24 '15 at 14:17
  • 1
    That's just the way I write comparative conditions in a COUNTIF/S. It means that it is always written the same when using it like `, "<>"&A1` or `, "<>"&DATE(2015, 12, 25)`. The only time I won't use it like that is comparing text e.g. `, "<>"&"text"` is just awkward. –  Dec 24 '15 at 14:21
  • To my mind, numbers are numbers and text is text and never the twain shall meet. That little quirk is just one of the many ways I satisfy the portion of my tiny brain that thinks along those lines. –  Dec 24 '15 at 14:33
3

simply use this (non array) formula:

=SUMPRODUCT((COUNTIFS(A2:E2,"<>0",A2:E2,A3:E3)>0)*1)

but for the second part, i don't think there is a dynamically way without VBA :/

as UDF i suggest something like this: (for the second part only)

Public Function getDoubles(rng1 As Range, rng2 As Range) As String
  Dim cell As Variant, str As String
    For Each cell In rng1.Value
      If cell <> 0 And Not IsError(Application.Match(cell, rng2, 0)) Then str = str & cell & ", "
    Next
  getDoubles = Left(str, Len(str) - 2)
End Function

But keep in mind: having a value multiple times in one range, the formula/UDF will pretty much likely mess up

To do it in a clean way (skipping all doubles) you can use this:

Public Function getDoubles(rng1 As Range, rng2 As Range, Optional getList As Boolean, Optional compType As VbCompareMethod = vbTextCompare) As Variant

  If rng1.Count = 1 Then
    getDoubles = Not IsError(Application.Match(rng1.Value, rng2, 0))
    Exit Function
  ElseIf rng2.Count = 1 Then
    getDoubles = Not IsError(Application.Match(rng2.Value, rng1, 0))
    Exit Function
  End If

  Dim tempCol As New Collection
  Dim colItem As Variant
  Dim isInCol As Boolean
  Dim rngItem As Variant

  For each rngItem in rng1.Value
    isInCol = False
    If Len(rngItem) > 0 And rngItem <> 0 Then 'remove the "And getOut <> 0" to do it also for 0's
      For Each colItem In tempCol
        isInCol = (StrComp(colItem, rngItem, compType) = 0)
        If isInCol Then Exit For
      Next
      If Not isInCol Then tempCol.Add rngItem
    End If
  Next

  Dim getOut As Variant

  If getList Then
    getOut = ""
  Else
    getOut = 0
  End If

  For Each colItem In tempCol
    For Each rngItem In rng2
      If StrComp(colItem, rngItem, compType) = 0 Then
        If getList Then
          getOut = getOut & colItem & ", "
        Else
          getOut = getOut + 1
        End If
        Exit For
      End If
    Next
  Next

  If getList Then
    getDoubles = Left(getOut, Len(getOut) - 2)
  Else
    getDoubles = getOut
  End If

End Function

If one (or both) range is only one item, it will return true if it is inside the other range, else it will be false.

Having 2 ranges of at least 2 cells it will output as folows without doubles:

=getDoubles(range1,range2)     = the count of matches
=getDoubles(range1,range2,1)   = the "," separated list of matches
=getDoubles(range1,range2,0,0) = like the first but case sensitive
=getDoubles(range1,range2,1,0) = like the second but case sensitive
Dirk Reichel
  • 7,989
  • 1
  • 15
  • 31
  • fwiw, A long-winded formula *could* conceivably concatenate the matches in the OP's sample but it has been my experience that as soon as one is provided the OP will come back with *'... but I need to to go to column ZZ'*. –  Dec 24 '15 at 16:13
  • That's the reason for "no way to do it dynamically without VBA"... this way I simply added a way as UDF to get what he wants... – Dirk Reichel Dec 24 '15 at 16:55
2

Try this simple UDF():

Public Function compare(r1 As Range, r2 As Range) As Long
   Dim r As Range, v As Variant, v2 As Variant
   Dim rr As Range
   For Each r In r1
      v = r.Value
      If v <> 0 And v <> "" Then
         For Each rr In r2
            v2 = rr.Value
            If v = v2 Then compare = compare + 1
         Next rr
      End If
   Next r
End Function

enter image description here

User Defined Functions (UDFs) are very easy to install and use:

  1. ALT-F11 brings up the VBE window
  2. ALT-I ALT-M opens a fresh module
  3. paste the stuff in and close the VBE window

If you save the workbook, the UDF will be saved with it. If you are using a version of Excel later then 2003, you must save the file as .xlsm rather than .xlsx

To remove the UDF:

  1. bring up the VBE window as above
  2. clear the code out
  3. close the VBE window

To use the UDF from Excel:

=myfunction(A1)

To learn more about macros in general, see:

http://www.mvps.org/dmcritchie/excel/getstarted.htm

and

http://msdn.microsoft.com/en-us/library/ee814735(v=office.14).aspx

and for specifics on UDFs, see:

http://www.cpearson.com/excel/WritingFunctionsInVBA.aspx

Macros must be enabled for this to work!

NOTE:

If there are 4 qwerty in the second range, you will get a count for each one. (but a slight mod can avoid this)

This routine will return the CSV:

Public Function compare2(r1 As Range, r2 As Range) As String
   Dim r As Range, v As Variant, v2 As Variant
   Dim rr As Range
   For Each r In r1
      v = r.Value
      If v <> 0 And v <> "" Then
         For Each rr In r2
            v2 = rr.Value
            If v = v2 Then compare2 = compare2 & "," & CStr(v)
         Next rr
      End If
   Next r
   If compare2 <> "" Then compare2 = Mid(compare2, 2)
End Function
Community
  • 1
  • 1
Gary's Student
  • 95,722
  • 10
  • 59
  • 99