B4J Question .GetKeyAt()

Harris

Expert
Licensed User
Longtime User
B4X:
For i = 0 To Mastlst.Size-1
        Dim mstmap As Map                    ' create the master map from list of maps
        mstmap.Initialize
        mstmap = Mastlst.Get(i)              ' get the current map from the list
        
        For Each key As String In mstmap.Keys
           Dim mastpk As String = key        ' set the current mastpk (should be only 1)
        Next
'...

I have a list of maps...
For each map in list, I need the ID (pk).

Erel states - don't use GetKeyAt or GetValueAt (legacy code) for no other reason than it is not supported in B4i... (and is slower...).

My projects are littered with this - and it certainly made sense to get the key or value needed.
Now I need three lines to accomplish the same result... iterating over a list of 1... (and I have to think about how to code it to get desired result).

For Each key As String In mstmap.Keys
Dim mastpk As String = key ' set the current mastpk (should be only 1)
Next

I watched the videos (ETP) over and over and still at a loss to comprehend.

Why am I so dumb in understanding this simple concept? It just bugs me.
I am sure others have had to deal with this when considering Erel's "good housekeeping" guides...

Thanks
 

DonManfred

Expert
Licensed User
Longtime User
Dim mastpk As String = key ' set the current mastpk (should be only 1)
B4X:
Dim mastpk As String =  map.Get(key)

But if you only want to get a specific key. Why not using directly
B4X:
Dim mastpk As String =  map.Get("mastpk")
instead of going over all Keys?
 
Upvote 0

Harris

Expert
Licensed User
Longtime User
Isn't that getting the value (yes)? I need the key content at this point (ie. 1054).
Later, I will get another map associated with this key.
 
Upvote 0

Harris

Expert
Licensed User
Longtime User
Yes, hence the statement -
' set the current mastpk (should be only 1)

That is why I started this post. GetKetAt does what I need but is not recommended...
 
Upvote 0

mindful

Active Member
Licensed User
How many keys do you have in your map ? If you want to get only the first key that is in the map add Exit after you save the key:
B4X:
For each key as string in mstmap.keys
  Dim thefirstkey as string = key
  Exit
Next

If you need to pull a key at a specific index then you need to do you own enumeration:
B4X:
Dim i as int = 0
For each key as string in mstmap.keys
  If i = 5 then
    Dim thesixthkey as string = key
    Exit
  End if
  i = i + 1
Next
 
Upvote 0

Harris

Expert
Licensed User
Longtime User
Sorry, we are getting side tracked here...

The for next loop was how it was handled previously, with GetKeyAt...

Mastlst is a list of maps.
Each item in list is extracted to a map
This item (a map) contains a KEY and a VALUE.
I need the key's value - not the value's value at this point.

Perhaps I should just go away and study this a little longer since it not only confuses me.
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
I think there are times where the need for good housekeeping is outweighed by the need for simplicity.

To my mind, the speed comment is valid if you are going to traverse the list using GetKeyAt, and would invariably be slower over a full map.

The reason it is not compatible with B4i is that the order of elements in the B4i Map is not guaranteed, Which you would have to remember if you port code from B4j/B4a to B4i.

I do occasionally use getKeyAt, when it is simpler and I know where things are going to be stored as in this instance.

If the speed issue is of concern, you could always test it.
 
Upvote 0

mindful

Active Member
Licensed User
This item (a map) contains a KEY and a VALUE.
I need the key's value - not the value's value at this point
This is what the example i posted does.
B4X:
For each key as string in mstmap.keys
.keys is a list of keys values in the map and it takes each object and casts it to a string variable (key). So key variable will return the key value from the current point in loop.

When you see for each think about this way:
Dim i as int = 0
For i = 0 to mstmap.keys.size - 1
Dim key as string = mstmap.keys.Get(i)
Next

Its basically the same thing but placed in a more simpe statement: for each object in the list do this..
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Hi @Harris,
I am reading again and again your post #7 but I'm not sure to fully understand it.
You have a list. Each item in the list is a map. Each map ha only ONE item, the one carrying as the key the value you're looking for?
If yes, did you choose this "maps in list" arrangement because of duplicate items having the same key?
I mean, why don't simply use a single map carrying all the key/value pairs?

Again, I'm sure I'm missing something ..and a coffee would help me some, but why not ask here?
 
Upvote 0

Harris

Expert
Licensed User
Longtime User
Dim key as string = mstmap.keys.Get(i)
Yes, this is what is needed...
Too bad it doesn't exist ( yet it does with getkeyat(i)... )

