Android Question Improvement for SearchView!

vfafou

Well-Known Member
Licensed User
Longtime User
Hello!
I want to use SearchView to my project:
https://www.b4x.com/android/forum/t...ul-alternative-to-autocompleteedittext.19379/

I'm trying to implement a functionality similar to Google Places Autocomplete without Google API!
So, I have a flat file with ~55000 rows with streets within the corresponding areas. I load only the 1st index (lightning fast process, thank Erel!!! :)) because I don't need to show "SubStrings" and all are working fine.
My only problem is that after street name and before area, I need to type the street number and then the ListView stops loading results because of not matching!

The row format in the flat file is:

StreetName, Area, PostCode

and I want to type:

StreetName 99, Area, PostCode

Is there any way to do this, assuming that it isn't possible to have the street numbering into the file?

Bonus question: Is it possible to use CustomListView for displaying/selecting results instead of the simple ListView?

Thank you in advance!
 
Last edited:

emexes

Expert
Licensed User
I did something like this a couple of years back, but in PowerBasic on a PC, for a similar number of addresses (vs the millions of addresses of the full country database). Somewhat embarrassingly, I can't find the program right now (I did find the database, though: was 3.6 million lines but just for the state).

Anyways, it was a ripper. What it would do is present an ever-shrinking list of addresses that contained/matched all of the words that you had typed in so far.

So let's say I was looking for 14 Zig Zag Road Belgrave and so far I had typed:

"14 Z"

then the program split that into space-separated words of "14" and "Z", scanned the list of 40795 addresses, and showed me the first dozen of the 22 addresses that had both those "14" and "Z" in it:

[1] 114 BLUE HORIZONS WAY, PAKENHAM 3810
[2] 14 AZURE COURT, PAKENHAM 3810
[3] 14 BRONZEWING STREET, PAKENHAM 3810
[4] 14 ELIZA HEIGHTS, PAKENHAM 3810
[5] 14 ELIZABETH COURT, PAKENHAM 3810
[6] 14 FITZROY STREET, PAKENHAM 3810
[7] 14 HAZELVALE ROAD, TECOMA 3160
[8] 14 JAZZ COURT, PAKENHAM 3810
[9] 14 MACKENZIE STREET, COCKATOO 3781
[10] 14 OZONE AVENUE, EMERALD 3782
[11] 14 TRIBUZI CLOSE, PAKENHAM 3810
[12] 14 ZENITH STREET, PAKENHAM 3810


which unfortunately doesn't show the address I'm looking for because it's further down the list, but... no worries, because by now I've pressed the "I" key and so my search text is "14 ZI" that the program splits into space-separated words of "14" and "ZI" and now the list of possibilities is reduced to:

[1] 14 MACKENZIE STREET, COCKATOO 3781
[2] 14 TRIBUZI CLOSE, PAKENHAM 3810
[3] 14 ZIG ZAG ROAD, BELGRAVE HEIGHTS 3160


and then when I press "G" = search text "14 ZIG" = words "14" and "ZIG" = one match

[1] 14 ZIG ZAG ROAD, BELGRAVE HEIGHTS 3160

Bingo!

The beauty of doing substring searches is that... well, let's take Mackenzie Street from above. There are at least 4 ways to (mis)spell that name: it could be "Mc" or "Mac", and with or without a space after the "c". But we can search for "14 KENZ" and we will find it, even though the "KENZ" is in the middle of a word.

Also, it is order-agnostic, so if you search for "Town1 Town2" then it will find both "Town1-Town2 Road" and "Town2-Town1 Road" (and also the local shorthands "Town1 Road" in Town2 and "Town2 Road" in Town1 - Bonus!)

On a Windows laptop, scanning through those 40795 addresses took < 2 ms, so speed wasn't an issue. It might be different on a phone.
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
One other little nice thing I had in the program is that it would remember the postcode of the last search I'd done, and then in the current search it would list the matches that were for that postcode first (followed by all the other matches for the other postcodes). It was bordering on spooky, how quick it would find the results - a lot of the time, I'd just type the house number and the first letter of the street name and... bingo!

It also moved up the list any results which precisely matched any search number. Eg, if you search for "5 Main St" then it would give preference to results that contained [not-a-digit]5[not-a-digit] so that results with a house number of "5" (or "5a" or "5b" or "3-5" or "3/5") would be put ahead of those with house numbers like "15" or "25" or with postcodes containing a "5".

Like I said, it was spooky how quick it would find an address. And if you had a high house number, like say 722, then often there would only be one matching address and you wouldn't need to type in the street name. Not even the first letter. I told one guy that it worked off voice recognition and that it could hear us speaking the address, he was even more spooked out than I was ;-)

