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.

ComputerVersteher
  • 2,638
  • 1
  • 10
  • 20
JLuc01
  • 187
  • 1
  • 12

5 Answers5

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.

T.M.
  • 9,436
  • 3
  • 33
  • 57
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
Ron Rosenfeld
  • 53,870
  • 7
  • 28
  • 60
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
chris neilsen
  • 52,446
  • 10
  • 84
  • 123
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))))

jg80
  • 201
  • 2
  • 9
  • 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. – jg80 Aug 22 '20 at 21:43
  • 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. – JLuc01 Aug 23 '20 at 10:04
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
FaneDuru
  • 38,298
  • 4
  • 19
  • 27
  • Work like a charm and exactly what I was looking for. Thank you. – JLuc01 Aug 23 '20 at 09:57
  • 1
    Nice. FYI might be interested in alternative via tricky Byte array as well as FilterXML + Filter function @FaneDuru – T.M. Aug 24 '20 at 12:03
  • @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... – FaneDuru Aug 24 '20 at 12:20
  • 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 – T.M. Aug 24 '20 at 16:27