I think there are times where the need for good housekeeping is outweighed by the need for simplicity.

I agree. My list (of maps) is very small so speed it not the concern. I have maps buried 4 levels deep (map.Put("key", another_map) ) at it takes 2 ms to get results...
GetKeyAt and GetValueAt are so very useful that it is a shame to abandon them, and they are so simple to use.
My concern was the advise not to use them anymore - but ever?
I don't want to put myself at risk - should these methods be deprecated in a future release - and I have some major cleanup involved.
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
should these methods be deprecated in a future release
Erel is the only one who can answer that, but I don't think he has removed anything yet. And as in Mindful's code, it would be simple enough to replace the method call with a sub if needed.
 
Upvote 0

Harris

Expert
Licensed User
Longtime User
And as in Mindful's code
I suppose... anything is possible with B4X.

If anything - it got the wheels turning here.
This isn't something we normally encounter.
It is a twist on what I have known and now have learned (struggled) to use as an alternative.
The alternate just does not seem natural and/or intuitive ( a departure from my simple comfort zone ).

Bouncing feedback helps me with your individual slants on this - for you people are far more proficient than I in any of these subject matters.
I thank you for your input.
 
Upvote 0

aminoacid

Active Member
Licensed User
Longtime User
I too had to convert some code that used GetValueAt and GetKeyAt when I discovered it was not supported in B4I. Even though the usage may not be efficient I feel that it sometimes simplifies the code and makes it easier to understand. Perhaps Erel will consider not deprecating it and supporting it in a subsequent release of B4I
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Yes, hence the statement -
' set the current mastpk (should be only 1)
Don't use a map in these situations. Either

1) Use a list, making index 0 the "key" and index 1 the "value"

B4X:
For i = 0 To Mastlst.Size-1
        Dim mstmap As List                    ' create the master map from list of maps
        mstmap.Initialize
        mstmap = Mastlst.Get(i)              ' get the current map from the list
       
        Dim mastpk As String = mstmap.Get(0)        ' set the current mastpk (should be only 1)
'...

2) Use a Type (example
B4X:
Type singleEntryMap (aKey as String, theValue as Object)
)

B4X:
For i = 0 To Mastlst.Size-1
        Dim mstmap As singleEntryMap                    ' create the master map from list of maps
        mstmap.Initialize
        mstmap = Mastlst.Get(i)              ' get the current map from the list
       
        Dim mastpk As String = mstmap.aKey        ' set the current mastpk (should be only 1)
'...
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
To reduce the code further using @OliverA type example
B4X:
For Each mstmap As singleEntryMap In MastLst
    Dim mastpk As String = mstmap.aKey
    ...
next
 
Upvote 0

Harris

Expert
Licensed User
Longtime User
Here is my (somewhat convoluted) code...
It functions and is quick - but granted, it could be cleaner...

All of this is done to record events that exceed the companies recommended policies.
Vehicle (engine) data is supplied from a BlueTooth adaptor. Code to process not shown here (java code).
For example 1: When vehicle speed > 41 mph AND the brake is ON (for more than 3 seconds), record an event... (excessive brake wear)
For example 2: If RPM > 1000 and IS Unloading (for more than 4 seconds), record an event. (over-revving the PTO pump causing damage)

The list of events can be many and all are processed in real time, every second, when the ignition key is ON.

B4X:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
     Private NativeMe As JavaObject  ' used for vehicle / engine data...
     Public mympp,ECMmap,tECMmap,pgnmp, EventMap As Map
     Private Mastlst, MastEvent, MastEventNum As List
     Public ecmtimer As Timer
      Private inEvent As Boolean = False
     Public sql6, sql As SQL     
     Private ignstatus, tempmap As String
     Private inc As Long = 0
     Private rinc As Long = 0
    
     Private mLst As List
     Private lastcon, newcon As Long
     Public rectick As Int = 1000
     Public ecmmastrec, currmastrec As Long
     Private noadd As Boolean = False
     Public CurrFileName As String = ""
     Private TempFileName As String = ""
     Public fWriter As TextWriter
     Private outstrm As OutputStream
     Private MyDir As String

    Public ignore1708 As Boolean = True
    Public ignore1939 As Boolean = False

