Page 1 of 1

Checking for Unique Elements in a List()

Posted: Wed Jul 10, 2024 3:36 pm
by blueb
The code below shows what I'm up to... but it seems like a convoluted way to check that the 'Primary Key' is unique.
Perhaps someone has a better way to have a 'Uniqueness' check when adding new items to Lists() :?:

Code: Select all

;Test

Structure TestStructure
  RowID.s
  Name.s
EndStructure

NewList mylist.TestStructure()

AddElement(mylist())
mylist()\RowID = "1"
mylist()\Name = "Anna"

AddElement(mylist())
mylist()\RowID = "2"
mylist()\Name = "Amy"

AddElement(mylist())
mylist()\RowID = "3"
mylist()\Name = "Ron"

AddElement(mylist())
mylist()\RowID = "4"
mylist()\Name = "Roger"
; ================================

;Add a new item, but check that the RowID is unique... 
; Same as a 'Primary Key' in SQLite

InsertNewRow.s = "2" ; RowID I want to add.

ResetList(mylist())        ; start at the top of the List()

While NextElement(mylist()); 
  If mylist()\RowID = InsertNewRow
     MessageRequester("", "'RowID' is not unique... abandoned the insert")
     Break ; returns the last elements information.
 Else 
    AddElement(mylist())
    mylist()\RowID = InsertNewRow
    mylist()\Name = "'Blueb' added"
 EndIf
 
Wend

Debug "At position "+ ListIndex(mylist()) +", the RowID is: "+ mylist()\RowID + " the name is: " + mylist()\Name

Re: Checking for Unique Elements in a List()

Posted: Wed Jul 10, 2024 3:52 pm
by skywalk
You could use a :memory: sqlite table and get this for free.
Or populate a Map() with your rowid. The Map() only allows 1 unique entry.

Re: Checking for Unique Elements in a List()

Posted: Wed Jul 10, 2024 3:58 pm
by SMaag
Use a Map() and a List() then you can use the Map() to index the List.

I guess there is anywehre in the PB Help a description for that. I remember it but at the moment I could not find it!

Re: Checking for Unique Elements in a List()

Posted: Wed Jul 10, 2024 4:02 pm
by netmaestro
You could use an index for the list that would consist of a Map() that would contain all current row IDs and nothing else.

Code: Select all

NewMap keys.i()

x = 1
keys(Str(x)) = 0 ; Do this before adding the element to the list

If FindMapElement(keys(), Str(x))
  Debug "Key "+Str(x) +" exists"
Else
  Debug "Key "+Str(x) +" is not found in the index" ; If you land here you know the key is unique
EndIf
Your map index will be compact and lightning fast.

Re: Checking for Unique Elements in a List()

Posted: Wed Jul 10, 2024 4:05 pm
by RASHAD
Hi

Code: Select all

ForEach mylist()
    If mylist()\RowID = InsertNewRow
      Debug "exist"
    EndIf
Next

Re: Checking for Unique Elements in a List()

Posted: Wed Jul 10, 2024 9:36 pm
by Demivec
Here's a way using the map idea:

Code: Select all

;Test
EnableExplicit

Structure TestStructure
  RowID.s
  Name.s
EndStructure

NewList mylist.TestStructure()
NewMap mylist_primaryKey()
#rowID_padsize = 5 ;how many digits in rowID including leading zeroes.

