Android Example How to sort a Map

I searched the Forum regarding how to sort a Map but could not find anything.
Here is my solution:
B4X:
Sub Globals
    Type SortHulp1(Key1 As String, Val1 As String)
End Sub

Sub SORT_MAP1(inMap1 As Map, Sort1 As String) As Map    'inMap is the Map that needs to be sorted; Sort1 is "Key1" or "Val1"
    If Sort1<>"Key1" And Sort1<>"Val1" Then Return Null
    Dim SortList1 As List
    SortList1.Initialize
    SortList1.clear
    For i = 0 To inMap1.Size-1
      Dim p As SortHulp1
      p.Key1 = inMap1.GetKeyAt(i)
      p.Val1 = inMap1.GetValueAt(i)
      SortList1.Add(p)
    Next
    SortList1.SortType(Sort1, True)         'Sort the list based on the desired field.
    inMap1.Clear
    For i = 0 To SortList1.Size-1
      Dim p As SortHulp1
      p = SortList1.Get(i)
      inMap1.Put(p.Key1,p.Val1)
    Next
    Return inMap1
End Sub
Usage example in which MyMap1 is an existing Map that requires sorting (by key or by value):
B4X:
MyMap1 = SORT_MAP1(MyMap1,"Key1") 'or: 
MyMap1 = SORT_MAP1(MyMap1,"Val1")
 

LucaMs

Expert
Licensed User
Do not forget that maps can contain any type of object, both as keys and as values.

Work your variables of type SortHulp1 (strange name) although you did not initialize them?

SortList1.Clear immediately after SortList1.Initialize is not needed.

You could use two public constants instead of "key1" and "val1".
 

Syd Wright

Well-Known Member
Licensed User
(strange name)
I am Dutch...!
SortList1.Clear immediately after SortList1.Initialize is not needed
OK.
although you did not initialize them?
SortHulp1 is a Type and created under Globals; inMap indeed does not need to be initialized. I also initially thought that this would be necessary. I assume that the original Map is used and that "inMap" in the sub is just a temporary name to the object: The input Map is not first copied to a new map called inMap.
Do not forget that maps can contain any type of object, both as keys and as values.
I hear what you say, but this will complicate matters substantialy. The Maps that I use nearly always use strings for the Key and the Value. Feel free to improve my Android Example and share it with everyone. I am curious how you will make it universal for any kind of object. Sorting simply won't work nor will it ever be used for many objects...
 
Last edited:

LucaMs

Expert
Licensed User
I am Dutch...!
I'm sorry for you.
Just kidding, of course, I'm italian! ;)
I would not have even used the number 1, especially this I meant.

SortHulp1 is a Type and created under Globals; inMap indeed does not need to be initialized. I also initially thought that this would be necessary. I assume that the original Map is used and that "inMap" in the sub is just a temporary name to the object: The input Map is not first copied to a new map called inMap.
SortHulp1 should be declared in Process_Globals and variables of this type should be initialized. Parameters must be never initialized.

I hear what you say, but this will complicate matters substantialy. The Maps that I use nearly always use strings for the Key and the Value. Feel free to improve my Android Example and share it with everyone. I am curious how you will make it universal for any kind of object. Sorting simply won't work nor will it ever be used for many objects...
I could be wrong, of course, but in many cases this would also work well with objects (depending on the type and complexity of the object).
Soon I will try to modify your code and add it here.


Thank you for sharing!!!
 

DonManfred

Expert
Licensed User
strange name
not really... I can imagine the Author is from the Netherlands so SortHulp is probably Dutch and means something like SortHelp....
 

LucaMs