'   operator constants... (= , <>, >= , <=, On, Off, AND, OR, ...)
    Private const EQL   As Int = 0
    Private const NEQL  As Int = 1     
    Private const GTEQL As Int = 2
    Private const LTEQL As Int = 3
    Private const LTHAN As Int = 4   
    Private const GTHAN As Int = 5

    Private const CON  As Int = -1
    Private const COFF As Int = -2

    Private const CAND As Int = 1
    Private const COR  As Int = 2     
    
    Type zonemast (mastpk As String, tmap As Map)
    
End Sub
Sub Service_Create
  mympp.Initialize
  ECMmap.Initialize
  tECMmap.Initialize
  pgnmp.Initialize
  mLst.Initialize
 
  tempmap = ""
 
  Mastlst.Initialize
  MastEvent.Initialize
 
  MyDir = File.DirDefaultExternal ' & "/ecmfiles/"
  File.MakeDir(MyDir,"ecmfiles")
  
  ecmtimer.Initialize("tim",rectick)
  NativeMe.InitializeContext
 
  C_DB
  Log(" ...Is 1939 ignored?: "&ignore1939)
    
  SetProtocol
  UpdatePGNmap

  BuildMastEventList 
 
End Sub


Sub Service_Start(StartingIntent As Intent)

  Log(" Ecm Service Started")
  ecmtimer.Enabled = True
 
End Sub



' build a structure to use for event evaluation
'
Public Sub BuildMastEventList
EventMap.Initialize
Mastlst.Initialize
MastEvent.Initialize
MastEventNum.Initialize

DefCM.SQL1.ExecNonQuery("DROP TABLE IF EXISTS evtviols")
DefCM.SQL1.ExecNonQuery("CREATE TABLE IF NOT EXISTS evtviols ( mpk INTEGER, sdate INTEGER, edate INTEGER, evtviol TEXT, evtzone INTEGER, evttype INTEGER, drvid INTEGER, trknum TEXT, sodo TEXT, eodo TEXT, ival1 INTEGER, ival2 INTEGER, sval1 TEXT, sval2 TEXT, dval1 REAL, dval2 REAL, compid INTEGER, sent INTEGER, lat REAL, lon REAL, location TEXT )")

Try
   Dim c, mst As Cursor
   mst = DefCM.SQL1.ExecQuery("Select * from zonemast where stype = 5")   ' type 5 are 'these' special events...  type 1 and 2 are Geo-Zones

   For i = 0 To mst.RowCount - 1
        mst.Position = i
        Dim mast As Map
        mast.Initialize

        Dim zm As zonemast   ' create new Type
        zm.Initialize

        Dim mstid As Int = mst.GetInt("mastpk")
        Dim mstevt As Map
        mstevt.Initialize
        
        For x = 0 To mst.ColumnCount -1
           mstevt.Put( mst.GetColumnName(x), mst.GetString2(x) )  ' add data from table to a map for faster processing - avoid SQL each time...
        Next
        
        zm.mastpk = mstid    'set the types values  - adding a map here
        zm.tmap = mstevt
        MastEvent.Add(zm)    ' add these types to a list
        
        MastEventNum.Add(mstid)   ' add the mastID to a list for quick lookups ( Could I use the zm Type for this?)

        Dim m As Map
        m.Initialize
        
' I reuse a table already constructed for a different purpose
        c = DefCM.SQL1.ExecQuery("Select * from zonedet where mastpk = "&mstid)
        For j = 0 To c.RowCount - 1
            c.Position = j
            Dim dm As Map
            dm.Initialize
            dm.Put("pid", c.GetInt("lat"))
            dm.Put("op",  c.GetInt("lon"))
            dm.Put("val", c.GetInt("s1"))
            dm.Put("more",c.GetInt("s2"))
            m.Put(c.GetInt("pk"), dm)
        Next   
        c.Close
        mast.Put(mstid,m) 
        Mastlst.Add(mast)       
    Next       
    mst.Close

    ' just for testing.... to be removed....
    For i = 0 To MastEvent.Size-1
        Dim zm As zonemast

        zm.Initialize
        zm = MastEvent.Get(i)
    
        Log(" mastevent list: "&MastEventNum.Get(i)&CRLF&zm)
    Next

    
Catch
  Log(" blew up in BuildMastEventList "&LastException.Message)
End Try
        
End Sub


