VBA Range.Sort with arrays. Alternative for simple use.
VBA Range.Sort with arrays. Alternative for simple use
.
I am considering a fairly simple VBA Array alternative approach to a simple use of the available VBA Range.Sort Method. https://docs.microsoft.com/en-us/off...cel.range.sort
In an important file of mine, I currently use the available VBA Range.Sort Method. https://docs.microsoft.com/en-us/off...cel.range.sort
It lets me sort some long rows of data where the order is based on the values in one of the columns , then for any rows which have the same value in that column , those similar rows would be further sorted by another column. For the VBA Range.Sort Method the syntax terminology would say that I am using pseudo code of this sort of form
MyRange.Sort Key1:=use column1 , __ , Key2:=use columnx , __ , __ , __ ,
You can use up to 3 "Key 's" in the simple single code line.
In other words, if you have some identical values in the columns used to define the final list order, you can sort further using the values in a second column to determine the order in the group of identical values in the first column which you used. In the simple single line use of the available VBA Range.Sort Method, you can do that one more time, so 3 times in total .##
In other words, in that single code line it does , pseudo:
Sort by a column, x: (Key1=x)
Sort by a second column, y (Key2:=y), for those rows with duplicates in it in column x
Sort a final time by a third column, z (Key3:=z), for those rows with duplicates in it in both column x and column y
Here is a simple graphical illustration of what I am talking about: Consider this spreadsheet range:
Before:-
_____[/color ( Using Excel 2007 32 bit )
Row\Col |
G |
H |
I |
J |
K |
13 |
Was G13 |
C |
Was I13 |
Was J13 |
Was K13 |
14 |
Was G14 |
a |
Was I14 |
Was J14 |
Was K14 |
15 |
Was G15 |
g |
Was I15 |
c |
e |
16 |
Was G16 |
g |
Was I16 |
b |
Was K16 |
17 |
Was G17 |
g |
Was I17 |
c |
f |
18 |
Was G18 |
G |
Was I18 |
c |
Was K18 |
19 |
Was G19 |
f |
Was I19 |
Was J19 |
Was K19 |
Worksheet: Sorting
I will run this simple procedure, ( which is in a normal code module. When I run the procedure the worksheet with Name "Sorting" is active).
Code:
Sub RangeSortExample()
Range("G13:K19").Sort Key1:=Range("G13:K19").Columns("B:B"), Order1:=xlAscending, Key2:=Range("G13:K19").Columns("D:D"), order2:=xlAscending, Key3:=Range("G13:K19").Columns("E:E"), order3:=xlDescending, MatchCase:=False
End Sub
Here is a break down of what that routine does:
The first key ,
Key1:=range("G13:K19").Columns("B:B"), Order1:=xlAscending , MatchCase:=False
, results in this
_____ ( Using Excel 2007 32 bit )
Row\Col |
G |
H |
I |
J |
K |
13 |
Was G14 |
a |
Was I14 |
Was J14 |
Was K14 |
14 |
Was G13 |
C |
Was I13 |
Was J13 |
Was K13 |
15 |
Was G19 |
f |
Was I19 |
Was J19 |
Was K19 |
16 |
Was G15 |
g |
Was I15 |
c |
e |
17 |
Was G16 |
g |
Was I16 |
b |
Was K16 |
18 |
Was G17 |
g |
Was I17 |
c |
f |
19 |
Was G18 |
G |
Was I18 |
c |
Was K18 |
Worksheet: Sorting
The above screen shows that we have all ordered ( based on the column 2 ,( "B" columns ), of that range , ( which is the column H of the spreadsheet) ). But note, we have duplicates in column 2 with values of g in the last 4 rows.
This next part ,
Key2:=range("G13:K19").Columns("D:D"), order2:=xlAscending, MatchCase:=False
, then sorts those last 4 rows using column 4, (columns "D") of that range values. ( This is the spreadsheet columns "J")
_____ ( Using Excel 2007 32 bit )
Row\Col |
G |
H |
I |
J |
K |
13 |
Was G14 |
a |
Was I14 |
Was J14 |
Was K14 |
14 |
Was G13 |
C |
Was I13 |
Was J13 |
Was K13 |
15 |
Was G19 |
f |
Was I19 |
Was J19 |
Was K19 |
16 |
Was G16 |
g |
Was I16 |
b |
Was K16 |
17 |
Was G15 |
g |
Was I15 |
c |
e |
18 |
Was G17 |
g |
Was I17 |
c |
f |
19 |
Was G18 |
G |
Was I18 |
c |
Was K18 |
Worksheet: Sorting
In the above screen shot we see that we now have 3 rows containing all gs in the second column, and all cs in the forth column.
The final term is, noting that we are using xlDescending (just for fun :) ) ,
Key3:=range("G13:K19").Columns("E:E"), order3:=xlDescending
So the last 3 rows are resorted such as to give in those last 3 rows a descending order in the values in the 5th column in those last 3 rows:
_____ ( Using Excel 2007 32 bit )
Row\Col |
G |
H |
I |
J |
K |
13 |
Was G14 |
a |
Was I14 |
Was J14 |
Was K14 |
14 |
Was G13 |
C |
Was I13 |
Was J13 |
Was K13 |
15 |
Was G19 |
f |
Was I19 |
Was J19 |
Was K19 |
16 |
Was G16 |
g |
Was I16 |
b |
Was K16 |
17 |
Was G18 |
G |
Was I18 |
c |
Was K18 |
18 |
Was G17 |
g |
Was I17 |
c |
f |
19 |
Was G15 |
g |
Was I15 |
c |
e |
Worksheet: Sorting
Notes:
For simplicity I chose to be case insensitive ( so like g = G __ - _ MatchCase:=False )
Some other argument options are available with the available VBA Range.Sort Method. I will not consider those.
## Furthermore, If you add the options on other lines then use a .Apply code line to apply the sort, then you can have more than 3 "Keys"
I am only considering a comparison to the simple single line using minimal options similar to the worked example
Why do an array alternative?.
I cant think of a particularly good reason: The VBA Range.Sort Method appears to be regarded as a good way to do these things, and often a solution for sorting arrays is based on pasting into a worksheet Range , then using the Range.Sort on that and then capturing the sorted range back into the original range
But I was just interested, out of general interest, to have an alternative: it often occurs that you find bugs in Excel things. The more you have control of "what is going on" , as you do with an array approach, the less is "hidden" from you, such that you have a better chance to change something id something goes wrong.
Various sorting methods, mathematical ways to sort..
The various methods of sorting seem immense and need a good understanding of advanced mathematics. I can't begin to discuss any of that. I will use exclusively the simply "Bubble sort method" , which I will explain from the simplest form, and then adapt to our case
The next posts will go through the simplest Bubble sort theory as applied to a simple 2 dimensional array, and then progress in the following posts to a Function to do similar to the simple case of a VBA Range.Sort Method with the possibility to add keys, for sorting further when values in the initial column used for sorting are identical
For ease of explanation I will refer to the first dimensions in an array as the rows, and the second dimensions as columns , pseudo such that an array could be thought of as a spreadsheet of values. For example a 2 dimensional array of 5 x 2 could be pictorially considered as 5 rows x 2 columns:
r1, c1 |
r1, c2 |
r2, c1 |
r2, c2 |
r3, c1 |
r3, c2 |
r4, c1 |
r4, c2 |
r5, c1 |
r5, c2 |
Minor Modifications advancements to simple Bubble Sort Routine
Minor Modifications advancements to simple Bubble Sort Routine
Change Ascending/Descending
For the simple example so far we chose Ascending order, which means that the things "get bigger as you go down the rows"
To reverse this to descending so that things "get smaller as you go down the rows", you simply need to change
the > to a < in the array routine
and
the Order1:=xlAscending to Order1:=xlDescending in the VBA Range.Sort routine
http://www.excelfox.com/forum/showth...0979#post10979
I intended developing the solution into a function, so as a start to this, the routine will be modified to take an Optional argument of 0 or 1 , with the default of 0 being the case for an Ascending list. I am not being particularly efficient with the coding, and will duplicate sections thus
Code:
Rem 1 Simple Bubble Sort
Dim rOuter As Long ' ========"Left Hand"=====================Outer Loop=====================================
For rOuter = 1 To UBound(arrTS(), 1) - 1 ' For row 1 to the (last row -1) last row, given by the first dimension upper limit of the array
Dim rInner As Long ' -------Inner Loop-------------"Right Hand"--------------------------
For rInner = rOuter + 1 To UBound(arrOut(), 1)
'If arrOut(rOuter, Clm) > arrOut(rInner, Clm) Then ' This means that I am bigger than the next. So I will swap . I keep doing this which will have the effect of putting the smallest in the current rOuter. By the this and all next rOuter, I miss out the last, and any previous, which means I effectively do the same which puts the next smallest in this next rOuter.
If GlLl = 0 Then ' We want Ascending list
'If UCase(CStr(arrOut(rOuter, Clm))) > UCase(CStr(arrOut(rInner, Clm))) Then
If UCase(CStr(arrOut(rOuter, Clm))) > UCase(CStr(arrOut(rInner, Clm))) Then
Dim temp As Variant ' I want to Swap those 2 above - I cant easilly in any coding change two values simulataneosly. So one of them Element values will put in this temporary place. This Element Values will then be given the other. Finally the other Element will be given this temporary value
Dim Clms As Long '-------| with the condition met a loop is done for all columns in the array in which those two values used in the comparison are replaced at each column
For Clms = 1 To UBound(arrOut(), 2)
Let temp = arrOut(rOuter, Clms): Let arrOut(rOuter, Clms) = arrOut(rInner, Clms): Let arrOut(rInner, Clms) = temp
Next Clms '----------| for each column in the array at the two rows rOuter and rInner
Else
End If
Else ' GlLl is not 0 , so presumably we want Descending list
If UCase(CStr(arrOut(rOuter, Clm))) < UCase(CStr(arrOut(rInner, Clm))) Then
'Dim temp As Variant ' I want to Swap those 2 above - I cant easilly in any coding change two values simulataneosly. So one of them Element values will put in this temporary place. This Element Values will then be given the other. Finally the other Element will be given this temporary value
'Dim Clms As Long '-------| with the condition met a loop is done for all columns in the array in which those two values used in the comparison are replaced at each column
For Clms = 1 To UBound(arrOut(), 2)
Let temp = arrOut(rOuter, Clms): Let arrOut(rOuter, Clms) = arrOut(rInner, Clms): Let arrOut(rInner, Clms) = temp
Next Clms '----------| for each column in the array at the two rows rOuter and rInner
Else
End If
End If ' End of Ascending or Descending example
Next rInner ' ---------------------------------------------------------------------
Next rOuter ' ===========================================================================================
Rem 2 Output for easy of demo
Here is a complete routine with calling procedure: http://www.excelfox.com/forum/showth...0981#post10981
Text or Numbers
It appears from the example tried, that the VBA Range.Sort routine recognizes numbers as numbers and then sorts as we might expect numbers to be sorted.
We can modify slightly our array routine to give similar results.
This is done by doing a comparison based on looking at numbers if at any time the two values under consideration can be seen by VBA as numbers.
The IsNumeric() function is used. If both values are seen to be numeric then they are compared as numbers. This would probably happen from a simple comparison of the values, but just to be on the safe side, we do an extra convert to Double via CDbl( ) in this sort of form
___ If IsNumeric(arrOut(rOuter, Clm)) And IsNumeric(arrOut(rInner, Clm)) Then ' Numeric case
_____ If CDbl(arrOut(rOuter, Clm)) > CDbl(arrOut(rInner, Clm)) Then
Once again, I am not coding particularly efficiently , and so I duplicate sections
Here is a complete routine with calling procedure: http://www.excelfox.com/forum/showth...0982#post10982
Here are some example results: http://www.excelfox.com/forum/showth...0983#post10983
The next post will make a few minor simplifications to develop further a general purpose Function.
Conversion to Array sort function: General considerations
Conversion to Array sort function
From now on, I am making up a lot of this as I go along, so I doubt it will bee the most efficient.
General considerations.
_ Always reference the same array
My long term plans on the final version of this is that the routine will work in some recursion process, that is to say , copies of the function will be going further and further to sort further for rows where the last sort left some duplicated values in the column used to sort by. Those copies of the Function would be called from the function itself. That is the simple explanation of hat recursion is about , as applied to VBA Functions: the function starts, then it pauses, whilst it set off another run of the Function to go off and do something similar to what the first function was doing.
One thing that is always a bit awkward to do is pass the array being sorted through the different copies of the function. So what is normally done, and what we will do is have an array outside the function, ( either in an initial calling routine or at the top of the code module )
In the signature line , ( declaring first line) , of the Function it will be defined as being taken ByRef This means that within the Function we refer specifically to that array. This means that changes made in that Function will be reflected directly by those changes into the array we have outside: To a first approximation we can think of it as thought we physically take the array into the function and do stuff to that array, then put it back outside when we have finished. ( The alternative way that we will not use would be to take the array into the function , ByVal , as its name suggests, takes the values of the array at the time into a copy variable , ( sometimes referred to as a local copy variable ), and all is done inside the function on that copy variable , which ceases to exist when the function Ends. The values inside the original array outside are not effected in the ByVal case.
Simple pseudo code examples of difference between ByRef and ByVal:
Pseudo code ByRef ' ( Usually default option )
varMyArray = x
_ Call ReferToIt(varMyArray)
Sub ReferToIt(ByRef arr)
_ arr=y ' This is similar to saying varMyArray = y
End
varMyArray is now = y ' because effectively varMyArray was in arr
Pseudo code ByVal
varMyArray = x
_ Call TakeValue(varMyArray)
Sub TakeValue(ByVal arr)
_ arr=z
End
varMyArray is still = x ' effectively just a copy of varMyArray was in arr , and this copy was thrown away at the End of the routine. So nothing done to arr could have any effect on varMyArray in the ByVal case
Sort only consecutive rows
I am anticipating wanting to sort a number of consecutive rows in the array. On the first run this will be all rows, like for a 5 row array _ 1 2 3 4 5 if the
If the sort based on the values in an initial column found that the third and forth row had a similar value in that column, then a sort based on another column would need in comparison 3 4.
So I am suggesting a strategy could be to have the function do the sort routine based on consecutive rows given in some way, such as in a text string.
The next posts will look at implementations of the general ideas above
Transformation to a Function or Pseudo Function Sub routine
Transformation to a Function or Pseudo Function Sub routine
The transformation to a Function we can do easily in two simple steps.
Step 1 The routine is changed so that it takes the array to be sorted into it
The calling routine and main routine here http://www.excelfox.com/forum/showth...0987#post10987 are almost the same as the previous. The only difference is that the array to be sorted, is now outside the main routine and is taken into the main routine at the main routine signature line
Sub SimpleArraySort4(ByRef arrTS() As Variant, Optional ByVal GlLl As Long)
( A few unused 'commented out lines have also been removed )
Because we are using ByRef , the previous testieing Calling routine can also use the original supplied array, arrTS() , after the main procedure Call , provided that the array taken in at the signature line is that sorted. If this is the case, then , in effect , the array taken in and the array in the routine are the same array, and changes done to the array in the function will be reflected in the array outside.
We discussed this previously. Just to recap:
Pseudo code ByRef ' ( Usually default option )
varMyArray = x
_ Call ReferToIt(varMyArray)
Sub ReferToIt(ByRef arr)
_ arr=y ' This is similar to saying varMyArray = y
End
varMyArray is now = y ' because effectively varMyArray was in arr
Often in learn material the basic difference between a simple Function procedure and a Sub routine procedure is said to be that a Function returns a value. In fact , as we see from above, the use of ByRef actually allows us effectively to return a value from a simple Sub procedure.
In the version Sub TestieSimpleArraySort4b() and Sub SimpleArraySort4b(__) here , http://www.excelfox.com/forum/showth...0988#post10988 we are using the effect of ByRef to give us a pseudo Function : To achieve this pseudo Function status, all the array references within the routine are changed to that variable taken into the routine at the signature line.
( This variable name can be anything, I will use arsRef() from now on for all references in the main routine). Doing this allows us to remove all the demo range code lines from the procedure: They can be placed outside and then we can use the array which we declare initially outside the routine, ( arrTS() ) , both before and after the procedure Call: Before it is used to feed the data to be sorted, and After the Call it had been filled with the sorted results , so can be used to paste out the sorted results alongside the original range to compare for demo purposes. To all intents and purposes, arrTS() can be considered as arsRef() and visa versa. This characteristic has been achieved by the use ByRef: Any use of arsRef() is simultaneously Referring to
Step 2 Function instead of Sub ???
I am doing this for completeness. But because I am doing things ByRef I don't need to do this. I may not use it finally
Often in learn material the basic difference between a simple Function procedure and a Sub routine procedure is said to be that a Function returns a value.
This can be explained as follows.
The signature line differs from a normal Sub routine in that it has the form similar to a Dim declaration code line. Consider such a simple Dim line:
_ Dim __ MyThing __ As Variant
A signature line for a Function could look like
Function MyThing(Arg) _ As Variant
In both cases you effectively have a variable of the specified type. The way you fill the variable is slightly different. In the simple case you fill it with something
__ MyThing = "42"
For the function you need to Call it to fill it, and unless the Argument is optional, then you must give it
_ MyThing_ "42"
or
_ Call MyThing("42")
Because the function usually ( you can decide, it does not have to !!!!! ) returns a value in a variable of the type you specify , the most typical use would look like
MyOtherThing = MyThing("42")
The returned value is then put in the variable MyOtherThing
At the end of the day, the main reason for using a function is given as that of returning a value of a specific type.
( By the way, once you have such coding written in a File , then in addition to using it in VBA,,then you could sometimes use it in a cell: Writing = MyThing("42") would often work to return you a value in the cell , resulting from what your function MyThing did with "42" )
If we declared our function as type Variant at the signature line, then that would allow us to return our sorted array, since a Variant variable can hold an array.
The reason why I may not finally use this is that by virtue of using ByRef, my Sub routine will also effectively return that array.
Within the Function itself, the Function MyThing, is like an unfilled variable , in this case a Variant variable. As it is unfilled it will not return anything. !!!!! So within the function, typically towards the end once we have finished doing all we wanted to do, we would have a code line like
Function MyThing(Byref ArrToSort() , ByVal aArg, Optional ByVal GlLl As Long) As Variant
' arrOut() = ArrToSort()
' Do stuff like sort the array
_ MyThing = arrOut()
End Function
The function is then "filled" with what it would return when called
Transformation of pseudo Function Sub SimpleArraySort4b(ByRef arsRef() As Variant, Optional ByVal GlLl As Long) to Function SimpleArraySort5(ByRef arsRef() As Variant, Optional ByVal GlLl As Long) As Variant
Let us note once more that this final step is not necessary: The last procedure fulfils all are current requirements of taking an array and sorting that array, and effectively giving it back to us. It does this by virtue of the array being taken ByRef
The true Function way is more conventionally used so I am doing it just for convenience
The convention way to use the Function would be to arrange that our sorted array is returned by the function. As our current routine stands we only need to add one code line to do this. This code line, placed towards the end of the Function would be
__SimpleArraySort5 = arsRef()
To explain:
The signature line .._
Function SimpleArraySort5(_____) As Variant
_.. Has a very similar effect to this line:
_ Dim _ SimpleArraySort5 _ As Variant
So SimpleArraySort5 could be thought of , to a first approximation , as a pseudo variable
The only difference is that when VBA ( or Excel following a = , like in = SimpleArraySort5 , in a cell ) "sees" SimpleArraySort5 , it will go off and do whatever is inside the function. At the End of the function, the value inside the pseudo variable will be empty still. If we do nothing else then the Function can be Called in exactly the same way that a normal sub routine can
_ Call SimpleArraySort5(___)
However, filling the pseudo variable , for example via code line SimpleArraySort5 = arsRef() , allows us the extra possibility of a code lines like
_____ = = SimpleArraySort5(____)
_ MySortedArray= SimpleArraySort5(MyUnsortedArray)
The type chosen in the declaring signature line must be compatible with what you are doing. In our case, we want to return an array. The only variable type that can hold/ pass an array is a Variant. Hence we chose Variant in this case
( because VBA works generally backwards when reading a code line, we could, in our case , use the same variable such
_ MyUnsortedSortedArray= SimpleArraySort5( MyUnsortedSortedArray )
_ arrTS() = SimpleArraySort5( arrTS() ) )
In the routines
Sub TestieSimpleArraySort5() and Function SimpleArraySort5(______) As Variant
http://www.excelfox.com/forum/showth...0989#post10989 , the main difference over the previous routines is the extra As Variant at the signature line, and finally a code line just before End Function of SimpleArraySort5 = arsRef()
In the testieing routine, we use codes line of this form in the conventional way in which a function is typically used.
_ arrTS() = SimpleArraySort5(arrTS(), _ 0 _ )
But we note that by virtue of using ByRef a simple call would surfice
_Call SimpleArraySort5(arrTS(), _ 0 _ )
Note: we have added an extra testing code section '2b)
In this extra section we fill a new array, arrDesc() , with the sorted array in Descending order. We use for demo purposes a typical function using code line
_ arrDesc() = SimpleArraySort5(arrTS(), 2136)
Correspondingly we have a demo output giving code line
_ RngToSort.Offset(0, RngToSort.Columns.Count * 3).Value = arrDesc()
We note further, that this is somewhat redundant. This is because the code part SimpleArraySort5(arrTS(), 2136) has the effect of re filling arrTS() with the newly sorted array by virtue of the use of ByRef in the signature line of the Function
We could therefore simply use a code line like _..
_Call SimpleArraySort5(arrTS(), 357)
_.. followed by an demo output giving line of
_ RngToSort.Offset(0, RngToSort.Columns.Count * 3).Value = arrTS()
We mow have a simple Array Bubble sort function
The next posts will develop ideas to allow extending the function to allow for sorting further using values from other columns for rows where duplicate values were in the column used initially
I will likely start from the "pseudo" Function
Sub SimpleArraySort4b(ByRef arsRef() ______)
Example: A few thousand columns with a few dozen rows
Example: A few thousand columns with a few dozen rows
A requirement I have is to re sort occasionally a sub set of rows in a large list of very wide rows ( approx 3500 columns ). Typically I have sections of 50 rows which “belong together”, ( 49grouped + 1 below ) , and things may be added. It is easier to add anywhere in the section and then do a sort on the entire group, or part thereof , as required..
49grouped+1.JPG : https://imgur.com/a8rflEn
The Range.Sort method works well, so the array version is just a spare alternative…
Range.Sort method coding.
A full description is given here:
http://www.excelfox.com/forum/showth...ll=1#post11032
http://www.excelfox.com/forum/showth...ll=1#post11033
http://www.excelfox.com/forum/showth...ll=1#post11034
http://www.excelfox.com/forum/showth...ll=1#post11035
Here is just a brief Description of the Range.Sort method coding:
The range to sort, rngToSort , is determined. It is basically taken as the user selected range on the active worksheet in the active window. Because there are a lot of columns, and the number is known ( 1 – 3488 ) , these are hardcode, and the user only needs to make a selection of any columns over the required rows: The selection determines the row range and the column range is hard coded.
An array, ArrrngOrig() , is made of the current range values.
This can be used if it is decided not to accept the sorted range and instead to replace the original range order.
In the Array version alternative this will be the array to be given to the sort routine.
The main code line doing the Range.Sort is
Code:
rngToSort.Sort Key1:=wksToSort.Columns("H"), order1:=xlDescending, Key2:=wksToSort.Columns("J"), order2:=xlDescending, Key3:=wksToSort.Columns("X"), order3:=xlDescending
The relevant arguments from there that we need to feed to our array sort routine are therefore
Key1:=wksToSort.Columns("H"), order1:=xlDescending
Key2:=wksToSort.Columns("J"), order2:=xlDescending
Key3:=wksToSort.Columns("X"), order3:=xlDescending
The array to be given can be assigned directly to
ArrrngOrig()
Array sort routine alternative coding( revision of our current coding )
Here is our Main recursion Array sort routine http://www.excelfox.com/forum/showth...0994#post10994
Sub SimpleArraySort6(ByVal CpyNo As Long, ByRef arsRef() As Variant, ByVal strRws As String, ByVal strKeys As String)
Because this is a recursion routine, it need an initial routine to Call it the first time and feed it the appropriate arguments.
It will need to supply :
CpyNo : This is is very useful for debugging to help tell us which copy of the routine is running at any time. We have seen that this is incremented every time a routine pauses and sets off another copy. The increment is done as the new copy is started, and that incremented value passed to the newly starting routine, so CpyNo will be an indication of the copy running. So this we will need to set to one at the initial Call done by the initial Calling routine to 1
Also it is needed to pick out the correct sort key, in other words to set the copy of the routine to sort based on the correct column: Each further copy of the recursion routine uses the next column of values to be used in the progressive sorting.
arsRef() : We can declare a dynamic Variant array and assign our range capture array , ArrrngOrig() , directly to this. Lets call this arrTS() for consistence to our previous discussions
strRws : This is used to give the indices of the array, ( in a string form ) of all rows currently being sorted. On the initial Call this will need to have all the rows and be of this form “ 1 2 3 4 5 6 ……….. N “. We will need to make this in the Calling routine
strKeys : This takes a form to look similar to the arguments of the Range.Sort method. Because we are looking from column A , our columns follow the spreadsheet columns. All our order is Descending. This would be our basic form required, which will be passed unchanged between copies of the recursion routine. ( We pass this ByValue , but we could just as well uses ByRef for this variable )
In our initial Calling routine we will need a code line of this form
strKeys = “ 8 Desc 10 Desc 24 Desc “
Calling initial routine
The routine is here http://www.excelfox.com/forum/showth...ll=1#post11038
The first 3 section, Rem 0 Rem 1 Rem 2 , and final section Rem 4 are not directly relevant to the sort routine, and are very similar to the same code sections in the Range.Sort routine described here: http://www.excelfox.com/forum/showth...ll=1#post11035
Rem 3
'3a) arguments for Called routine
A simple loop produces our string of all row indices, strIndcs
An array is made containing the initial unsorted range to be passed to the recursion routine, arrTS()
The remaining arguments of recursion routine copy number, CpyNo , abd the keys required, strKeys , are passed hard coded directly in the next section, '3b)
'3b) ( '3c) alternative**) This is the main equivalent to the Range.Sort method
The main recursion routine is Called and after the range is given the array of sorted values
**‘3c) is an alternative which comes closer to resembling the single line Range.Sort method , for comparison:
Code:
rngToSort.Sort Key1:=wksToSort.Columns("H"), order1:=xlDescending, Key2:=wksToSort.Columns("J"), order2:=xlDescending, Key3:=wksToSort.Columns("X"), order3:=xlDescending
'3c)
arrTS() = rngToSort.Value: Call SimpleArraySort6(1, arrTS(), strIndcs, " 8 Desc 10 Desc 24 Desc "): rngToSort.Value = arrTS()
here again, pseudo form, for ease of comparison:
rngToSort.Sort
___________Key1:=wksToSort.Columns("H"), order1:=xlDescending,
_________________Key2:=wksToSort.Columns("J"), order2:=xlDescending,
_______________________Key3:=wksToSort.Columns("X"), order3:=xlDescending
‘ 3c)
arrTS() = rngToSort.Value
Call SimpleArraySort6(1, arrTS(), strIndcs,
__________________________" 8 Desc 10 Desc 24 Desc ")
rngToSort.Value = arrTS()
( For a spreadsheet, for the letter across the top,
_____________H is column number 8 , J is column number 10 , X is column number 24 )
Here is the file used do far:
"ProAktuellex8600x2Sort1.xlsm" https://app.box.com/s/d6isabudadt3swnryxiz7motspzeqa17
Here are some timing experimants:
http://www.excelfox.com/forum/showth...ll=1#post11041
http://www.excelfox.com/forum/showth...ll=1#post11042
http://www.excelfox.com/forum/showth...ll=1#post11043
http://www.excelfox.com/forum/showth...ll=1#post11045
http://www.excelfox.com/forum/showth...1046#post11046
Alternative Version. Sort row indicies, then use arrOut()=Indx(ArrOrig(). Rs(), Cms() )
I am thinking of an alternative approach, the idea being to reduce on the steps to reorganising the array at every swap stage.. the idea came from 2 things …
_1 The recursion routine is fed currently the row indices of the rows that need to be sorted.
_2 We can use the VBA Application.Index Method which allows us to re sort an array “ in one go “ via a code line like , pseudo formula..
arrOut() = App.Indx( ArrIn() , {1;3;2} , {1,2,3} )
arrOut() = App.Indx( ArrIn() , row indices , column indicies )
arrOut() = App.Indx( ArrIn() , Rs() , Cms() )
The above code line would change an ArrIn() like this …_
A b c
D e f
G h I
_ … to a given output in arrOut() like this:
A b c
G h I
D e f
So the idea is that we sort the indices values, and then re apply the formula above
At this stage I propose modifying the existing code so as to have a better chance of a direct comparison in performance.. Both will be then subject to similar general inefficiencies arising from the very opened out explicit form of the codlings generally. I will do two version of this code, Sub SimpleArraySort7( and Sub SimpleArraySort8(
Sub SimpleArraySort7( will add the extra coding, and Sub SimpleArraySort8( will remove some of the now unnecessary / redundant parts thereafter, so as to attempt a good comparison to the previous Sub SimpleArraySort6(
Finally I may make a more trimmed version
Here the basic modifications for Sub SimpleArraySort7(__ , Sub TestieSimpleArraySort7()
Global Variables
To help simplify the comparison and so reduce the changes to the routines, I will have a some Global variables at the top of the module and outside any routine for the row and column indices
Dim Cms() As Variant, Rs() As Variant
This will allow me to refer to, that is to say change and use, in any copy of the recursion routine. (It would also be an alternative place here at the top of the module and outside any routine for our main array, arrTS(): we could then always refer to this, and then not need the ByRef arsRef() at the signature line of the recursion routine. But for now I will leave that as it is for closer comparison of the routines. )
These two “single width” arrays,
_ Rs() , “vertical, rows”
and
_ Cms() , “horizontal columns”
, will hold whole number indices for use in the VBA Index method formula idea.
For the type of the elements of these arrays, the Long type would be OK, and also in such situations VBA usually accepts Sting types that look like an number. The only reason that I use Variant is that I use a convenient way to get the initial indices, and that way happens to return a field of Variant types
To help in the development of this coding and to help with the explanation here, I have also moved the variable for the test range, RngToSort , to the top of the module and outside any routine to make it a global variable: This way I can use multiples of it’s dimensions to position intermediate paste outs of the arrTS(). For example , I have added a section immediately after the end of the main outer loop == for sorting, ' Captains Blog, Start Treck , which pastes out the current state of the sorted array , arrTS(), along with the current state of the indices, Rs()
Code:
' Captains Blog, Start Treck
Let RngToSort.Offset((RngToSort.Rows.Count * (CopyNo + 1)), 0).Value = arsRef()
RngToSort.Offset((RngToSort.Rows.Count * (CopyNo + 1)), -1).Resize(UBound(Rs(), 1), UBound(Rs(), 2)).Value = Rs()
Debug.Print " Running Copy " & CopyNo & " of routine." & vbCr & vbLf & " Sorted rows " & strRws & " based on values in column " & Clm & vbCr & vbLf & " Checking now for Dups in that last sorted list" & vbCr & vbLf
For Sub SimpleArraySort7(__ , I will also include a new array variable , as a global variable, arrIndx(). This I will fill by the formula line of
arrIndx() = Application.Index(arrOrig(), Rs(), Cms())
The formula above needs to be applied to the original range, so I also have another global variable which will contain the original range, arrOrig()
The final output, for example from the first passing of this section, can be seen here:
http://www.excelfox.com/forum/showth...ll=1#post11049
In that screenshot the output from the previous routines produced in
__arsRef()
is shown and , for comparison, alongside it is shown that produced by
______________arrIndx() = Application.Index(arrIndx(), Rs(), Cms())
Modifying indices values in main sort loop sorting
In other words, how do we get the modified Rs() to use in
arrIndx() = Application.Index(arrIndx(), Rs(), Cms())
The way the current coding is organised makes this fairly simple. We have sections where all column elements in a row are swapped.
__ For Clms = 1 To UBound(arsRef(), 2)
___ Let Temp = arsRef(rOuter, Clms): Let arsRef(rOuter, Clms) = arsRef(rInner, Clms): Let arsRef(rInner, Clms) = Temp
__ Next
We use the row information in the variables rOuter and rInner. So quite simply, we do the same swap for row indices,
Dim TempRs As Long
_ TempRs = Rs(rOuter, 1): Let Rs(rOuter, 1) = Rs(rInner, 1): Let Rs(rInner, 1) = TempRs
With those simple modifications we obtain for the final full run the results shown here:
http://www.excelfox.com/forum/showth...ll=1#post11050
Summary
In the next post the actual modifications to transform Sub SimpleArraySort6( to Sub SimpleArraySort7( are walked through in a bit more detail. Here is the brief summary to help in orienteering.
In the previous recursion routines, ( and still in this intermediate version, Sub SimpleArraySort7( ) the entire row values are swapped at various stags in the Bubble sort process. In our codings so far, we had typical sections like this
Code:
Dim Clms As Long '-------| with the condition met a loop is done for all columns in the array in which those two values used in the comparison are replaced at each column
For Clms = 1 To UBound(arsRef(), 2)
Let Temp = arsRef(rOuter, Clms): Let arsRef(rOuter, Clms) = arsRef(rInner, Clms): Let arsRef(rInner, Clms) = Temp
Next Clms '----------| for each column in the array at the two rows rOuter and rInner
In that above snippet, every column value in a row goes through the typical three line swap using a temporary variable: __ temp=My1 _ My1=My2 _ My2=temp https://excel.tips.net/T002525_Swapp...o_Numbers.html
For the new coding idea, we only need to swap the row indices so as to change their order from like {1;2;3;4….} to the new sorted order like ModifiedRowIndicies ={2;1;4;3….}, since then we will apply the idea .._
__ arrNext() = App.Indx( originalRange , rows() , columns() )
__ arrNext() = App.Indx(originalRange, ModifiedRowIndicies , {1,2,3,4,…..})
_.. to get the modified row order from the original range. ( The columns, columns() , remain in the original order , {1,2,3,4,…..} )
So we have an extra code line which just swaps the row indicia. ( In this intermediate version I still include the previous swap sections )
Code:
Dim Temp As Variant ' I want to Swap those 2 above - I cant easilly in any coding change two values simulataneosly. So one of them Element values will put in this temporary place. This Element Values will then be given the other. Finally the other Element will be given this temporary value
Dim Clms As Long '-------| with the condition met a loop is done for all columns in the array in which those two values used in the comparison are replaced at each column
For Clms = 1 To UBound(arsRef(), 2)
Let Temp = arsRef(rOuter, Clms): Let arsRef(rOuter, Clms) = arsRef(rInner, Clms): Let arsRef(rInner, Clms) = Temp
Next Clms '----------| for each column in the array at the two rows rOuter and rInner
Dim TempRs As Long
Let TempRs = Rs(rOuter, 1): Let Rs(rOuter, 1) = Rs(rInner, 1): Let Rs(rInner, 1) = TempRs
The main other extra code section is then the code line to get the new row order using the reordered row indices
___ arrIndx() = Application.Index(arrOrig(), Rs(), Cms() )
This done at the end of the sort section
Code:
Next rInner ' ---------------------------------------------------------------------
Next rOuter ' ===========================================================================================
Debug.Print "Doing an arrIndx()"
Let arrIndx() = Application.Index(arrOrig(), Rs(), Cms())
' Captains Blog, Start Treck
1 Attachment(s)
Sub TestieSimpleArraySort7() Sub SimpleArraySort7( )
Sub TestieSimpleArraySort7()
Sub SimpleArraySort7(ByVal CpyNo As Long, ByRef arsRef() As Variant, ByVal strRws As String, ByVal strKeys As String)
Global variables
At the top of the module in which are codes are we need our global variables
Code:
Dim Cms() As Variant, Rs() As Variant ' "HorizointalColumn" Indicies , "Virtical row" Indicies
Dim RngToSort As Range ' Test data range
Dim arrIndx() As Variant ' For modified array at end of each sort of a set of rows
Dim arrOrig() As Variant ' This arrIndx() = Application.Index(arrOrig(), Rs(), Cms()) applies the modified Rs() to the original unsorted data range. So we need an array to use constantly containing the original data range
Sub TestieSimpleArraySort7()
I use the same basic test data range as in other routines in the last few versions of Sub SimpleArraySort7(
Additionally I need the original data range in the separate ( global) array, arrOrig() , to use continuously in the first argument in the code line,
______Application.Index(arrOrig(), Rs(), Cms())
So this takes the value of the test data range
My array of column indicies , Cms() , is conveniently obtained in one go using a spreadsheet function , Column( ) which returns the column numbers of a given range in a horizontal array. I want like {1, 2, 3, 4, 5}, for my 5 column range, so I use something like Column(A:E) , Evaluate(Column(1to6)).JPG : https://imgur.com/jbaZdgJ
Similarly I can obtain the initial required vertical array indicia {1; 2; 3; ………} from a spreadsheet function, Row( ) Evaluate(Row(1to15)).JPG : https://imgur.com/UVTQCYO
' test index
I have a short test section whereby I paste out the row and column indices as well as the original array using these indices, .._
RngToSort.Offset(RngToSort.Rows.Count, 0).Value = Application.Index(arrTS(), Rs(), Cms())
_... which should return the original range for simple ordered indices, ( https://www.excelforum.com/excel-new...ml#post4571172 )
For convenience I paste out the array thus produced directly under the original range for comparison. We find that they are identical , as expected.
Sub SimpleArraySort7(ByVal CpyNo As Long, ByRef arsRef() As Variant, ByVal strRws As String, ByVal strKeys As String)
For this version we have kept the same coding as for Sub SimpleArraySort7(_
The signature line remains the same, as all the extra variable we need are the Global variables discussed.
In every swap section we add the row indicie swap line ( 3 lines )
TempRs = Rs(rOuter, 1): Rs(rOuter, 1) = Rs(rInner, 1): Rs(rInner, 1) = TempRs
The only extra line needed for the routine to function with the Index method idea is
__arrIndx() = Application.Index(arrOrig(), Rs(), Cms())
This last line is added at just after every sort. We also include in this version a few lines to paste out the stages in the sorting for both the array from the previous method, arsRef() , and that from the Index method idea, arrIndx()
Code:
Next rInner ' ---------------------------------------------------------------------
Next rOuter ' ===========================================================================================
Debug.Print "Doing an arrIndx()"
Let arrIndx() = Application.Index(arrOrig(), Rs(), Cms())
' Captains Blog, Start Treck
RngToSort.Offset((RngToSort.Rows.Count * (CopyNo + 1)), 0).Clear ' Area for array produced from previous method
Let RngToSort.Offset((RngToSort.Rows.Count * (CopyNo + 1)), 0).Value = arsRef()
RngToSort.Offset((RngToSort.Rows.Count * (CopyNo + 1)), RngToSort.Columns.Count).Clear ' Area for array produced by Index method idea
Let RngToSort.Offset((RngToSort.Rows.Count * (CopyNo + 1)), RngToSort.Columns.Count).Value = arrIndx()
RngToSort.Offset((RngToSort.Rows.Count * (CopyNo + 1)), -1).Resize(UBound(Rs(), 1), UBound(Rs(), 2)).Clear
Let RngToSort.Offset((RngToSort.Rows.Count * (CopyNo + 1)), -1).Resize(UBound(Rs(), 1), UBound(Rs(), 2)).Value = Rs() ' Current indicies order to apply to original range
Debug.Print " Running Copy " & CopyNo & " of routine." & vbCr & vbLf & " Sorted rows " & strRws & " based on values in column " & Clm & vbCr & vbLf & " Checking now for Dups in that last sorted list" & vbCr & vbLf
' Rem 3 Determine any duplicates in sort column values , and re run the routine to sort them by another column
Coding here
http://www.excelfox.com/forum/showth...ll=1#post11053
http://www.excelfox.com/forum/showth...ll=1#post11052
and in attached File
In the next post , the code is simplified slightly to just do the Index method idea