I have a list of cells with values like the below:
a,a,b,c,d
f,g,h,h,h,j
a,b,b
f,f,f,y,y,u,u
I want a formula that will give me the below (unique list of above). I should be able to write it for one row and copy it down.
a,b,c,d
f,g,h,j
a,b
f,y,u
There is no way to do this with a formula that will return comma-separated unique results into one cell, using only the built-in worksheet functions.
But, it is very simple to achieve the same thing with a User Defined Function (UDF).
Just place this small routine in a standard code module:
Public Function UniqueList(s)
Dim i&, k$, v
v = Split(s, ",")
For i = 0 To UBound(v)
If InStr(k, v(i)) = 0 Then k = k & "," & v(i)
Next
UniqueList = Mid$(k, 2)
End Function
If your source string is in cell A1 then in cell B1 enter this formula:
=UniqueList(A1)
That's it. Now copy the formula downward as far as needed.
Considering the repeated letters are in order, as in your sample, this should do the trick:
Function UniqueLetters(ByVal cell As Range) As String
letters = Split(cell.Value, ",")
For Each letter In letters
If letter <> current_letter Then
current_letter = letter
unique_letters = unique_letters + letter + ","
End If
Next
UniqueLetters = Left(unique_letters, Len(unique_letters) - 1)
End Function
Related
As mentioned in the title, I wonder if there is any way to use built-in functions in excel to see whether a cell contains a specific number and count the total numbers in the cell. The cell can contain a list of numbers seperated by comas, for instance, "1,4,7" or ranges "10-25" or a combination of both. See the print screen.
No, there is not, but you could write a VBA function to do that, something like:
Function NumberInValues(number As String, values As String) As Boolean
Dim n As Integer
n = CInt(number)
Dim parts() As String
parts = Split(values, ",")
For i = LBound(parts) To UBound(parts)
parts(i) = Replace(parts(i), " ", "")
Next
Dim p() As String
Dim first As Integer
Dim last As Integer
Dim tmp As Integer
For i = LBound(parts) To UBound(parts)
p = Split(parts(i), "-")
' If there is only one entry, check for equality:
If UBound(p) - LBound(p) = 0 Then
If n = CInt(p(LBound(p))) Then
NumberInValues = True
Exit Function
End If
Else
' Check against the range of values: assumes the entry is first-last, does not
' check for last > first.
first = CInt(p(LBound(p)))
last = CInt(p(UBound(p)))
If n >= first And n <= last Then
NumberInValues = True
Exit Function
End If
End If
Next
NumberInValues = False
End Function
and then your cell C2 would be
=NumberInValues(B2,A2)
Calculating how many numbers there are in the ranges would be more complicated as numbers and ranges could overlap.
The key part of implementing this is to create a List or Array of individual numbers that includes all the Numbers represented in the first column.
Once that is done, it is trivial to check for an included, or do a count.
This VBA routine returns a list of the numbers
Option Explicit
Function createNumberList(s)
Dim AL As Object
Dim v, w, x, y, I As Long
Set AL = CreateObject("System.Collections.ArrayList")
v = Split(s, ",")
For Each w In v
'If you need to avoid duplicate entries in the array
'uncomment the If Not lines below and remove the terminal double-quote
If IsNumeric(w) Then
'If Not AL.contains(w) Then _"
AL.Add CLng(w)
Else
x = Split(w, "-")
For I = x(0) To x(1)
'If Not AL.contains(I) Then _"
AL.Add I
Next I
End If
Next w
createNumberList = AL.toarray
End Function
IF your numeric ranges might be overlapping, you will need to create a Unique array. You can do that by changing the AL.Add function to first check if the number is contained in the list. In the code above, you can see instructions for that modification.
You can then use this UDF in your table:
C2: =OR($B2=createNumberList($A2))
D2: =COUNT(createNumberList($A2))
Here is a possible formula solution using filterxml as suggested in the comment:
=LET(split,FILTERXML("<s><t>+"&SUBSTITUTE(A2,",","</t><t>+")&"</t></s>","//s/t"),
leftn,LEFT(split,FIND("-",split&"-")-1),
rightn,IFERROR(RIGHT(split,LEN(split)-FIND("-",split)),leftn),
SUM(rightn-leftn+1))
The columns from F onwards show the steps for the string in A2. I had to put plus signs in because Excel converted a substring like "10-15" etc. into a date as usual.
Then to find if a number (in C2 say) is present:
=LET(split,FILTERXML("<s><t>+"&SUBSTITUTE(A2,",","</t><t>+")&"</t></s>","//s/t"),
leftn,LEFT(split,FIND("-",split&"-")-1),
rightn,IFERROR(RIGHT(split,LEN(split)-FIND("-",split)),leftn),
SUM((--leftn<=C2)*(--rightn>=C2))>0)
As noted by #Ron Rosenfeld, it's possible that there may be duplication within the list: the Count formula would be susceptible to double counting in this case, but the Check (to see if a number was in the list) would give the correct result. So the assumptions are:
(1) No duplication (I think it would be fairly straightforward to check for duplication, but less easy to correct it)
(2) No range in wrong order like 15-10 (although this could easily be fixed by putting ABS around the subtraction in the first formula).
Here is a little cheeky piece of code for a VBA solution:
Function pageCount(s As String)
s = Replace(s, ",", ",A")
s = Replace(s, "-", ":A")
s = "A" & s
' s now looks like a list of ranges e.g. "1,2-3" would give "A1,A2:A3"
pageCount = Union(Range(s), Range(s)).Count
End Function
because after all the ranges in the question behave exactly like Excel ranges don't they?
and for inclusion (of a single page)
Function includes(s As String, m As String) As Boolean
Dim isect As Range
s = Replace(s, ",", ",A")
s = Replace(s, "-", ":A")
s = "A" & s
Set isect = Application.Intersect(Range(s), Range("A" & m))
includes = Not (isect Is Nothing)
End Function
I have a scenario where I have to read through values in one cell which is comma separated and retrieve only values from that array to match with a particular lookup value. For eg:
So what I need is a function to retrieve all Task(or any other issuetype which could vary) from row 2 Links column
expected result: Against A2 I want to retrieve A4 and A6
This is something I modified so that you could customize it to any lookup value
Function GetLinkedItem(link As String, targetLinkType As String)
Dim temp(0 To 0) As String
GetLinkedItem = "None"
If Trim(link) = "" Then Exit Function
Dim links() As String, i As Long
links = Split(link, ",")
For i = 0 To UBound(links)
'select the links that are targetLinkType
Dim j As Long
j = GetRow(Trim(links(i)))
If Sheets("Data").Cells(j, ISUUETYPE_COL) = targetLinkType Then
temp(0) = temp(0) & " " & Sheets("Data").Cells(j, ID_COL) & ","
End If
GetLinkedItem = Join(temp, ",")
Next i
End Function
You can create a UDF to perform this lookup. In a new module in your VBE, paste the following:
Function getTasks(tasklist As Range, availabletasks As Range) As String
'tasklist is the incoming array in Column C
'availabletasks is the stuff in Column A
'Array for output
Dim tasks() As String: ReDim tasks(0 To 0)
'Loop through each item in taslist using an array
For Each task In Split(tasklist.Value, ", ")
'Search availabletasks
If Not availabletasks.Find(task) Is Nothing Then
'pop the array
If tasks(0) <> "" Then ReDim Preserve tasks(0 To UBound(tasks) + 1)
tasks(UBound(tasks)) = task
End If
Next
'Return what we found
getTasks = Join(tasks, ", ")
End Function
Now in your spreadsheet you can use this function just like a regular formula:
=getTasks(C1,$A$1:$A$6)
Where C1 has the list like A4, A25, A22, A6, A29, A42 and $A$1:$A$6 are just like your example Column A. This will return A4, A6
Thanks so much. I added the code in a new module and used the function as formula. I was getting just 1 value instead of 2(Just got A4 and not A6).
I'm trying to get all the content between multiple parenthesis and comma delimiting them. So for example
A1 contains
thisfile.jpg (/path/to/file.jpg), thisfile2.jpg (/path/to/file2.jpg)
and B1 should look like
/path/to/file.jpg, /path/to/file2.jpg
If it's just one entry I can get what I need with this:
MID(A1,FIND("(",A1)+1,FIND(")",A1)-FIND("(",A1)-1)
But that only returns the first one, I need to be for each parenthesis. The amount of parenthesis in each row will vary.
I am sure there are better solutions out there with formulas only. Yet, I cannot help you there. But the following UDF is surely also a feasible solution. Just copy this code into an empty module:
Option Explicit
Public Function GetPaths(strTMP As String)
Dim i As Long
Dim varArray As Variant
varArray = Split(strTMP, "(")
For i = LBound(varArray) To UBound(varArray)
If InStr(1, varArray(i), ")") > 0 Then
GetPaths = GetPaths & ", " & Mid(varArray(i), 1, InStr(1, varArray(i), ")") - 1)
End If
Next i
GetPaths = Mid(GetPaths, 3)
End Function
Afterwards, you can use this formula in column B as follows: =GetPaths(A1).
I have a value in Column A, which I wnat to compare with multiple values of corresponding cell column B and put the answer in column C. Say it searching in column B for values which are less than or equal to 12 and put the answer in same order in column C.
Column A #### Column B #### Column C
12 #### 10,12,13,14 #### Yes, Yes, No, No
101 #### 101,102,103,104 #### Yes, No, No, No
Assuming, as you described in your problem, that the cells in Column B and C are strings, separated by commas, then the easiest way is to do this using a user-defined function (called StringComp) in VBA.
The function takes two arguments, one for the list of comma separated numbers in Column B, and the second points to the cell with the number you wish to use as a comparison to your list, Column A.
I think this can be done using array functions, but the VBA solution is much simpler.
After first copying and pasting the below VBA into your workbook VBA Module, for your example above, assuming your data starts from Row 1, you would use the StringComp function by putting =StringComp(B1,A1) in cell C1, and this will return Yes,Yes,No,No, as per your example.
Public Function StringComp(a As Variant, b As Variant) As String
Dim strOUT, strOUT_temp, strArray() As String
Dim intCount As Integer
strArray = Split(a.Text, ",")
For intCount = LBound(strArray) To UBound(strArray)
If (Val(strArray(intCount)) <= Val(b)) Then
strOUT_temp = "Yes"
Else
strOUT_temp = "No"
End If
If (intCount = LBound(strArray)) Then
strOUT = strOUT_temp
Else
strOUT = strOUT & "," & strOUT_temp
End If
Next
StringComp = strOUT
End Function
Let's say I have a series of cells like so:
A
1 Foo
2 Bar
3 Hello
4 World
5 Random Text
What I'd like to do is have the result of my formula populate another cell with:
Foo, Bar, Hello, World, Random Text
Now, I know how to concatenate two cells with:
=A1&", "&A2
but how can I do the same thing with the entire series?
Here's a function you might be able to use. Simply put this in your workbook code module, then you can enter it in cells like:
=JoinRange(A1:A6) or =JoinRange(A2:D15), etc.
Public Function JoinRange(ByVal rng As Range) As String
Dim dlmt As String: dlmt = ","
Dim multiRow As Boolean: multiRow = rng.Rows.Count > 1
Dim r As Long, c As Long
Select Case rng.Columns.Count
Case 1
If multiRow Then
JoinRange = Join(Application.WorksheetFunction.Transpose(rng), dlmt)
Else:
'a single cell
JoinRange = rng
End If
Case Is > 1
If multiRow Then
'a 2d range of cells:
For r = 1 To rng.Rows.Count
For c = 1 To rng.Columns.Count
JoinRange = JoinRange & rng(r, c) & dlmt
Next
Next
JoinRange = Left(JoinRange, Len(JoinRange) - 1)
Else:
JoinRange = Join(Application.WorksheetFunction.Transpose( _
Application.WorksheetFunction.Transpose(rng)), dlmt)
End If
Case Else
End Select
End Function
Put a comma and a space in cell B1, then use this formula:
=CONCATENATE(A1,B1,A2,B1,A3,B1,A4, B1, A5)
There are several answers to the following question that you can try as well, including VBA options and a formula:
Need to concatenate varying number of cells...
With =A1 in B1 then =B1&", "&A2 in B2 and copied down would seem to work.