' this runs in a timer each second....
Private Sub ProcViols
Dim st, et, ms  As Long
st = DateTime.Now
Dim res As Boolean = True
Try
    For i = 0 To Mastlst.Size-1
        Dim mstmap As Map                    ' create the master map from list of maps
        mstmap.Initialize
        mstmap = Mastlst.Get(i)              ' get the current map from the list
        
        For Each key As String In mstmap.Keys
           Dim mastpk As String = key        ' set the current mastpk (should be only 1)
        Next
        

        For j = 0 To mstmap.Size -1   
            res = True
            For Each v As Map In mstmap.Values    ' get a map from each master
                    For Each x As Map In v.Values     ' get the map and process the values
                    'Log( "vars - PID: "&x.Get("pid")&"  Operator: "&x.Get("op")&"  Value: "&x.Get("val")&"  And/Or: "& x.Get("more") )
                    
                    Dim bd As Boolean =  ProcessCurrPID(x.Get("pid") , x.Get("op"), x.Get("val"), x.Get("more") )  ' process each event
                    If bd = False Then
                       res = False         ' if a condition fails - ALL params fail...
                    End If
                Next

                If res Then    ' if ALL conditions passed
                    
                   If EventMap.ContainsKey(mastpk) Then
                        
                      ' then the event was started already - need to check for end of this event
                      ' res was inited - still in event...  do nothing

                   Else
                      SaveEventStart(mastpk)   
                      EventMap.Put(mastpk, res)
                      ' build our viol record...
                   End If
                  
                Else   ' when false
                    
                   If EventMap.ContainsKey(mastpk) Then  ' does the set contain this record when false?
                         UpdateEvent(mastpk)                ' if so, update the event a write a record
                   End If
                        
                End If
                
            Next
        Next   
    Next
Catch
    Log(" blew up in ProcViols: "&LastException.Message)
End Try   

et = DateTime.Now
ms = et - st
    
Log(" ----------------    Time: (ms): "&ms)  ' how long does the process take? generally 2 to 60 ms
        
    
End Sub


' determine if the passed pid meets the requirements ( ie:  is this true?    61 >= 60   )
'
Private Sub ProcessCurrPID(pid As String, opr As Int, pidval As Double, more As Int) As Boolean
    
Dim ret As Boolean = False
Dim thispidval As Double

Try

    Select pid
        
        Case 2   ' this is a speed pid - determine if value is metric or US
           Dim tpi As Double = ECMmap.Getdefault("2",0)
          
           If DefCM.metric = False Then   ' speed in MPH
              thispidval = NumberFormat2(tpi * 0.62137,1,1,1,False)
'              Log(" us miles pid: "&pid&"  pidval value: "&NumberFormat2( thispidval,1,1,1,False) )
           Else
              thispidval = tpi
'              Log(" KMS pid: "&pid&"  pidval value: "& NumberFormat2( thispidval,1,1,1,False) )
           End If

        Case 16  ' this is a brake pid.  Determine if it is ON - and change the value to numeric for processing
            Dim tpid As String = ECMmap.Getdefault("16",-2)
            If tpid.ToLowerCase = "on" Then
                thispidval = CON
            Else
                thispidval = COFF
            End If
        
        Case 99   ' this is the ignition switch - change to numeric value
            Dim tpidx As String = ECMmap.Getdefault("99",0)
            If tpidx.ToLowerCase = "on" Then
                thispidval = CON
            Else
                thispidval = COFF
            End If
            
        Case Else  ' the default for all other pids - values are numeric... 
                thispidval = ECMmap.GetDefault(pid, -100)
                
    End Select

'    Log(" What is THIS pid "&pid& "  and pidval: "& NumberFormat2( thispidval,1,1,1,False))

    Select opr

        Case EQL  '  see constants
            If thispidval = pidval Then   ' equal to...
               ret = True   
            End If
'            Log(pid&" operator was = "&thispidval&"   -   "&pidval)
            
        Case NEQL
            If thispidval <> pidval Then   ' NOT equal to...
               ret = True   
            End If
'            Log(pid&" operator was not <> "&thispidval&"   -   "&pidval)

        Case GTEQL
            If thispidval >= pidval Then
               ret = True   
            End If
'            Log(pid&" operator >= " &thispidval&"   -   "&pidval)

        Case LTEQL
            If thispidval <= pidval Then
               ret = True   
            End If
'            Log(pid&" operator was <= "&thispidval&"   -   "&pidval)

        Case LTHAN
            If thispidval < pidval Then
               ret = True   
            End If
'            Log(pid&" operator was < "&thispidval&"   -   "&pidval)

        Case GTHAN
            If thispidval > pidval Then
               ret = True   
            End If
'            Log(pid&" operator was > "&thispidval&"   -   "&pidval)
            
    End Select
        
