Nowadays there are plenty of
services available on the internet, e.g. for searching street names.
This tutorial shows us how to implement a searchhelp for street names
that doesn't require an active internet connection.
Starting point is a list with all streets in Germany: strassen_osm.txt:
The file size is about 1.50 MB and contains around 96000 entries.
The GuiXT script for entering the name of the street (first letters
are sufficient), result table etc. looks like this:
In the InputScript "search_street_va03.txt" we call a .NET
(VB/C#) function and then fill the table on the SAP screen with the data
that we recieve from the function. (We restrict the results to a maximum
of 10 rows.)
GuiXT
// Clear table
Set "V[streettab.*]" ""
callvb result = utils.StreetSearcher.FindStreets "&V[searchstreet]"
if not V[result=0]
message "&V[result]"
endif
Set V[i] 1
Set V[max_streets] 10
label start
copytext fromText="streets_results&V[i]" toString="streetTemp"
Set "V[streettab.cell.streets.&V[i]]" "&V[streetTemp]"
Set V[i] &V[i] + 1
if V[i=&V[max_streets]]
goto endloop
endif
goto start
label endloop
In the function "find_streets" of class "utilities" of our
class library "tutorials.dll" we search the textfile for
any matching entry. A match means that the street name starts with the
letters that the user has given. Then we put those results into a GuiXT
textvariable for the previous InputSript:
Imports guinet
Imports System
Imports System.Collections.Generic
Imports System.IO
Namespace utils
Friend Class MyStringComparer
Implements IComparer(Of String)
Public Function Compare(x As String, _
y As String) As Integer Implements _
IComparer(Of String).Compare
If x Is Nothing AndAlso y Is Nothing Then
Return 0
End If
If x Is Nothing Then Return -1
If y Is Nothing Then Return 1
' Compare full strings (case-insensitive)
' This allows proper binary search and sorting
Return String.Compare(x, y, _
StringComparison.CurrentCultureIgnoreCase)
End Function
End Class
Public Class StreetSearcher
Private streets_read As Boolean = False
Private streets As String()
Private ReadOnly comparer As _
MyStringComparer = New MyStringComparer()
Public Function FindStreets(search As String) _
As String
Dim g As guixt = New guixt()
Try
If String.IsNullOrWhiteSpace(search) Then
Return "Sucheingabe ist leer"
End If
' Load and sort streets on first use
If Not streets_read Then
Dim filePath As String = _
Path.Combine("c:\", "temp", _
"strassen_osm.txt")
If Not File.Exists(filePath) Then
Return String.Format( _
"Datei nicht gefunden: {0}", _
filePath)
End If
streets = File.ReadAllLines(filePath)
Array.Sort(streets, comparer)
streets_read = True
End If
' Clear result cells (1-10)
For c As Integer = 1 To 10
g.SetText("streets_results" + _
c.ToString(), "")
Next
' Perform binary search with prefix matching
Dim index As Integer = _
BinarySearchPrefix(streets, search, _
comparer)
If index < 0 Then
Return "Kein Eintrag gefunden"
End If
' Find the start of all matching entries
Dim startIndex As Integer = index
While startIndex > 0 AndAlso _
StartsWithIgnoreCase( _
streets(startIndex - 1), search)
startIndex -= 1
End While
' Display up to 10 matching results
Dim resultCount As Integer = 0
For c As Integer = startIndex _
To streets.Length - 1
If resultCount >= 10 Then Exit For
If StartsWithIgnoreCase( _
streets(c), search) Then
g.SetText( _
"streets_results" + _
(resultCount + 1).ToString(), _
streets(c))
resultCount += 1
ElseIf resultCount > 0 Then
' Stop when found matches but hit
' non-match
Exit For
End If
Next
If resultCount > 0 Then
Return "0"
Else
Return "Kein Eintrag gefunden"
End If
Catch ex As Exception
Return String.Format("Fehler: {0}", _
ex.Message)
End Try
End Function
''' <summary>
''' Performs binary search for entries starting
''' with the search string (case-insensitive).
''' </summary>
Private Function BinarySearchPrefix( _
array As String(), search As String, _
comp As IComparer(Of String)) As Integer
Dim left As Integer = 0
Dim right As Integer = array.Length - 1
While left <= right
Dim mid As Integer = left + _
(right - left) \ 2
Dim cmp As Integer = _
comp.Compare(array(mid), search)
' Check if entry starts with search term
If StartsWithIgnoreCase( _
array(mid), search) Then
Return mid
End If
' If string at mid is less than search,
' search right
If cmp < 0 Then
left = mid + 1
Else
right = mid - 1
End If
End While
Return -1
End Function
''' <summary>
''' Case-insensitive prefix matching helper.
''' </summary>
Private Function StartsWithIgnoreCase( _
source As String, prefix As String) _
As Boolean
If source Is Nothing OrElse _
prefix Is Nothing Then
Return False
End If
Return source.StartsWith(prefix, _
StringComparison.CurrentCultureIgnoreCase)
End Function
End Class
End Namespace
using guinet;
using System;
using System.Collections.Generic;
using System.IO;
namespace utils
{
internal class MyStringComparer :
IComparer<string>
{
public int Compare(string x, string y)
{
if (x == null && y == null) return 0;
if (x == null) return -1;
if (y == null) return 1;
// Compare full strings (case-insensitive)
// This allows proper binary search and
// sorting
return string.Compare(x, y,
StringComparison
.CurrentCultureIgnoreCase);
}
}
public class StreetSearcher
{
private bool streets_read = false;
private string[] streets;
private readonly MyStringComparer comparer =
new MyStringComparer();
public string FindStreets(string search)
{
guixt g = new guixt();
try
{
if (string.IsNullOrWhiteSpace(search))
{
return "Sucheingabe ist leer";
}
// Load and sort streets on first use
if (!streets_read)
{
string filePath =
Path.Combine("c:\\", "temp",
"strassen_osm.txt");
if (!File.Exists(filePath))
{
return
$"Datei nicht gefunden: " +
$"{filePath}";
}
streets =
File.ReadAllLines(filePath);
Array.Sort(streets, comparer);
streets_read = true;
}
// Clear result cells (1-10)
for (int c = 1; c <= 10; c++)
{
g.SetText(
"streets_results" +
c.ToString(), "");
}
// Perform binary search with
// prefix matching
int index = BinarySearchPrefix(
streets, search, comparer);
if (index < 0)
{
return "Kein Eintrag gefunden";
}
// Find the start of all matching
// entries
int startIndex = index;
while (startIndex > 0 &&
StartsWithIgnoreCase(
streets[startIndex - 1],
search))
{
startIndex--;
}
// Display up to 10 matching results
int resultCount = 0;
for (int c = startIndex;
c < streets.Length &&
resultCount < 10; c++)
{
if (StartsWithIgnoreCase(
streets[c], search))
{
g.SetText(
"streets_results" +
(resultCount + 1)
.ToString(),
streets[c]);
resultCount++;
}
else if (resultCount > 0)
{
// Stop when found matches
// but hit non-match
break;
}
}
return resultCount > 0 ? "0" :
"Kein Eintrag gefunden";
}
catch (Exception ex)
{
return $"Fehler: {ex.Message}";
}
}
/// <summary>
/// Performs binary search for entries
/// starting with the search string
/// (case-insensitive).
/// </summary>
private int BinarySearchPrefix(
string[] array, string search,
IComparer<string> comp)
{
int left = 0;
int right = array.Length - 1;
while (left <= right)
{
int mid = left +
(right - left) / 2;
int cmp = comp.Compare(
array[mid], search);
// Check if entry starts with
// search term
if (StartsWithIgnoreCase(
array[mid], search))
{
return mid;
}
// If string at mid is less than
// search, search right
if (cmp < 0)
{
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return -1;
}
/// <summary>
/// Case-insensitive prefix matching
/// helper.
/// </summary>
private bool StartsWithIgnoreCase(
string source, string prefix)
{
if (source == null ||
prefix == null)
return false;
return source.StartsWith(prefix,
StringComparison
.CurrentCultureIgnoreCase);
}
}
}
Notes:
- On using sorted lists we recommend you use a binary search (instead
of linear search) to significantly reduce the search time.
- The textfile containing the street names needs to be read only once from
the hard disk and is then available in memory for future requests.
The user can then mark any row of the result table and on
clicking "choose" the name of the chosen street is put into an
InputField:
GuiXT
Set V[i] "1"
label selection
if V[streettab.stat.rowselection.&V[i]=X]
set V[chosenstreet] "&V[streettab.cell.streets.&V[i]]"
endif
Set V[i] "&V[i]" + 1
if not V[i=11]
goto selection
endif
return