Small vector include (dynamic array)

Share your advanced PureBasic knowledge/code with the community.
milan1612
Addict
Addict
Posts: 894
Joined: Thu Apr 05, 2007 12:15 am
Location: Nuremberg, Germany
Contact:

Small vector include (dynamic array)

Post by milan1612 »

As I recently needed a dynamically resizing array inside a dynamically allocated structure, I wrote
a small include that does this job in a generic way, so I can reuse it when I need that again.

Here's the code (note that there is a brief documentation on top of it):

Code: Select all

; Vector library - a simple dynamic array
; Requires Purebasic 4.4x or higher

; Copyright (c) 2009 Milan Schoemig <milan1612@gmx.de>

; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:

; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.

; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
EnableExplicit

; NOTE: If debugging is enabled during compilation, this vector libray will
; check your parameters every time you call one of the procedures. If an error
; is detected, a debug message will show up guarding you to the procedure
; that caused the error. If debugging is disabled, all checks are disabled
; to increase the overall performance.
; 

; DOCUMENTATION

; Vector_New(element_size, initial_size = 0)
;     Creates a new vector object and returns a pointer to it.
;
;     element_size is the size in bytes of the size of one element
;
;     initial_size (if set) causes the vector to initialize enough
;     memory, to hold initial_size of elements. If you know that you
;     will add a certain number of elements, use of this parameter
;     can greatly improve performance during insertions.
; 
; Vector_Delete(*vector._Vector)
;     Deletes the vector object and deallocates all associated memory.
;
;     Note: If you use the vector to store pointers to memory you
;     allocated yourself, you must free it prior to calling this method!
;
; Vector_Size(*vector._Vector)
;     Returns the number of elements hold inside the vector object.
;
; Vector_Reserve(*vector._Vector, size)
;     Causes the vector object to allocate enough memory to hold
;     size numbers of elements.
;
;     size is the number of elements you want the vector to reserve space for.
; 
; Vector_Clear(*vector._Vector)
;     Removes all elements inside the vector object.
;
;     Note: The vector may or may not deallocate reserved memory. So
;     don't rely on this function if you want to release all the memory.
; 
; Vector_Add(*vector._Vector, *data_ptr)
;     Adds the specified element to the vector.
;     
;     *data_ptr is a pointer to some memory where your element is located.
;
;     Note: This function actually copies the memory. It does not just
;     store the pointer to it.
; 
; Vector_Remove(*vector._Vector, index)
;     Removes the element of the vector at the specified index.
;     
;     index is - well - the index to the element you want to remove.
;
;     Note: index is zero-based!
; 
; Vector_Get(*vector._Vector, index)
;     Returns a pointer to the element at the specified index of the vector.
;     
;     index is the index to the element you want to get.
;
;     Note: index is zero-based!
; 
; Vector_Set(*vector._Vector, index, *data_ptr)
;     Sets the element at the specified index of the vector to the
;     value you provide with *data_ptr.
;     
;     index is the index to the element you want to set.
;
;     *data_ptr is a pointer to the memory location of your data.
;     
;     Note: This function copies the data from your location to the vector's memory.
; 
; Vector_ForEach(*vector._Vector, *user_data, *ProcAddr._ForEachProc)
;     This function calls the procedure you provide with *ProcAddr for
;     each element that is inside of the vector.
;     
;     *user_data is a variable for you to provide your for-each-procedure with custom
;     data that is passed to every call to the procedure.
;     
;     *ProcAddr is a pointer to a procedure with the following signature:
;      Procedure procname(*data_ptr, index, *user_data) - where *data_ptr is a pointer
;      to the data of the current element, index is the current element's index and *user_data
;      is the variable you passed to Vector_ForEach
;
;     Note: In your procedure, you need to return 1 if you want to proceed with the enumeration.
;     Returning 0 causes Vector_ForEach to stop calling your procedure.

Structure _Vector
  *data_ptr
  size.i
  capacity.i
  element_size.i
EndStructure

Prototype _ForEachProc(*data_ptr, index, *user_data)

DeclareDLL Vector_New(element_size, initial_size = 0)
DeclareDLL Vector_Delete(*vector._Vector)
DeclareDLL Vector_Size(*vector._Vector)
DeclareDLL Vector_Reserve(*vector._Vector, size)
DeclareDLL Vector_Clear(*vector._Vector)

DeclareDLL Vector_Add(*vector._Vector, *data_ptr)
DeclareDLL Vector_Remove(*vector._Vector, index)
DeclareDLL Vector_Get(*vector._Vector, index)
DeclareDLL Vector_Set(*vector._Vector, index, *data_ptr)

DeclareDLL Vector_ForEach(*vector._Vector, *user_data, *ProcAddr._ForEachProc)

Macro _FailIf(Cond)
  CompilerIf #PB_Compiler_Debugger
    If (cond)
      Debug #PB_Compiler_Procedure + " failed, check your parameters."
      ProcedureReturn 0
    EndIf
  CompilerEndIf
EndMacro

ProcedureDLL Vector_New(element_size, initial_size = 0)
  Protected *vector._Vector = 0
  _FailIf(element_size <= 0 Or initial_size < 0)

  *vector = AllocateMemory(SizeOf(_Vector))
  *vector\element_size = element_size
  If initial_size
    *vector\data_ptr = AllocateMemory(initial_size * element_size)
    *vector\capacity = initial_size
  EndIf

  ProcedureReturn *vector
EndProcedure

ProcedureDLL Vector_Delete(*vector._Vector)
  _FailIf(*vector <= 0)
  
  FreeMemory(*vector\data_ptr)
  FreeMemory(*vector)
  
  ProcedureReturn 1
EndProcedure