Catch
    Log(" blew up in ProcessCurrPID "&LastException.Message)
End Try           
Return ret
    
End Sub




Private Sub UpdateEvent(mpk As String)
    
Try   
    
    Dim zm As zonemast
    zm.Initialize
    Dim idx As Int
    For i = 0 To MastEventNum.Size-1
        If (mpk = MastEventNum.Get(i)) Then
           idx = i
           Exit   
        End If
    Next
'    idx = MastEventNum.IndexOf(idx)   ' tried indexof - but it always returned -1
    
    Log(" What is idx: "&idx&"  Mastnum: "&MastEventNum& "  mpk: "&idx)
    
    zm = MastEvent.Get(idx)   ' get the Type at the index
    
    Dim m As Map
    m.Initialize
    m = zm.tmap   ' get the map stored in type zm
    Log(" What is zm type: "&zm)

    Dim grace As Int = m.GetDefault("s1",0)   
    'Dim summ As  Int = m.GetDefault("s3",0) ' to use later...
    
   Dim c As Cursor
   c = DefCM.SQL1.ExecQuery("Select * from evtviols where mpk = "&mpk)
    If c.RowCount > 0 Then
        c.Position = 0
        Dim sd As Long = c.GetLong("sdate")
        Dim grc As Long = (DateTime.Now - sd) / 1000
        
        If grc > grace Then  ' record an event is duration exceeds grace period allowed....
            
            LogColor("  Recording violation "&mpk&" Time: "&grc&"  Grace: "&grace, Colors.Blue)
            Dim tmap As Map
            tmap.Initialize
            tmap.Put("sdate",c.GetLong("sdate"))
            tmap.Put("edate",DateTime.Now)
            tmap.Put("vspeed",grc)
            tmap.Put("zone",mpk)
            tmap.Put("event",5)
            tmap.Put("trknum",DefCM.DefTrkNum)
            tmap.Put("compid",DefCM.Company_id)
            tmap.Put("drvid",DefCM.CDrv)
            tmap.Put("sodo",c.GetString("sodo"))
            tmap.Put("eodo",NumberFormat2(GPSServMod.ECMOdom,1,1,1,False))
            tmap.Put("sourceid",DefCM.ShiftID)
            tmap.Put("swver",DefCM.swver)
            tmap.Put("comment","")

            CallSubDelayed2(LogServmod,"RecordTheseEvents",tmap)   ' make a record for reporting...
            DefCM.SQL1.ExecNonQuery("DELETE FROM evtviols where mpk = "&mpk) ' allow a new record to start...
        Else
            LogColor("  NOT recording viol "&mpk&" Not Exceed Grace: "&grc, Colors.Red)
            
        End If
    Else
        LogColor("  NOT recording viol "&mpk&" Not Found: ", Colors.Red)
            
    End If   
    c.Close
    
Catch
    Log(" blew up in UpdateEvent "&LastException.Message)
End Try       

EventMap.Remove(mpk) ' remove the item from the map
    
    
End Sub



Private Sub SaveEventStart(mpk As String)

    DefCM.SQL1.ExecNonQuery2("INSERT INTO evtviols (mpk,sdate,edate,evtviol,evtzone,evttype,drvid,trknum,sodo,eodo,compid,sent,lat,lon,location) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", _
    Array As Object(mpk,  DateTime.Now, 0, 0, mpk , 5, DefCM.CDrv,DefCM.DefTrkNum, NumberFormat2( GPSServMod.ECMOdom,1,1,1,False) , 0,0, 0, GPSServMod.gLoc.fLatitude, GPSServMod.gLoc.fLongitude, DefCM.CurrLoc)  )
  
  
   ' just for testing... to be removed....
    Dim c As Cursor
    c = DefCM.SQL1.ExecQuery("Select * from evtviols")
    If c.RowCount > 0 Then
    For i = 0 To c.RowCount -1
        c.Position = i
        Log(" viol recs: "&c.GetString("mpk")&"  "&c.GetString("sdate"))
    Next
    End If
    c.Close
    
End Sub
 
Upvote 0

Harris

Expert
Licensed User
Longtime User
it's better to switch to a SQlite db
All of these params (list and maps) originated in a SQlite db table...

I wanted to strictly avoid having to create a SQL query ONCE EVERY SECOND to do what needs to be done...
When not hammering on the data so rapid fire - I certainly do use the table - because, as you point out, it is much easier.

Thanks
 
Upvote 0
Top