1

I want to divide numbers into unique sorted digits. For example, the number can be 127425 and I would like 12457 as the result, meaning sorted and duplicate removed. I think the best is to explain with example:

+---------+--------+
| Number  | Result |
+---------+--------+
| 127425  | 12457  |
+---------+--------+
| 2784425 | 24578  |
+---------+--------+
| 121     | 12     |
+---------+--------+
| 22222   | 2      |
+---------+--------+
| 9271    | 1279   |
+---------+--------+

The longest result can be only 123456789.

I don't think we need an array for that (no delimiter), but the use of substring could probably do the job. I just don't know where to begin, hence no code.

Any ideas are welcome. Thanks.

2
  • What about 0 digit? Just forgotten or missing on purpose? Commented Aug 22, 2020 at 23:30
  • No zero, only 9 digits possible or less in the result. The initial number can have 20 digits or more (even if it is unlikely). Commented Aug 23, 2020 at 9:46

5 Answers 5

3

Alternative to the newer dynamic array functions

Loving the above nice solutions it's always a challenge to think over additional approaches (via Byte array, Filter() and FilterXML() function):

Function UniqueDigits(ByVal txt) As String
    Dim by() As Byte: by = txt
    Dim digits: digits = Array(49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 0) ' equalling Asc values
'a) create 1-based 1-dim array with digit positions
    Dim tmp: tmp = Filter(Application.Match(by, digits, 0), 11, False)
'b) get uniques
    tmp = Uniques(tmp)
'c) sort it (don't execute before getting uniques)
    BubbleSort tmp
'd) return function result
    UniqueDigits = Join(tmp, "")
End Function
Function Uniques(arr)
'Note: using FilterXML() available since vers. 2013+
    Dim content As String       ' replacing "10" referring to zero indexed as 10th digit
    content = Replace("<t><s>" & Join(arr, "</s><s>") & "</s></t>", "10", "0")
    arr = WorksheetFunction.FilterXML(content, "//s[not(preceding::*=.)]")
    Uniques = Application.Transpose(arr)
End Function
Sub BubbleSort(arr)
    Dim cnt As Long, nxt As Long, temp
    For cnt = LBound(arr) To UBound(arr) - 1
        For nxt = cnt + 1 To UBound(arr)
            If arr(cnt) > arr(nxt) Then
                temp = arr(cnt)
                arr(cnt) = arr(nxt)
                arr(nxt) = temp
            End If
        Next nxt
    Next cnt
End Sub

Further hints :-) tl;dr

...explaining

a) how to transform a string to a digits array, b) how to get uniques via FilterXML instead of a dictionary c) (executing BubbleSort needs no further explanation).

ad a) the tricky way to get a pure digits array Transforming a string of digits into an array of single characters may need some explanation.

  1. A string (here txt) can assigned easily to a byte array via Dim by() As Byte: by = txt. (Note that classical characters would be represented in a byte array by a pair of Asc values, where the second value mostly is 0; so digit 1 is represented by 49 and 0, 2 by 50 and 0 up to 9 by 57 and 0).

Digits are defined in a 1-based Asc value array from 1~>49 to 9~>57, followed by the 10th item 0~>48 and eventually the Asc value 0 as 11th item related to each second byte pair.

Dim digits: digits = Array(49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 0) ' equalling Asc values
  1. Usually the Match() function searches for a specified item in order to get its relative position within an array (here digits) and would be executed by the following syntax: ``.

MATCH(lookup_value, lookup_array, [match_type]) where the lookup_value argument can be a value (number, text, or logical value) or a cell reference to a number, text, or logical value.

An undocumented feature is that instead searching e.g. 2 in the lookup_array digits via Application.Match(2, digits,0) you can use the byte array as first argument serving as 1-based array pattern where VBA replaces the current Asc values by their position found within the digits array.

Application.Match(by, digits, 0)

Finally a negative filtering removes the companion Asc 0 values (11 plus argument False) via

Dim tmp: tmp = Filter(Application.Match(by, digits, 0), 11, False)

ad b) get uniques via FilterXML

Help reference for the WorksheetFunction.FilterXML method demands two string parameters

FilterXML(XMLContentString, XPathQueryString)

The first argument doesn't reference a file, but needs a valid ("wellformed") XML text string starting with a root node (DocumentElement) which is roughly comparable to a html structure starting with the enclosing pair of <html>...</html> tags.

So a wellformed content string representing e.g. number 121 could be:

<t>
    <s>1</s>
    <s>2</s>
    <s>1</s>
</t>

The second argument (limited to 1024 characters) must be a valid XPath query string like the following find non-duplicates

"//s[not(preceding::*=.)]"

where the double slash // allows to find s nodes at any hierarchy level and under the condition that it is not preceded by any nodes * with the same value content =.

Recommended readings

@RonRosenfeld is a pioneer author of numerous answers covering the FilterXML method, such as e.g. Split string cell....

@JvDV wrote a nearly encyclopaedic overview at Extract substrings from string using FilterXML.

Sign up to request clarification or add additional context in comments.

1 Comment

That sounds also quite interesting. Thanks for sharing.
2

Another VBA routine to sort the unique elements of a cell, using an ArrayList:

Option Explicit
Function sortUniqueCellContents(S As String) As String
    Dim arr As Object, I As Long, ch As String * 1
    
Set arr = CreateObject("System.Collections.ArrayList")

For I = 1 To Len(S)
    ch = Mid(S, I)
    If Not arr.contains(ch) Then arr.Add ch
Next I

arr.Sort
sortUniqueCellContents = Join(arr.toarray, "")

End Function

1 Comment

Good one, but I decided to use the answer from FaneDuru. Thanks.
2

If you have a version of Excel that supports Dynaaic Arrays, then try this (for input in A2)

=CONCAT(SORT(UNIQUE(MID(A2,SEQUENCE(LEN(A2),1,1,1),1))))

How it works

  1. SEQUENCE(LEN(A27),1,1,1) returns an array of numbers 1 .. the length of the input string
  2. MID(A2, ... ,1)) uses that array to return a Spill range of the individual characters in the input string
  3. UNIQUE( reduces that to a range of unique characters only
  4. SORT sorts that range
  5. CONCAT concatenates that range into a single string

Gearing off that to build a VBA function

Function UniqueDigits(s As String) As String
    With Application.WorksheetFunction
        UniqueDigits = Join(.Sort(.Unique(Split(Left$(StrConv(s, 64), Len(s) * 2 - 1), Chr(0)), 1), , , 1), "")
    End With
End Function

1 Comment

Interesting, but not what I was looking for. I will try to remember this anyway. Thanks.
1

Try the next function, please:

Function RemoveDuplSort(x As String) As String
  Dim i As Long, j As Long, arr As Variant, temp As String
  'Dim dict As New Scripting.Dictionary 'in case of reference to 'Microsoft Scripting Runtime,
                                        'un-comment this line and comment the next one:
  Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
  For i = 1 To Len(x)
    dict(Mid(x, i, 1)) = 1
  Next i
  arr = dict.Keys
      For i = LBound(arr) To UBound(arr) - 1
        For j = i + 1 To UBound(arr)
            If arr(i) > arr(j) Then
                temp = arr(i)
                arr(i) = arr(j)
                arr(j) = temp
            End If
        Next j
    Next i
  RemoveDuplSort = Join(arr, "")
End Function

It can be called in this way:

Sub testRemoveDuplSort()
  Dim x As String
  x = "2784425" 'x = myLabel.Caption
  Debug.Print RemoveDuplSort(x)
End Sub

4 Comments

Work like a charm and exactly what I was looking for. Thank you.
Nice. FYI might be interested in alternative via tricky Byte array as well as FilterXML + Filter function @FaneDuru
@T.M. Yes, the byte array (using StrConv(x, vbFromUnicode)) should be a good alternative instead of iteration using Mid, but the iteration is anyhow, necessary for the Dictionary loading. Is it a way to filter it directly? About FilterXML, I could see its implementation mostly in your answers... :) It did not look complicated, but I do not have the habit to use it from reflex. When I will find some time, I promised to myself to play a little with XML approach...
Firstly, I like your answer. ... Secondly, as I'm always trying to explore further approaches enriching my own knowledge, my answer below (workable vers. 2013+) does not only demonstrate how to transform a string to a digits array (originally Byte type), but also to get uniques via FilterXML instead of a dictionary. @FaneDuru
1

If your number is in cell A3, then this one will return a string of unique numbers.
=CONCAT(SORT(UNIQUE(MID(A3,ROW(INDIRECT("1:"&LEN(A3))),1))))

2 Comments

ahh, wait I did read the header that said the number was in a label in a userform, did not check how to access these..maybe you can still figure out some way to use this.
Exact, but I could put the number in a cell, use your formula and get the result back to my form... Not exactly what I wanted. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.