Expert
Licensed User
[Not tested well and, mainly, not tested with a map of objects - Do not forget I'm lazy :p and that I'm late for years :(]

B4X:
Sub Process_Globals
   Type tMapItem(Key As Object, Value As Object)
   Public Const SORTMAP_FIELD_KEY As String = "Key"
   Public Const SORTMAP_FIELD_VALUE As String = "Value"
End Sub


B4X:
' Sorts a Map based on the SortField you chose,
' the keys of the Map or the values of the Map.
' SortField: pass one of the two constants provided:
' SORTMAP_FIELD_KEY or SORTMAP_FIELD_VALUE.
Public Sub SortMap(MapToSort As Map, SortField As String, CaseInsensitive As Boolean, Ascending As Boolean) As Map    'inMap is the Map that needs to be sorted; Sort1 is "Key1" or "Val1"
   If SortField <> SORTMAP_FIELD_KEY And SortField <> SORTMAP_FIELD_VALUE Then Return Null
 
   Dim lstSort As List
   lstSort.Initialize
 
   For i = 0 To MapToSort.Size - 1
       Dim Item As tMapItem
       Item.Key = MapToSort.GetKeyAt(i)
       Item.Value = MapToSort.GetValueAt(i)
       lstSort.Add(Item)
   Next
 
   If CaseInsensitive Then
       lstSort.SortTypeCaseInsensitive(SortField, Ascending)
   Else
       lstSort.SortType(SortField, Ascending)
   End If

   MapToSort.Clear
   For i = 0 To lstSort.Size - 1
       Dim Item As tMapItem
       Item = lstSort.Get(i)
       MapToSort.Put(Item.Key, Item.Value)
   Next
 
   Return MapToSort
End Sub


' Test (strings only):
B4X:
Sub Activity_Create(FirstTime As Boolean)
   Dim mapStrings As Map
   mapStrings.Initialize
 
   For i = 1 To 20
       mapStrings.Put(i, Chr(Rnd(65, 91)))
   Next
 
   mapStrings = SortMap(mapStrings, SORTMAP_FIELD_VALUE, False, True)
 
   For Each Key As Int In mapStrings.Keys
       Log(Key & TAB & mapStrings.Get(Key))
   Next
End Sub


[The two constants are useful so you don't need to check if the user writes "key" or "KEY" or "kEy"... Also, providing constants for parameters is a good practice]
 
Last edited:

LucaMs

Expert
Licensed User
not tested with a map of objects
Tested: does not work :mad::eek:

B4X:
Sub Process_Globals
'...
Type tObject(ID As Int, Name As String, Capital As Double)

B4X:
Private Sub SecondTest
   Dim mapObjects As Map
   mapObjects.Initialize
  
   Dim lstNames As List
   lstNames.Initialize2(Array As String("Erel", "Klaus", "Mario", "Penelope", "Cameron", _
                                                    "Kate", "June", "Juliet", "Susan", "Mandy"))
   For i = 1 To 10
       Dim MyObject As tObject
       MyObject.Initialize
       Dim ID As Int = Rnd(1, 101)
       MyObject.ID = ID
       MyObject.Name = lstNames.Get(i - 1)
       MyObject.Capital = Rnd(100, 100000) / 100
       mapObjects.Put(ID, MyObject)
   Next
  
   mapObjects = SortMap(mapObjects, SORTMAP_FIELD_VALUE, False, True)
  
   Log("objects test" & CRLF)
   For Each Key As Int In mapObjects.Keys
       Log(Key & TAB & mapObjects.Get(Key))
   Next
  
End Sub

Error occurred on line: 100 (Main) [100 lstSort.SortType(SortField, Ascending)]
java.lang.RuntimeException: java.lang.ClassCastException: b4a.example.main$_tobject cannot be cast to java.lang.Comparable
 

Syd Wright

Well-Known Member
Licensed User
Pity that it doesn't work, but keep trying! :) At least my version in thread #1 does work fine for strings.
Good idea to use constants. I can't see where the cause of the java error is.
Yes "SortHulp" (Dutch) can best be translated to "Sorting Aid".
I often use numbered objects, like Dhulp1, Dhulp2 etc. My (self-taught) programming experience goes back about 40 years (to the time of the Zilog Z80 processor), so over the years many personal particularities (and quicks) pop up...
 

Syd Wright

Well-Known Member
Licensed User
OK, looks promissing. I tried your second test a moment ago and get the same java error.
Maybe converting input integers, longs, doubles etc. to a string before sorting (and reverse this after sorting) is a simple solution to use my original code. But ofcourse without using such a trick would be better.

By the way, the sorting has its own particularities. For example I tried to sort a list with times as the key:
for example 21:34, 7:15, 9:55, 15:10, 22:00, 6:40 and 23:36. Not only did I have to remove the ":" but also had to add a "0" in front of the times before 12:00 otherwise the sort result would be wrong!
 

LucaMs

Expert
Licensed User
By the way, the sorting has its own particularities. For example I tried to sort a list with times as the key:
for example 21:34, 7:15, 9:55, 15:10, 22:00, 6:40 and 23:36. Not only did I have to remove the ":" but also had to add a "0" in front of the times before 12:00 otherwise the sort result would be wrong!
Yes, when sorting numbers as strings they must been of the same length.
 
Last edited:

LucaMs

Expert
Licensed User
Tested: does not work :mad::eek:

B4X:
Sub Process_Globals
'...
Type tObject(ID As Int, Name As String, Capital As Double)

B4X:
Private Sub SecondTest
   Dim mapObjects As Map
   mapObjects.Initialize
 
   Dim lstNames As List
   lstNames.Initialize2(Array As String("Erel", "Klaus", "Mario", "Penelope", "Cameron", _
                                                    "Kate", "June", "Juliet", "Susan", "Mandy"))
   For i = 1 To 10
       Dim MyObject As tObject
       MyObject.Initialize
       Dim ID As Int = Rnd(1, 101)
       MyObject.ID = ID
       MyObject.Name = lstNames.Get(i - 1)
       MyObject.Capital = Rnd(100, 100000) / 100
       mapObjects.Put(ID, MyObject)
   Next
 
   mapObjects = SortMap(mapObjects, SORTMAP_FIELD_VALUE, False, True)
 
   Log("objects test" & CRLF)
   For Each Key As Int In mapObjects.Keys
       Log(Key & TAB & mapObjects.Get(Key))
   Next
 
End Sub

Error occurred on line: 100 (Main) [100 lstSort.SortType(SortField, Ascending)]
java.lang.RuntimeException: java.lang.ClassCastException: b4a.example.main$_tobject cannot be cast to java.lang.Comparable


Does "anyone" have the solution? :)
 

Diceman

Active Member
Licensed User
I don't believe sorting maps will work because you cannot be assured of traversing the map in any particular (consistent) order. You have to assume the order of the map is non-sequential (different from the order they were inserted) because the physical location of the rows in a map is determined internally by the Map class. The best you could hope for is to put the map keys into a List then sort the list. After the List has been sorted, you need to traverse the List and for each item in the list, get the value from the map. So if you have a map with 1000 entries in it, the List will have 1000 entries and in the loop for the list, you will look up the map values 1000 times. This will be quite slow for large maps.
 

Diceman

Active Member
Licensed User
The code in #7 works and you get ordered maps; the "only" "little" problem is that it is created to sort objects but it seems to work only with simple data types (strings, numbers).

I don't think you can depend on being able to traverse the map and expect it to return the rows in the same order they were added.

You are executing
MapToSort.Put(Item.Key, Item.Value)​

to put physically put the map into sorted order. But it doesn't guarantee the rows will stay in the same order as they were added. It may work 95% of the time.
It is like adding rows to a database table in sorted order and then using SQL "Select * from table" without an Order By clause because you are depending on the insert order to always retrieve the rows in the correct order.

You may need to confirm with with Erel if you need it sorted properly 100% of the time.
 

Diceman

Active Member
Licensed User
GetKeyAt / GetValueAt should never be used. It is explained in the video tutorial about collections.
You should use For Each instead.

I haven't followed the other posts. Sorry.

Is it possible to sort a map and traverse the map in sorted order (reliably)?
Or is it better to copy the map to a List, sort the list and traverse the List in sorted order?
 
Top