Link to home
Start Free TrialLog in
Avatar of mcdonald_g
mcdonald_gFlag for United States of America

asked on

Searching an array for strings

I created an array to hold and entire Ebook name  "Ebook()". The array holds all the versus of the Bible. First, should the array be String or Byte? Also, could someone provide Search engine code to search for words or phrases in the Ebook() array.  Thanks!
SOLUTION
Avatar of David Lee
David Lee
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Oh, and I should have mentioned that if the search finds nothing it returns a 0.  Otherwise it'll return the elment number of the item containing the search string/phrase.
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
If you change SearchArray to the below, then it will a find verses that contains any of the words (seperated by spaces) in the textbox:

Private Function SearchArray(ByVal strSearchText As String, ByVal lastIndex As Long) As Long
    Dim intItem As Long
    Dim intWord As Byte
    Dim words As Variant
    Dim curVerse As String
   
    words = Split(strSearchText, " ")
    For intItem = (lastIndex + 1) To UBound(Ebook)
        curVerse = Ebook(intItem)
        For intWord = LBound(words) To UBound(words)
            If InStr(1, curVerse, words(intWord), vbTextCompare) > 0 Then
                SearchArray = intItem
                Exit Function
            End If
        Next
    Next
    If lastIndex > 1 Then
        SearchArray = -1
    End If
End Function
I also changed Command1_Click() to handle the new return value of -1 from SearchArray().

Sorry, here is the whole thing with the changes that allow finds matches if any of the words in the textbox are found in the verse:

Option Explicit
Option Base 1     'Because I hate 0 based arrays
   
Private verseIndex As Long
Private Ebook As Variant

Private Sub Form_Load()
    ReDim Ebook(5)
    Ebook(1) = "Genesis 1:1 In the beginning God created the heaven and the earth."
    Ebook(2) = "Genesis 1:2 Now the earth was unformed and void, and darkness was upon the face of the deep; and the spirit of God hovered over the face of the waters."
    Ebook(3) = "Genesis 1:3 And God said: 'Let there be light.' And there was light."
    Ebook(4) = "Genesis 1:4 And God saw the light, that it was good; and God divided the light from the darkness."
    Ebook(5) = "Genesis 1:5 And God called the light Day, and the darkness He called Night. And there was evening and there was morning, one day."
   
    verseIndex = 0
    Text1.Text = "light"
    Label1.Caption = ""
    Command1.Caption = "Search"
End Sub
   
Private Sub Text1_Change()
    verseIndex = 0
    Command1.Caption = "Search"
End Sub
       
Private Sub Command1_Click()
    If Text1.Text <> "" Then
        verseIndex = SearchArray(Text1.Text, verseIndex)
        Select Case verseIndex
            Case 0
                Label1.Caption = "[No Match Found]"
                Command1.Caption = "Search"
                MsgBox "No Match Found", vbInformation, "Search"
               
            Case Is > 0
                Label1.Caption = Ebook(verseIndex)
                Command1.Caption = "Find Next"
               
            Case -1
                verseIndex = 0
                Command1.Caption = "Search Again"
                MsgBox "Last Verse Reached", vbInformation, "Search"
               
        End Select
    End If
End Sub

Private Function SearchArray(ByVal strSearchText As String, ByVal lastIndex As Long) As Long
    Dim intItem As Long
    Dim intWord As Byte
    Dim words As Variant
    Dim curVerse As String
   
    words = Split(strSearchText, " ")
    For intItem = (lastIndex + 1) To UBound(Ebook)
        curVerse = Ebook(intItem)
        For intWord = LBound(words) To UBound(words)
            If InStr(1, curVerse, words(intWord), vbTextCompare) > 0 Then
                SearchArray = intItem
                Exit Function
            End If
        Next
    Next
    If lastIndex > 1 Then
        SearchArray = -1
    End If
End Function
Avatar of mcdonald_g

ASKER

Thanks Idle Mind
Thank you for giving BlueDevilFan assist points.

I have been getting so much flack lately when I modify someone elses submission and then get all the points.

~IM
Thanks for the consideration, Idle_Mind.  I appreciate it!  Nice bit of coding too.  It makes the routine much more functional.
BlueDevilFan, I thank you also.
Guys, I hope you will be available for help with this function in the future
Feel free to post here if you need additional help with it.

~IM
Use Filter() function - it returns array of matches:

        Dim matches() As String, nummatches As Long, i As Long
        matches = Filter(Ebook, Text1.Text, True, vbTextCompare)
        nummatches = UBound(matches) + 1
        Caption = nummatches & " matches found"
        For i = 0 To nummatches - 1
            Debug.Print matches(i)
        Next
Your code is definitely more compact ameba!

The only drawback I see is that if you have someting huge, like the entire bible as in this case, then your matches could take up a lot of memory (possible doubling the amount of memory used if you searched for something silly like "a").  With the other approach presented here you don't use any additional memory and you can still find the next match.

~IM
Also the filter function can only search for one value (or more than one value but they all have to be together in an exact match like a phrase).

You can do multiple Filter calls but then you may have duplicate results.

~IM
mcdonald_g,