ProcedureDLL Vector_Size(*vector._Vector)
  _FailIf(*vector <= 0)
  
  ProcedureReturn *vector\size
EndProcedure

ProcedureDLL Vector_Reserve(*vector._Vector, size)
  _FailIf(*vector <= 0 Or size <= *vector\size)
  
  If *vector\size = 0 And *vector\capacity = 0
    *vector\data_ptr = AllocateMemory(size * *vector\element_size)
    *vector\capacity = size
  ElseIf *vector\capacity < size
    *vector\data_ptr = ReAllocateMemory(*vector\data_ptr, size * *vector\element_size)
    *vector\capacity = size
  EndIf
  
  ProcedureReturn 1
EndProcedure

ProcedureDLL Vector_Clear(*vector._Vector)
  _FailIf(*vector <= 0)
  
  If *vector\size
    FillMemory(*vector\data_ptr, *vector\size * *vector\element_size)
    *vector\size = 0
  EndIf
  
  ProcedureReturn 1
EndProcedure

ProcedureDLL Vector_Add(*vector._Vector, *data_ptr)
  _FailIf(*vector <= 0 Or *data_ptr <= 0)
  
  If *vector\size = *vector\capacity
    Vector_Reserve(*vector, Int(*vector\capacity * 1.5) + 1)
  EndIf
  CopyMemory(*data_ptr, *vector\data_ptr + (*vector\element_size * *vector\size), *vector\element_size)
  *vector\size + 1
  
  ProcedureReturn *vector\size
EndProcedure

ProcedureDLL Vector_Remove(*vector._Vector, index)
  _FailIf(*vector <= 0 Or index < 0 Or index >= *vector\size)
  
  If index = 0
    If *vector\size > 1
      CopyMemory(*vector\data_ptr + *vector\element_size, *vector\data_ptr, (*vector\size - 1) * *vector\element_size)
      *vector\size - 1
      FillMemory(*vector\data_ptr + (*vector\size * *vector\element_size), *vector\element_size)
    Else
      FillMemory(*vector\data_ptr, *vector\element_size)
      *vector\size = 0
    EndIf
  ElseIf index = *vector\size - 1
    *vector\size - 1
    FillMemory(*vector\data_ptr + (*vector\size * *vector\element_size), *vector\element_size)
  Else
    Protected *destination = *vector\data_ptr + (index * *vector\element_size)
    Protected *source = *vector\data_ptr + ((index + 1) * *vector\element_size)
    CopyMemory(*source, *destination, (*vector\size - index) * *vector\element_size)
    *vector\size - 1
    FillMemory(*vector\data_ptr + (*vector\size * *vector\element_size), *vector\element_size)
  EndIf
EndProcedure

ProcedureDLL Vector_Get(*vector._Vector, index)
  _FailIf(*vector <= 0 Or index < 0 Or index >= *vector\size)
  
  ProcedureReturn *vector\data_ptr + (*vector\element_size * index)
EndProcedure

ProcedureDLL Vector_Set(*vector._Vector, index, *data_ptr)
  _FailIf(*vector <= 0 Or index < 0 Or index >= *vector\size Or *data_ptr <= 0)
  
  CopyMemory(*data_ptr, *vector\data_ptr + (*vector\element_size * index), *vector\element_size)
  
  ProcedureReturn 1
EndProcedure

ProcedureDLL Vector_ForEach(*vector._Vector, *user_data, *ProcAddr._ForEachProc)
  _FailIf(*vector <= 0 Or *ProcAddr <= 0)
  
  Protected i, ret
  While i < *vector\size
    ret = *ProcAddr(Vector_Get(*vector, i), i, *user_data)
    If ret = 0
      Break
    EndIf
    i + 1
  Wend
  
  ProcedureReturn 1
EndProcedure
Here's an example to show you how to use it:

Code: Select all

EnableExplicit
XIncludeFile "Vector.pbi"

; Create a vector holding integer elements
Define vector = Vector_New(SizeOf(Integer))

; Populate the vector with all numbers from 1 to 20
Define value, i
For i = 1 To 20
  value = i
  Vector_Add(vector, @value)
Next

; Print all inserted elements
Debug "All elements:"
For i = 0 To Vector_Size(vector) - 1
  Debug PeekI(Vector_Get(vector, i))
Next

Debug ""

; Our procedure for Vector_ForEach
Procedure PrintEven(*ptr.Integer, index, *user_data)
  ; Print all even values
  If *ptr\i % 2 = 0
    Debug *ptr\i
  EndIf
  
  ; Return 1 if you want to proceed with the enumeration
  ProcedureReturn 1
EndProcedure

; Enumerate all elements with the foreach helper
Debug "Even elements:"
Vector_ForEach(vector, 0, @PrintEven())

; Delete the vector
Vector_Delete(vector)
I hope someone else will find it useful. As a side note, I tested it with the project I'm working
on, but if nevertheless you find any bugs or crashes, please report them.
Windows 7 & PureBasic 4.4
Little John
Addict
Addict
Posts: 4791
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Small vector include (dynamic array)

Post by Little John »

Hi,

I haven't tested it yet, but it looks very useful. Thanks for sharing!

Regards, Little John
User avatar
Demivec
Addict
Addict
Posts: 4270
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

larger vector include (dynamic array)

Post by Demivec »

It looks great. :D

I have also written an include for using vectors that I am in the process of finishing up. I wrote it for use in a personal project also. It takes a slightly different approach than yours. It is functional and works great but I want to add one last feature to it before it will be ready for posting also.

Yours is awesome because it's lean and mean. :wink:

The Vector_ForEach() idea is great as well. I was searching for a way to implement something similar. I'm going to make careful note of what you've presented.
Post Reply