[PB4] Arbre de données avec LinkedLink2

Partagez votre expérience de PureBasic avec les autres utilisateurs.
Avatar de l’utilisateur
Flype
Messages : 2431
Inscription : jeu. 29/janv./2004 0:26
Localisation : Nantes

[PB4] Arbre de données avec LinkedLink2

Message par Flype »

Les listes chainées de purebasic sont très pratiques surtout lorsqu'on les utilisent avec des structures.

Ceci dit elles sont limitées à une profondeur de 1.

On ne peut pas construire un arbre avec.
Les arbres ressemblent aux listes chainées mais peuvent avoir une profondeur infinie.
C'est à dire qu'on a des listes dans des listes.

Un des meilleurs examples est l'exploration d'un dossier comme C:\
ou encore la lecture d'un fichier XML, ou l'analyse hiérarchique d'une base de données...

Donc voici en préambule à un example d'utilisation qui va suivre juste derrière une implémentation très simple / très proche du concept purebasic d'une liste chainée imbricable.

LinkedList2.pbi

Code : Tout sélectionner

;---------------------------------------
; Object:  LinkedList2
; Version: Version 0.2
; Author:  Flype (flype44@hotmail.fr)
; Date:    Mar 2006
; Requir : Purebasic 4.0
;---------------------------------------


; MiniSet.pbi

Global NewList set.l()

Procedure.l Push(value.l)
  If AddElement(set())
    set() = value
  EndIf
EndProcedure
Procedure.l Pop(value.l)
  ForEach set()
    If set() = value
      DeleteElement(set())
    EndIf
  Next
EndProcedure
Procedure.l NotIn(value.l)
  ForEach set()
    If set() = value
      ProcedureReturn #False
    EndIf
  Next
EndProcedure

; LinkedList2.pbi

Structure LL
  *nextLL
  *prevLL
  nByte.l
  nElement.l
  userdata.l
EndStructure

Macro NewTree(STRUCTNAME)
  LL_Alloc(SizeOf(STRUCTNAME))
EndMacro

Macro NewList2(STRUCTNAME)
  LL_Alloc(SizeOf(STRUCTNAME))
EndMacro

Macro Assert2(element,func)
  If element = -1
    Debug "[" + func + "] List is empty"
  ElseIf element = -2
    Debug "[" + func + "] Out of bounds"
  ElseIf element = #Null
    Debug "[" + func + "] Pointer is null"
    End
  ElseIf NotIn(element)
    Debug "[" + func + "] Not a valid list"
    End
  EndIf
EndMacro

Procedure.l LL_Alloc(nByte.l=SizeOf(LL))
  *list.LL = AllocateMemory(nByte)
  Push(*list)
  Assert2(*list,"NewList2")
  *list\nByte = nByte
  ProcedureReturn *list
EndProcedure
Procedure.l LL_Free(*element.LL)
  Assert2(*element,"LL_Free")
  ZeroMemory_(*element,*element\nByte)
  FreeMemory(*element)
  Pop(*element)
EndProcedure
Procedure.l LL_Link(*element1.LL,*element2.LL)
  Assert2(*element1,"LL_Attach")
  *element1\nextLL = *element2
  If *element2
    *element2\prevLL = *element1
  EndIf
EndProcedure

Procedure.l CountList2(*list.LL)
  Assert2(*list,"CountList2")
  ProcedureReturn *list\nElement
EndProcedure
Procedure.l FirstElement2(*list.LL)
  Assert2(*list,"FirstElement2")
  If *list\nextLL
    ProcedureReturn *list\nextLL
  EndIf
  Assert2(-1,"FirstElement2")
EndProcedure
Procedure.l LastElement2(*list.LL)
  Assert2(*list,"LastElement2")
  While *list
    If *list\nextLL = #Null
      ProcedureReturn *list
    EndIf
    *list = *list\nextLL
  Wend
  Assert2(-1,"LastElement2")
