Page 1 of 2

IsAnagram()

Posted: Mon Jul 25, 2011 1:46 pm
by ar-s
Hi,

For a little contest, i have made that procedure wich check if two stings are anagrams.

Code: Select all

;- IsAnagram(SourceString.s,DicoString.s)

Procedure.b IsAnagram(SourceString.s,DicoString.s)
  ; Libs ANAGRAMMES by Ar-S / 2011
  
  Protected.l NbrCarSource,NbrCarString,Trouve,cherche
  Protected.s LettreSource
  
  NbrCarSource = Len(SourceString)
  NbrCarDico = Len(DicoString)
  Trouve = 0
  
  If NbrCarSource <> NbrCarDico
    ProcedureReturn #False
    
  Else  
    For i = 1 To NbrCarSource
      LettreSource = Mid(SourceString,i,1)
      cherche = FindString(DicoString,LettreSource,1)
      
      If cherche = 0
        ProcedureReturn #False
      Else  
        Trouve + 1

        If Trouve = NbrCarSource
          ProcedureReturn #True
        Else
          DicoString = RemoveString(DicoString, LettreSource, 0, 1, 1)
        EndIf
        
      EndIf
    Next i
  EndIf
EndProcedure
Here is an example

Code: Select all

; IsAnagram() par Ar-S / 2011
; More usefull stuff in my Ar-S Lib : http://forumpb.ldvmultimedia.com/index.php?topic=22.msg40


If IsAnagram("PURE BASIC","CUBE SARIP")
  MessageRequester("Oh yeah","Both words are anagram")
Else
  MessageRequester("Nop !","There is no anagram")
EndIf
CyA

Re: IsAnagram()

Posted: Mon Jul 25, 2011 3:30 pm
by gnasen
looks interesting, but i really try to avoid string operations. I pretty often get some brain error induced bugs with them ;) You could do it like this with an array for example:

Code: Select all