;Return pointer to existing element if RowID already exists or 0.
;If moveToExistingElement <> 0 then change current list element to existing element. 
Procedure.i elementExists(List mylist.TestStructure(), Map mylist_primaryKey(), rowID$, moveToExistingElement = #False)
  If FindMapElement(mylist_primaryKey(), rowID$) <> 0
    If moveToExistingElement
      ChangeCurrentElement(mylist(), mylist_primaryKey())
    EndIf
    ProcedureReturn mylist_primaryKey() ;RowID already exists
  Else
    ProcedureReturn  0 ;RowID does not exist
  EndIf
EndProcedure 

;Adds and returns pointer to new list element
Procedure.i addNewElement(List mylist.TestStructure(), Map mylist_primaryKey(), rowID$, name$)
  LastElement(mylist())
  AddElement(mylist())
  mylist()\RowID = rowID$
  mylist()\Name = name$
  
  mylist_primaryKey(rowID$) = @mylist()
  ProcedureReturn mylist_primaryKey()
EndProcedure 

;Remove list element with rowID and update primary key map
Procedure.i removeElement(List mylist.TestStructure(), Map mylist_primaryKey(), rowID$)
  If elementExists(mylist(), mylist_primaryKey(), rowID$, 1)
    DeleteMapElement(mylist_primaryKey())
    DeleteElement(mylist())
    ProcedureReturn #True ; element found and deleted
  EndIf
  
  ProcedureReturn #False ;no element deleted
EndProcedure 

Define i, InsertNewRow.s,
       nameGen_start.s="Anne,Ash,Bea,Beau,Bekke,Blaine,Blaire,Blake,Blue,Bree,Brooke,Bryce,Bryne,Cade,Cain,Chad,Chase,Claire," +
                       "Clay,Cole,Craig,Dean,Drake,Eve,Faith,Fern,Flynn,Gage,Grant,Greer,Guy,Hayes,Heath,Hope,Hugh,Jace,Jack," +
                       "Jade,Jai,Jake,James,Jay,Joel,John,Joy,Jude,June,Kai,Kate,Kent,Kurt,Kyle,Lane,Lark,Leif,Liv,Love,Luke," +
                       "Lynn,Mark,Max,May,Neve,Noor,Paige,Park,Paul,Pax,Pearl,Quinn,Rae,Rain,Reese,Reid,Rex,Rose,Rue,Ruth,Sage," +
                       "Sam,Seth,Shae,Shane,Skye,Sloane,Snow,Starr,Taj,Tate,Tess,Trace,Troy,True,Vale,Vance,Viv,Wren,Wyn,Zack",
       nameGen_start2.s="A,Aar,Aid,Al,An,Au,Bel,Bran,Bry,Ca,Car,Char,Chlo,Clar,Con,Coop,Cor,Dan,Dav,Dyl,E,Ed,El,Em,Ev,Gar,Hai," +
                        "Han,Harp,Haz,Hen,Hud,Hun,I,Is,Ja,Jack,Jo,Jor,Kay,Lan,Lau,Le,Li,Lil,Lo,Log,Lu,Ma,Mag,Mar,Mat,Me,Mi,Mol," +
                        "Na,Ni,No,Nor,O,Ol,Or,Pa,Pai,Par,Pey,Pi,Rea,Ri,Ro,Ru,Ry,Sa,Scar,Sky,So,Stel,Tay,Thom,Ty,Wil,Wy,Zo,",
       nameGen_end.s="ea,ah,am,an,as,att,belle,bert,brey,by,cas,chael,chid,cob,cole,dam,dan,den,die,dith,don,dra,drew,e,el," +
                     "elle,en,er,ery,ett,ey,gan,gie,gio,gory,ham,hew,ice,ick,id,ie,iel,ive,ker,la,lah,lar,las,leb,lei,ler," +
                     "let,ley,liam,lian,lo,lor,lotte,low,ly,lyn,ma,min,na,nah,nic,niel,nor,ny,on,ot,per,pher,phie,rah,ren," +
                     "rett,rine,ry,sa,saac,seph,sie,sley,son,stin,ter,tha,than,ton,trick,tumn,va,vi,vieve,wen,y,ya,zie", 
       newName.s

;Add a few names
For i = 1 To 100
  InsertNewRow.s = RSet(Str(i), #rowID_padsize, "0")
  If Random(2) > 1
    newName.s = StringField(nameGen_start, Random(CountString(nameGen_start, ",")) + 1, ",")
  Else
    newName.s = StringField(nameGen_start2, Random(CountString(nameGen_start2, ",")) + 1, ",") +
                StringField(nameGen_end, Random(CountString(nameGen_end, ",")) + 1, ",")
    If Random(1)
      newName + StringField(nameGen_end, Random(CountString(nameGen_end, ",")) + 1, ",")
    EndIf
  EndIf
  
  addNewElement(mylist(), mylist_primaryKey(), InsertNewRow, newName)
Next

SortStructuredList(mylist(), #PB_Sort_Ascending, OffsetOf(TestStructure\RowID), #PB_String)
Debug "**** List in row order"
ForEach mylist()
  Debug mylist()\RowID + ": " + mylist()\Name
Next

SortStructuredList(mylist(), #PB_Sort_Ascending, OffsetOf(TestStructure\Name), #PB_String)
Debug "**** List in name order"
ForEach mylist()
  Debug mylist()\RowID + ": " + mylist()\Name
Next

;delete a few rows
Define deleteRow.s, rowsDeleted = 0
For i = 1 To 20
  deleteRow = RSet(Str(Random(100)), #rowID_padsize, "0")
  If removeElement(mylist(), mylist_primaryKey(), deleteRow)
    rowsDeleted + 1
    Debug "'RowID " + deleteRow + "' has been removed."
  Else
    Debug "'RowID " + deleteRow + "' does not exist, abandoned the removal."
  EndIf
Next
Debug "**** Total of " + rowsDeleted + " row(s) were removed."

; ================================

;Add a new item, but check that the RowID is unique... 
; Same as a 'Primary Key' in SQLite
Define foundUniqueRowID = #False
Repeat
  InsertNewRow.s = RSet(Str(Random(MapSize(mylist_primaryKey()) + 1, 1)), #rowID_padsize, "0") ; RowID I want to add.
  If elementExists(mylist(), mylist_primaryKey (), InsertNewRow, 1) <> 0
    ;MessageRequester("", "'RowID' is not unique... abandoned the insert")
    Debug "'RowID " + InsertNewRow + "' is not unique... abandoned the insert"
    ;returns the last elements information
  Else
    foundUniqueRowID = #True
    addNewElement(mylist(), mylist_primaryKey(), InsertNewRow, "'Blueb' added")
  EndIf
  
  
  Debug "At position "+ ListIndex(mylist()) +", the RowID is: "+ mylist()\RowID + " the name is: " + mylist()\Name
Until foundUniqueRowID

SortStructuredList(mylist(), #PB_Sort_Ascending, OffsetOf(TestStructure\RowID), #PB_String)
Debug "**** List in row order"
ForEach mylist()
  Debug mylist()\RowID + ": " + mylist()\Name
Next

SortStructuredList(mylist(), #PB_Sort_Ascending, OffsetOf(TestStructure\Name), #PB_String)
Debug "**** List in name order"
ForEach mylist()
  Debug mylist()\RowID + ": " + mylist()\Name
Next
@Edit: added an orderly way to remove an element also., RASHAD. :)
@Edit: edited to correct syntax.
@Edit: edited to make a much more complex test example including fake name generation. An exercise left for the reader is to add a way of tracking available RowIDs instead of the random approach used here or even a simple incrementing RowID.

Re: Checking for Unique Elements in a List()

Posted: Wed Jul 10, 2024 11:18 pm
by RASHAD
Thanks Demivec
I don't want to be rude :D

Re: Checking for Unique Elements in a List()

Posted: Thu Jul 11, 2024 1:12 pm
by blueb
Yes I could use SQLite, but I've been using Droopy's Little Database, with modifications. I'd like to stick to using Lists()... this what Droopy's uses, and it's pure PureBasic. Besides it works very well for smaller projects.

I thought I could add one or two more items to improve the module... one being adding 'Primary Keys'. What I was doing (see my first post), and RASHAD's idea seems to be the simplest method, so far. I may look into using a MAP() with 'Primary Keys', but it kind of goes against what the module is all about.

Thanks for the posts. Any other 'Link-List' versions?

Re: Checking for Unique Elements in a List()

Posted: Thu Jul 11, 2024 2:11 pm
by NicTheQuick
There is no better way than iterating through the whole list to find the duplicate.
Even if the order of the list does not matter and you would sort it by its primary key, you would not be able to do a binary search.

That's why everyone here is suggesting the usage of Maps which are a built-in feature of Purebasic, or SQLite which has different strategies to create indices over a column of entries that might improve performance too.

Re: Checking for Unique Elements in a List()

Posted: Thu Jul 11, 2024 3:04 pm
by skywalk
If you must backup or save your settings or data at some point, it makes more sense to use SQLite.