One last version of my "search engine".  Create a new project and add a TextBox, CommandButton, CheckBox and a Label.  If the CheckBox is checked then the verse must have all of the words in the search criteria to be a match.  If the CheckBox is unchecked then the verse only needs one of the words in the search criteria to be a match.

~IM

Option Explicit
Option Base 1     'Because I hate 0 based arrays
   
Private verseIndex As Long
Private Ebook As Variant

Private Sub Form_Load()
    ReDim Ebook(5)
    Ebook(1) = "Genesis 1:1 In the beginning God created the heaven and the earth."
    Ebook(2) = "Genesis 1:2 Now the earth was unformed and void, and darkness was upon the face of the deep; and the spirit of God hovered over the face of the waters."
    Ebook(3) = "Genesis 1:3 And God said: 'Let there be light.' And there was light."
    Ebook(4) = "Genesis 1:4 And God saw the light, that it was good; and God divided the light from the darkness."
    Ebook(5) = "Genesis 1:5 And God called the light Day, and the darkness He called Night. And there was evening and there was morning, one day."
   
    verseIndex = 0
    Text1.Text = "light"
    Label1.Caption = ""
    Command1.Caption = "Search"
    Check1.Caption = "Must contain all values"
End Sub
   
Private Sub Text1_Change()
    verseIndex = 0
    Command1.Caption = "Search"
End Sub

Private Sub Check1_Click()
    verseIndex = 0
    Command1.Caption = "Search"
End Sub
   
Private Sub Command1_Click()
    If Text1.Text <> "" Then
        verseIndex = SearchArray(Text1.Text, verseIndex)
        Select Case verseIndex
            Case 0
                Label1.Caption = "[No Match Found]"
                Command1.Caption = "Search"
                MsgBox "No Match Found", vbInformation, "Search"
               
            Case Is > 0
                Label1.Caption = Ebook(verseIndex)
                Command1.Caption = "Find Next"
               
            Case -1
                verseIndex = 0
                Command1.Caption = "Search Again"
                MsgBox "Last Verse Reached", vbInformation, "Search"
               
        End Select
    End If
End Sub

Private Function SearchArray(ByVal strSearchText As String, ByVal lastIndex As Long) As Long
    Dim intItem As Long
    Dim intWord As Byte
    Dim words As Variant
    Dim curVerse As String
    Dim allWords As Boolean
   
    words = Split(strSearchText, " ")
    For intItem = (lastIndex + 1) To UBound(Ebook)
        curVerse = Ebook(intItem)
        allWords = True
        For intWord = LBound(words) To UBound(words)
            If InStr(1, curVerse, words(intWord), vbTextCompare) > 0 Then
                If Check1.Value = vbUnchecked Then
                    SearchArray = intItem
                    Exit Function
                End If
            Else
                allWords = False
                If Check1.Value = vbChecked Then
                    Exit For
                End If
            End If
        Next
        If Check1.Value = vbChecked And allWords = True Then
            SearchArray = intItem
            Exit Function
        End If
    Next
    If lastIndex > 0 Then
        SearchArray = -1
    End If
End Function
Hi IM,
It still doesn't return number of matches... hehe :)

For multiple words, Filter can be executed for the first word, then result array is filtered for the second, it isn't bad at all, it is one of efficient string functions introduced in VB6 (like Replace, Join...).
And it is easy to implement "does not contain" syntax: "light -darkness".

VB's Instr is not very efficient, to find only if there is a match, without knowing position, Like operator is faster.
mcdonald_g,
Is there a download of complete text you are using?
True, my algorithm won't give you the number of matches.

I need to use "Like" more often.  I alway forget it's there.

For finding strings that contain all of the words, I can see how using the Filter in a loop would be nice.

For finding strings that contain any of the words, I suppose your could use a collection and place the resultings strings in it using the string as the key and value to remove duplicates.

The whole memory usage thing still bothers me a little though.  =)

~IM
I like both approaches as they each have their own benefits and depending on the application, one approach may be better suited than the other.

My approach only returns the next match found but doesn't keep additional copies of the verses in memory.  The code is longer and more complicated.

Using amebas suggested filter approach, you will know how many matches there are and can display all of them at once, but all of the matches found will be copied to a seperate array using more memory (this isn't necessarily a problem, just a drawback in my opinion).  Implementing a search to include all words will look elegant in a For...Next loop that applies the filter repeatedly for each word in the criteria.  Implementing a search to include any of the words will be a little messier though if you want to remove duplicate strings found.

In this case, it would probably make sense to be able to view all of the matches at once.  Given the size of the dataset and the possibility of a large number of matches though, memory consumption is still a concern for me.  Under perfect conditions, I would store the verses in a database and use queries to find matches.  The search may take a little longer but it I think memory would be better utilized.

~IM
Guys I know this question was posted months ago, however I'm still working on the project in which comments are posted.  I have the Bible stored in an Access database utilizing structured tables and fields. I need a super-fast way to search the database or memory (if the database is stored in an array). All positive return matched should be posted in an listbox.  I can post more points again if requested.  I hope you responed to this request.  I can email a copy of the DB if needed.