Procedure isAnagram(string1.s,string2.s)
  
  Protected len.i = Len(string1)
  
  If len <> Len(string2)
    ProcedureReturn #False
  EndIf
  
  Dim char1.i(len-1)
  Dim char2.i(len-1)
  
  For a=0 To len-1
    char1(a) = PeekB(@string1+a)
    char2(a) = PeekB(@string2+a)
  Next
  
  SortArray(char1(),#PB_Sort_Ascending)
  SortArray(char2(),#PB_Sort_Ascending)
  
  For a=0 To len-1
    If char1(a) <> char2(a)
      ProcedureReturn #False
    EndIf
  Next
  
  ProcedureReturn #true
  
EndProcedure
By the way: Are anagrams case sensitiv?

Re: IsAnagram()

Posted: Mon Jul 25, 2011 3:43 pm
by ar-s
Hi, Thanks for your code,
For the case sensitiv question, it was only numbers in my contest, but it have to.
I simply use a Debug IsAnagram(LCase("EpURE"),LCase("UPREE")) for example.

Re: IsAnagram()

Posted: Mon Jul 25, 2011 5:57 pm
by Demivec
@Edit: removed code (posted due to light headedness :wink:)

Re: IsAnagram()

Posted: Mon Jul 25, 2011 6:10 pm
by eesau
Demivec, that doesn't do the same thing, yours checks for palindromes.

Re: IsAnagram()

Posted: Mon Jul 25, 2011 6:29 pm
by Demivec
eesau wrote:Demivec, that doesn't do the same thing, yours checks for palindromes.
Your correct, I realised my foolishness too late to correct the mistake before your posting. :wink:

Thanks for the heads-up anyway.

I've done some postings in this regard in the past here.

Re: IsAnagram()

Posted: Mon Jul 25, 2011 6:38 pm
by Danilo
What about this?

Code: Select all

CompilerIf #PB_Compiler_Unicode
    #numChars = $FFFF
CompilerElse
    #numChars = $FF
CompilerEndIf

Global Dim sourceChars.c( #numChars )
Global Dim destChars  .c( #numChars )

Procedure IsAnagram(SourceString.s,DicoString.s)
    Protected *source.Character = @SourceString
    Protected *dest.Character   = @DicoString

    Protected x.c = *source\c
    
    While x
        sourceChars(x) + 1
        *source+SizeOf(Character)
        x = *source\c
    Wend
    
    x.c = *dest\c

    While x
        destChars(x) + 1
        *dest+SizeOf(Character)
        x = *dest\c
    Wend
    
    retval = CompareMemory(@sourceChars(),@destChars(),#numChars)

    FillMemory(@sourceChars(), #numChars+1)
    FillMemory(@destChars()  , #numChars+1)

    ProcedureReturn retval
EndProcedure

Debug IsAnagram(LCase("EpURE"),LCase("UPREE"))
Debug IsAnagram("hello","world")
Debug IsAnagram("orwld","world")

If IsAnagram("PURE BASIC","CUBE SARIP")
  MessageRequester("Oh yeah","Both words are anagram")
Else
  MessageRequester("Nop !","There is no anagram")
EndIf
It just counts how often each (unicode-)character is in the strings
and compares the memory.

Re: IsAnagram()

Posted: Mon Jul 25, 2011 6:49 pm
by Demivec
@Danillo: Your code doesn't clear the global arrays. If you do a test for non-anagrams then all tests are unreliablel afterwards because they are adding to old tallys.

Here's a solution that is similar to Danilo's, though different:

Code: Select all

Procedure sortLettersInPlace(*word.Character, wordLength) ;sorts the letters of a word without creating a new string
  Protected Dim letters.c(wordLength)
  Protected *letAdr = @letters()
  
  CopyMemoryString(*word, @*letAdr)
  SortArray(letters(), #PB_Sort_Ascending, 0, wordLength - 1)
  CopyMemoryString(@letters(), @*word)
EndProcedure

Procedure isAnagram(SourceString.s, DicoString.s)
  sortLettersInPlace(@SourceString, Len(SourceString))
  sortLettersInPlace(@DicoString, Len(DicoString))
  If SourceString =DicoString
    ProcedureReturn #True
  EndIf
  ProcedureReturn #False
EndProcedure

If isAnagram("PURE BASIC","CUBE SARIP")
  MessageRequester("Yup!","Both words are anagrams.")
Else
  MessageRequester("Nop!", "Words are not anagrams.")
EndIf
It works for unicode or ascii and requires no big global arrays. :wink:

Re: IsAnagram()

Posted: Mon Jul 25, 2011 6:49 pm
by luis
Hi, another variant (ascii only obviously) without string operations.

Code: Select all

a$ = "pure basic"
b$ = "preuabic s"


Procedure.i IsAnagram(a$, b$)
 Protected l, i
 Protected *a.Character, *b.Character
 Protected Dim ac(256), Dim bc(256)
 
 l = Len(a$) : If l <> Len(b$) : ProcedureReturn 0 : EndIf
 
 *a = @a$
 *b = @b$
 
 For i = 1 To l
     ac(*a\c) + 1 : *a + 1 
     bc(*b\c) + 1 : *b + 1          
 Next
 
 ProcedureReturn CompareMemory(@ac(), @bc(), 256 * SizeOf(Integer))      
EndProcedure

Debug IsAnagram(a$, b$)

EDIT: oh, someone else already did something similar I see :)

Re: IsAnagram()

Posted: Mon Jul 25, 2011 7:00 pm
by helpy
Here is mine:

Code: Select all

EnableExplicit

Procedure IsAnagram( String1.s, String2.s )
	Protected *c1.Character, *c2.Character
	Protected len1, len2
	Protected bCharFound
	
	; RemoveSpaces
	String1 = RemoveString( String1, " " )
	String2 = RemoveString( String2, " " )
	
	; Test, if same amount of characters
	len1 = StringByteLength(String1)
	len2 = StringByteLength(String2)
	If len1 <> len2
		ProcedureReturn #False
	EndIf
	
	; ToLowerCase
	String1 = LCase(string1)
	String2 = LCase(String2)
	
	*c1 = @String1
	While *c1\c          ; Loop through String 1
		*c2 = @String2
		bCharFound = #False
		While *c2\c        ; Loop through String 2
			If *c2\c = *c1\c
				MoveMemory( *c2 + SizeOf(Character), *c2, len2 - (*c2 - @String2) )
				bCharFound = #True
				Break
			Else
				*c2 + SizeOf(Character)
			EndIf
		Wend
		If Not bCharFound
			Break
		EndIf
		*c1 + SizeOf(Character)
	Wend
	
	If Not *c1\c
		ProcedureReturn #True
	Else
		ProcedureReturn #False
	EndIf
EndProcedure

Macro TestAnagram( s1, s2 )
	If IsAnagram( s1, s2 )
		Debug "   Anagram: " + s1 + " :: " + s2
	Else
		Debug "No Anagram: " + s1 + " :: " + s2
	EndIf
EndMacro

TestAnagram( "PureBasic", "basic pure" )
TestAnagram( "PureBasic", "basic pure x" )
TestAnagram( "abcd", "xyzv" )
TestAnagram( "PureBasic", "au ei prbsc" )
Nice idea, Danilo :-)

cu, guido

Re: IsAnagram()

Posted: Mon Jul 25, 2011 7:03 pm
by Danilo
Demivec wrote:@Danillo: Your code doesn't clear the global arrays. If you do a test for non-anagrams then all tests are unreliablel afterwards because they are adding to old tallys.
Ooops! :oops:

Corrected, thank you!

Re: IsAnagram()

Posted: Mon Jul 25, 2011 7:08 pm
by RASHAD

Code: Select all



Procedure.b IsAnagram(SourceString.s,DicoString.s)
  Text$ = DicoString
  Text2$ = SourceString
  If Len(Text$) > Len(Text2$)
     mlen = Len(Text$)
  Else
     mlen = Len(Text2$)
  EndIf
  For i = 1 To mlen
    If Mid(DicoString,i,1) < Chr(65) Or (Mid(DicoString,i,1) > Chr(90) And Mid(DicoString,i,1) < Chr(97)) Or Mid(DicoString,i,1) > Chr(122)
       Text$ = RemoveString(Text$, Mid(DicoString,i,1))
    EndIf
    If Mid(SourceString,i,1) < Chr(65) Or (Mid(SourceString,i,1) > Chr(90) And Mid(SourceString,i,1) < Chr(97)) Or Mid(SourceString,i,1) > Chr(122)
       Text2$ = RemoveString(Text2$, Mid(SourceString,i,1))
    EndIf
  Next
        
  If Len(Text$) <> Len(Text2$)
    ProcedureReturn #False
  EndIf

  For i = 1 To Len(Text$)
   Text2$ = RemoveString(Text2$, Mid(Text$,i,1),0)
   ;Text2$ = RemoveString(Text2$, Mid(Text$,i,1),1)   For Case insensitive remove
  Next

  If Text2$ =""
      ProcedureReturn #True
  Else
      ProcedureReturn #False
  EndIf
EndProcedure


If IsAnagram("PURE BASIC","CUBE SAR!?IP")
  MessageRequester("Oh yeah","Both words are anagram")
Else
  MessageRequester("Nop !","There is no anagram")
EndIf

Edit : Updated

Re: IsAnagram()

Posted: Mon Jul 25, 2011 7:09 pm
by DarkDragon
Danilo wrote:It just counts how often each (unicode-)character is in the strings
and compares the memory.
I also thought about a character histogram.

[EDIT]
Ah sorry, I've just seen 256 as a constant somewhere and mixed it up with your code, but it was luis' ones.

@luis: 256 isn't safe ;-) . Danilo did it the correct way.

[EDIT2]
I like the idea with the sorting of the characters inside the string!

Re: IsAnagram()

Posted: Mon Jul 25, 2011 7:23 pm
by luis
@Darkdragon: why isn't safe ? Sorry I don't understand.

Not because it doesn't work with unicode I hope (since I wrote it in the post).

Anyway:

Code: Select all

Procedure.i IsAnagram(a$, b$)
 CompilerIf #PB_Compiler_Unicode  
  #anagram_counter_size = 65536   
 CompilerElse
  #anagram_counter_size = 256  
 CompilerEndIf          
 Protected *a.Character, *b.Character, l, i 
 Protected Dim ac(#anagram_counter_size - 1), Dim bc(#anagram_counter_size - 1)
 
 l = Len(a$) : If l <> Len(b$) : ProcedureReturn 0 : EndIf 
 *a = @a$ : *b = @b$ 
 For i = 1 To l     
     ac(*a\c) + 1 : *a + SizeOf(Character)
     bc(*b\c) + 1 : *b + SizeOf(Character)
 Next 
 ProcedureReturn CompareMemory(@ac(), @bc(), #anagram_counter_size * SizeOf(Integer)) 
EndProcedure


a$ = "pure basic"
b$ = "preuabic s"
Debug IsAnagram(a$, b$)
Should work right ? Quite compact too :)

EDIT: -1 in the Dim

Re: IsAnagram()

Posted: Mon Jul 25, 2011 7:42 pm
by helpy
Anagram on Wikipedia:

==> A decimal point = I'm a dot in place

This example shows, that before testing the strings, all none-letter characters (spaces, apostropes, dots, ...) should be removed!

This means that IsAnagram should depend on the alphabet of a language!

cu,
guido