EndProcedure
Procedure.l AddElement2(*list.LL,position.l=-1)
  Assert2(*list,"AddElement2")
  *this.LL = LL_Alloc(*list\nByte)
  Assert2(*this,"AddElement2")
  *last.LL = LastElement2(*list)
  *list\nElement + 1
  If *last
    *this\prevLL = *last
    *last\nextLL = *this
  Else
    *this\prevLL = *list
  EndIf
  ProcedureReturn *this
EndProcedure
Procedure.l ClearList2(*list.LL)
  Assert2(*list,"ClearList2")
  *list\nElement = 0
  *list = FirstElement2(*list)
  While *list
    *this = *list
    *list = *list\nextLL
    LL_Free(*this)
  Wend
EndProcedure
Procedure.l DeleteList2(*list.LL)
  Assert2(*list,"DeleteList2")
  ClearList2(*list)
  LL_Free(*list)
EndProcedure
Procedure.l DeleteElement2(*list.LL,position.l)
  Assert2(*list,"DeleteElement2")
  *this.LL = FirstElement2(*list)
  While *this
    If i = position
      LL_Link(*this\prevLL,*this\nextLL)
      LL_Free(*this)
      *list\nElement - 1
      ProcedureReturn
    EndIf
    *this = *this\nextLL
    i + 1
  Wend
  Assert2(-2,"DeleteElement2")
EndProcedure
Procedure.l NextElement2(*list.LL)
  Assert2(*list,"NextElement2")
  ProcedureReturn *list\nextLL
EndProcedure
Procedure.l PreviousElement2(*list.LL)
  Assert2(*list,"PreviousElement2")
  *prev.LL = *list\prevLL
  If *prev
    If *prev\prevLL = #Null
      ProcedureReturn #Null
    EndIf
  EndIf
  ProcedureReturn *prev
EndProcedure
Procedure.l SwapElement2(*element1.LL,*element2.LL)
  Assert2(*element1,"SwapElement2")
  Assert2(*element2,"SwapElement2")
  *prev1 = *element1\prevLL
  *next1 = *element1\nextLL
  *element1\nextLL = *element2\prevLL
  *element1\nextLL = *element2\nextLL
  *element2\prevLL = *prev1
  *element2\nextLL = *next1
EndProcedure
Procedure.l SelectElement2(*list.LL,position.l)
  Assert2(*list,"SelectElement2")
  *list = FirstElement2(*list)
  While *list
    If i = position
      ProcedureReturn *list
    EndIf
    *list = *list\nextLL
    i + 1
  Wend
  Assert2(-2,"SelectElement2")
EndProcedure