<HUMOUR>Back around 2000, I had a Palm Pilot (an early PDA, before touchscreen phones were a thing). My non-tech-savvy Mum asked me what it was, so I pressed the calculator shortcut to bring up a numeric keypad, pulled the stylus half-out so it looked like an antenna, held it up to my ear and told her it was a mobile phone. She was a bit doubtful, but then I started dialling up a number on the screen and that was enough to convince her that (for once) I was not joking. Man, I should have patented that.</HUMOUR>
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
Hey, I know this is pushing things a bit far, but... after dinner, I was thinking that one day I'll probably use this myself in B4J, and I was curious as to whether Java was up to the task performance-wise (on my 7 year old laptop), so I thought, I'll just do it.

Man, Java Strings are much faster than I thought they were. I can still often speed things up 2-to-4 times by getting down and dirty with Chars and Ints and bitmasks, but... the payoff isn't as big as it has been with other compilers. I think what's happening is that Java Strings are hanging on to and reusing old deleted storage space, rather than releasing it back to the OS/VM and going through the memory allocation rigmarole when space is needed again. Plus avoiding creating new strings, eg, no need to change anything for a .Trim on a string that doesn't have a space front or rear, or a .ToUpperCase on an already-uppercase string.

Anyways, so I wrote the thing, and this is what I've got:

B4X:
Read 40795 addresses in 235 ms
Found 3 matching addresses in 20 ms
14 MACKENZIE STREET, COCKATOO 3781
14 TRIBUZI CLOSE, PAKENHAM 3810
14 ZIG ZAG ROAD, BELGRAVE HEIGHTS 3160

It searches 40795 addresses for 2 keys ("14" and "ZI") in 20 ms. I can live with that.

Code is below, and address file is attached:

B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
#End Region

Sub Process_Globals
    Private fx As JFX
    'Private MainForm As Form
 
    Dim Addresses() As String
    Dim NumAddresses As Int = 0
 
End Sub

Sub ReadAddressFile(D As String, F As String)
 
    Dim TempAddresses As List
    TempAddresses.Initialize
 
    Dim AddressString As String
 
    Dim TR As TextReader
    TR.Initialize(File.OpenInput("c:\b4j", "addresses.txt"))
    Do While True
        AddressString = TR.ReadLine
        If AddressString = Null Then
            Exit
        End If
 
        If AddressString.Contains(",") Then
            TempAddresses.Add(AddressString.Trim.ToUpperCase)
        else if AddressString.Trim.Length <> 0 Then
            Log("Missing comma for address: " & AddressString)
        End If
    Loop
    TR.Close

    'copy to globals, ready for searching

    NumAddresses = TempAddresses.Size

    Dim Addresses(NumAddresses) As String
    For I = 0 To NumAddresses - 1
        Addresses(I) = TempAddresses.Get(I)
    Next
 
End Sub

'return list of addresses that contain all search keystrings
'to maximum of Limit addresses
Sub FindAddress(K As List, Limit As Int) As List
 
    Dim NumKeys As Int = K.Size
    Dim KeyString(NumKeys) As String
 
    For I = 0 To NumKeys - 1
        KeyString(I) = K.Get(I)
        KeyString(I) = KeyString(I).ToUpperCase    'note: not trimmed
    Next

    Dim FoundAddresses As List
    FoundAddresses.Initialize

    Dim FoundFlag As Boolean
 
    For AI = 0 To NumAddresses -1
        FoundFlag = True
        For KI = 0 To NumKeys -1
            If Addresses(AI).Contains(KeyString(KI)) = False Then
                FoundFlag = False
                Exit
            End If
        Next
 
        If FoundFlag Then
            FoundAddresses.Add(Addresses(AI))
            If FoundAddresses.Size >= Limit Then
                Exit
            End If
        End If
    Next
 
    Return FoundAddresses
 
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    'MainForm = Form1
    'MainForm.RootPane.LoadLayout("Layout1") 'Load the layout file.
    'MainForm.Show
 
    Dim StartTime As Long = DateTime.Now
    ReadAddressFile("c:\b4j", "addresses.txt")
    Dim EndTime As Long = DateTime.Now
    Log("Read " & NumAddresses & " addresses in " & (EndTime - StartTime) & " ms")
 
    Dim K As List
    K.Initialize
 
    K.Add("ZI")
    K.Add("14")
 
    Dim FoundList As List
 
    Dim StartTime As Long = DateTime.now
    FoundList = FindAddress(K, 1000)
    Dim EndTime As Long = DateTime.Now
    Log("Found " & FoundList.Size & " matching addresses in " & (EndTime - StartTime) & " ms")
 
    For Each S As String In FoundList
        Log(S)
    Next
 
End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub
 

Attachments

  • Addresses.zip
    210 KB · Views: 346
Last edited:
Upvote 0
Top