;---------------------------------------
EDIT
Correctif: Ligne 7 Assert2(-1,"FirstElement2") au lieu de Assert2(#False,"FirstElement2")
Nouvel alias: NewTree(), même chose que NewList2()
Dernière modification par Flype le ven. 31/mars/2006 1:30, modifié 4 fois.
Image
Avatar de l’utilisateur
Flype
Messages : 2431
Inscription : jeu. 29/janv./2004 0:26
Localisation : Nantes

Message par Flype »

Et un example d'utilisation qui analyse la structure des données d'une connexion ODBC.

Le résultat de l'analyse est stocké dans un arbre en mémoire.

Ensuite cette arbre est injecté dans un TreeGadget() à titre d'example.

N'oublier pas de changer vos parametres de connexion à la ligne 115 ( connexion, utilisateur, mot de passe).

Code : Tout sélectionner

;------------------------------------------------------------------------

IncludeFile "LinkedList2.pb"

;------------------------------------------------------------------------

Structure DATABASE Extends LL
  name.s
  *tables
EndStructure
Structure TABLE Extends LL
  name.s
  *fields
EndStructure
Structure FIELD Extends LL
  name.s
  type.s
  null.s
  key.s
  def.s
  extra.s
EndStructure

;------------------------------------------------------------------------

Procedure.l ParseDatabase(name.s,user.s,password.s)
  
  ; Parse Databases
  
  dbDB = OpenDatabase(#PB_Any,name,user,password)
  
  If dbDB
    
    If DatabaseQuery(dbDB,"SHOW DATABASES")
      
      *conn.DATABASE = NewList2(DATABASE)
      
      While NextDatabaseRow(dbDB)
        
        *db.DATABASE = AddElement2(*conn)
        *db\name = GetDatabaseString(dbDB,0)
        *db\tables = NewList2(TABLE)
        
        ; Parse Tables
        
        dbTable = OpenDatabase(#PB_Any,name,user,password)
        
        If dbTable
          
          If DatabaseQuery(dbTable,"SHOW TABLES FROM " + *db\name)
            
            While NextDatabaseRow(dbTable)
              
              *table.TABLE = AddElement2(*db\tables)
              *table\name = GetDatabaseString(dbTable,0)
              *table\fields = NewList2(FIELD)
              
              ; Parse Fields
              
              dbField = OpenDatabase(#PB_Any,name,user,password)
              
              If dbField
                
                If DatabaseQuery(dbField,"DESCRIBE " + *db\name + "." + *table\name)
                  
                  While NextDatabaseRow(dbField)
                    
                    *field.FIELD = AddElement2(*table\fields)
                    *field\name  = GetDatabaseString(dbField,0)
                    *field\type  = GetDatabaseString(dbField,1)
                    *field\null  = GetDatabaseString(dbField,2)
                    *field\key   = GetDatabaseString(dbField,3)
                    *field\def   = GetDatabaseString(dbField,4)
                    *field\extra = GetDatabaseString(dbField,5)
                    
                  Wend
                  
                EndIf
                
                CloseDatabase(dbField)
                
              EndIf
              
            Wend
            
          EndIf
          
          CloseDatabase(dbTable)
          
        EndIf
        
      Wend
      
    EndIf
    
    CloseDatabase(dbDB)
    
  EndIf
  
  ProcedureReturn *conn
  
EndProcedure

;------------------------------------------------------------------------

If InitDatabase() = #False
  MessageRequester("Error","InitDatabase() failed",#True):End
EndIf

If OpenWindow(0,50,50,640,480,"ParseDatabase()") And CreateGadgetList(WindowID(0))
  
  TreeGadget(0,10,10,620,460)
  
  *root = ParseDatabase("test","guest","")
  
  If *root
    *db.DATABASE = FirstElement2(*root)
    While *db ; parse databases
      AddGadgetItem(0,-1,*db\name,0,0)
      *table.TABLE = FirstElement2(*db\tables)
      While *table ; parse tables
        AddGadgetItem(0,-1,*table\name,0,1)
        *field.FIELD = FirstElement2(*table\fields)
        While *field ; parse fields
          AddGadgetItem(0,-1,*field\name,0,2)
          AddGadgetItem(0,-1,*field\type,0,3)
          AddGadgetItem(0,-1,*field\def,0,3)
          AddGadgetItem(0,-1,*field\null,0,3)
          AddGadgetItem(0,-1,*field\key,0,3)
          AddGadgetItem(0,-1,*field\extra,0,3)
          *field = NextElement2(*field)
        Wend
        *table = NextElement2(*table)
      Wend
      *db = NextElement2(*db)
    Wend
  EndIf
  
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
  
EndIf

;------------------------------------------------------------------------
Image
comtois
Messages : 5186
Inscription : mer. 21/janv./2004 17:48
Contact :

Message par comtois »

Je vais regarder tout ça plus attentivement ce week-end , ça me semble fort intéressant :)
http://purebasic.developpez.com/
Je ne réponds à aucune question technique en PV, utilisez le forum, il est fait pour ça, et la réponse peut profiter à tous.
Dr. Dri
Messages : 2527
Inscription : ven. 23/janv./2004 18:10

Message par Dr. Dri »

c'était pas faisable avec la lib vector de Lio ?
en tout cas c'est du boulot!

Dri
Avatar de l’utilisateur
Droopy
Messages : 1151
Inscription : lun. 19/juil./2004 22:31

Message par Droopy »

Ce code m'interesse, mais aurais-tu un autre exemple car je n'ai pas de bdd sous la main ?
Dorian
Messages : 489
Inscription : mar. 15/mars/2005 15:33

Message par Dorian »

Droopy a écrit :Ce code m'interesse, mais aurais-tu un autre exemple car je n'ai pas de bdd sous la main ?
Idem ^^
Avatar de l’utilisateur
Flype
Messages : 2431
Inscription : jeu. 29/janv./2004 0:26
Localisation : Nantes

Message par Flype »

Yep, je peux faire un petit explorateur de dossier tout simplement.
c'est pareil on obtient un arbre de données.

et oui je pense qu'il doit être possible de faire çà avec la lib vector.
mais j'avais besoin de faire mon jeu de fonctions le plus proche possible des fonctions purebasic. Au moins je n'y perd pas mon latin et peut être meme que si c'est convaincant çà pourrait plaire à fred. J'aimerais effectivement voir un jour les arbres en natifs dans Pure. C'est quand meme, comme on dit, une structure de données importante au même titre que les listes, les tableaux, les structures...

ps:
il y a encore des choses à faire pour améliorer les fonctionnalités.
je n'ai pas encore fait 'InsertElement()'. C'est AddElement2(*list.LL,position.l=-1) mais position n'est pas géré pour le moment.
Image
Avatar de l’utilisateur
Flype
Messages : 2431
Inscription : jeu. 29/janv./2004 0:26
Localisation : Nantes

Message par Flype »

MAJ du source 'LinkedList2.pb' (voir 1er post)

Et voilà l'exemple de parcours d'un dossier.

Donc là pareil dans le meme esprit,
On charge d'abord les données dans un arbre grâce à ParseDirectory().
Ensuite l'arbre est utilisé pour remplir le TreeGadget() grâce à ParseTree().

PS: Ce code PB4 semble mettre en avant un bug avec TreeGadget().
J'ai déjà notifié çà sur le forum anglais.
On obtient un Invalid access memory au delà d'un millier ou plus d'items dans le TreeGadget().
Du coup, si vous mettez ParseDirectory(0,"C:\") çà plante à coup sûr chez moi.

Code : Tout sélectionner

;------------------------------------------------------------------------

IncludeFile "LinkedList2.pb"

;------------------------------------------------------------------------

Structure FILE Extends LL
  name.s
  size.d
  *sub
EndStructure

;------------------------------------------------------------------------

Procedure ParseDirectory(id, folder.s) 
  
  If Right(folder, 1) <> "\"
     folder + "\"
  EndIf
  
  If ExamineDirectory(id, folder, "*.*")
    
    *root.FILE = NewTree(FILE)
    
    While NextDirectoryEntry(id) 
      
      If DirectoryEntryName(id) <> "." And DirectoryEntryName(id) <> ".."
        
        *a.FILE = AddElement2(*root)
        *a\name = DirectoryEntryName(id)
        
        If DirectoryEntryType(id) = #PB_DirectoryEntry_Directory
          *a\sub = ParseDirectory(id+1, folder+DirectoryEntryName(id))
        EndIf
        
      EndIf
      
    Wend 
    
    FinishDirectory(id)
    
  EndIf 
  
  ProcedureReturn *root
  
EndProcedure

Procedure ParseTree(gadget.l, depth.l, *root.FILE) 
  
  If *root
    
    *file.FILE = FirstElement2(*root)
    
    While *file
      
      AddGadgetItem(0, -1, *file\name, #Null, depth)
      
      If *file
        ParseTree(gadget, depth+1, *file\sub) 
      EndIf
      
      *file = NextElement2(*file)
      
    Wend
    
  EndIf
  
EndProcedure

If OpenWindow(0, 50, 50, 640, 480,"ParseDatabase()") And CreateGadgetList(WindowID(0))
  
  TreeGadget(0, 10, 10, 620, 460)
  
  ParseTree(0, 0, ParseDirectory(0, GetEnvironmentVariable("USERPROFILE")) )
  
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
  
EndIf





Image
Avatar de l’utilisateur
Droopy
Messages : 1151
Inscription : lun. 19/juil./2004 22:31

Message par Droopy »

Merci beaucoup, je regarde cela ce soir avec interêt . :D
A+
Gillou
Messages : 373
Inscription : sam. 28/août/2004 17:35
Localisation : Bretagne, 22
Contact :

Message par Gillou »

Franchement génial, MERCI Flype
Avatar de l’utilisateur
Flype
Messages : 2431
Inscription : jeu. 29/janv./2004 0:26
Localisation : Nantes

Message par Flype »

j'en ai fait une UserLibrary pour PureBasic 4.0

voir le lien LinkedListEx Version 0.3 ici:
http://purebasic.forum-gratuit.com/view ... highlight=
Image
Avatar de l’utilisateur
Flype
Messages : 2431
Inscription : jeu. 29/janv./2004 0:26
Localisation : Nantes

Message par Flype »

pour répondre à une question qu'on m'a posée sur le forum anglais,
voici un example d'utilisation avec un fichier XML.

Parcours (récursif) de l'arbre XML grace à MSXML3 de PBOSL.
Stockage (récursif) dans une liste chainée avec la userlib LINKEDLISTEX.

Code : Tout sélectionner

; dépendance: MSXML3 (PBOSL)

Structure ENTRY Extends LINKEDLISTEX
  text.s
  *sub
EndStructure

Procedure.l ParseXml(oNode.l)
  
  If oNode
    
    *list = NewListEx( SizeOf(ENTRY) )
    
    If MSXML3_HasChildNodes(oNode) ; recursive only if childnodes
      
      oNodeList = MSXML3_SelectNodes(oNode, "entry")
      
      For i = 0 To MSXML3_NodeListGetLength(oNodeList) - 1
        
        oNode = MSXML3_NodeListGetItem(oNodeList, i)
        
        *item.ENTRY = AddElementEx(*list)
        *item\text  = MSXML3_SpecialGetNamedAttributeValue(oNode, "text")
        *item\sub   = ParseXml(oNode)
        
        MSXML3_ReleaseObject(oNode)
        
      Next
      
      MSXML3_ReleaseObject(oNodeList)
      
    EndIf
    
    ProcedureReturn *list
    
  EndIf
  
EndProcedure

Procedure ParseTree(*list, depth.l)
  
  If *list
    
    *item.ENTRY = FirstElementEx(*list)
    
    While *item
      
      Debug Space(depth * 4) + "ParseTree: " + *item\text
      
      ParseTree(*item\sub, depth + 1)
      
      *item = NextElementEx(*item)
      
    Wend
    
  EndIf
  
EndProcedure

XML$ + "<entry>"
XML$ + "  <entry text='1'>"
XML$ + "    <entry text='1.1'>"
XML$ + "      <entry text='1.1.1'/>"
XML$ + "      <entry text='1.1.2'/>"
XML$ + "    </entry>"
XML$ + "    <entry text='1.2'>"
XML$ + "      <entry text='1.2.1'/>"
XML$ + "      <entry text='1.2.2'/>"
XML$ + "    </entry>"
XML$ + "  </entry>"
XML$ + "  <entry text='2'>"
XML$ + "    <entry text='2.1'>"
XML$ + "      <entry text='2.1.1'/>"
XML$ + "      <entry text='2.1.2'/>"
XML$ + "    </entry>"
XML$ + "    <entry text='2.2'>"
XML$ + "      <entry text='2.2.1'/>"
XML$ + "      <entry text='2.2.2'/>"
XML$ + "    </entry>"
XML$ + "  </entry>"
XML$ + "</entry>"

oDoc = MSXML3_CreateDomDocument()

If oDoc And MSXML3_LoadXML(oDoc, XML$)
  ParseTree(ParseXml(MSXML3_SelectSingleNode(oDoc, "entry")), 0)
  MSXML3_ReleaseObject(oDoc)
EndIf
Image
Répondre