HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Long-term Dynamic Arrays

01-25-2009, 05:34 AM#1
Ammorth
What are my options?

I need dynamic arrays that will be long term use (long-term meaning the life of a unit in a non unit slaughtering map). Creation/destruction does not have to be instant, and the arrays don't have to be re-sizable (although it would be cool if they were).

Don't want to use LinkedList since I need O(1) for read/write and don't plan to remove. I looked at DARY, but it's still pre-vJass and it talks about the arrays being meant for short-term use. DARY mentioned CSCache, but that is horribly out-dated. I could use Table, but not as clean as I would have hopped.

Any recommendations or suggestions?

edit: I created my own dynamic array library and so far, I am rather impressed by how simple it is. It does not do array re-sizing, nor does it initialize array values, but it allocates and destroys arrays with worse case O(n) complexity (where n is number of dynamic arrays). Reading and writing is O(1).

Collapse JASS:
library DynamicArrays initializer init
// note:  DataArray is used to store used/free space while MainArray is used for the dynamic array data.
// note2: a negative number in the Data array means the magnitude is being used while a positive number
// means the magnitude is free.
globals
    private constant integer MAX_SIZE = 32760
    private integer array MainArray[MAX_SIZE]
    private integer array DataArray[MAX_SIZE]
endglobals

struct DynamicArray
    private integer start
    private integer size
    
    static method create takes integer size returns DynamicArray
        local DynamicArray a = DynamicArray.allocate()
        local integer temp = 0
        local integer tempsize
        if size <= 0 then
            call a.destroy()
            return 0
        endif
        loop
            if temp >= MAX_SIZE then
                call a.destroy()
                return 0
            endif
            if DataArray[temp] > size then // enough free
                set a.start = temp
                set a.size = size
                set tempsize = DataArray[temp]
                set DataArray[temp] = -size
                set temp = temp + size
                if DataArray[temp] == 0 then
                    set DataArray[temp] = tempsize - size
                endif
                exitwhen true
            else
                if DataArray[temp] < 0 then // slots taken till
                    set temp = temp - DataArray[temp]
                else // not enough room in this allocation
                    set temp = temp + DataArray[temp]
                endif
            endif
        endloop
        return a
    endmethod
    
    method operator [] takes integer i returns integer
        if not (i < .size) then
            return 0
        else
            return MainArray[.start+i]
        endif
    endmethod
    
    method operator []= takes integer i, integer data returns nothing
        if not (i < .size) then
            return
        else
            set MainArray[.start+i] = data
        endif
    endmethod
    
    method onDestroy takes nothing returns nothing
        local integer temp  = 0
        local integer tempback = 0
        local integer tempforward = 0
        loop
            exitwhen temp == .start
            set tempback = temp
            if DataArray[temp] > 0 then
                set temp = temp + DataArray[temp]
            else
                set temp = temp - DataArray[temp]
            endif
        endloop
        set DataArray[temp] = .size
        set tempforward = temp + .size
        if DataArray[tempforward] > 0 then // sub it's index and add to front
            set DataArray[temp] = DataArray[temp] + DataArray[tempforward]
            set DataArray[tempforward] = 0
        endif
        if tempback != temp and DataArray[tempback] > 0 then // add to it's index
            set DataArray[tempback] = DataArray[tempback] + DataArray[temp]
            set DataArray[temp] = 0
        endif
        set .start = 0
        set .size = 0
    endmethod      
endstruct

function CheckDynamicArraysData takes nothing returns nothing
    local integer i = 0
    local string out = ""
    loop
        exitwhen i >= MAX_SIZE
        if DataArray[i] > 0 then
            set out = out + "[|cff00ff00"+I2S(i)+" - "+I2S(i+DataArray[i]-1)+"|r]"
            set i = i + DataArray[i]
        else
            set out = out + "[|cffff0000"+I2S(i)+" - "+I2S(i-DataArray[i]-1)+"|r]"
            set i = i - DataArray[i]
        endif
    endloop
    call BJDebugMsg(out)
endfunction

private function init takes nothing returns nothing
    set DataArray[0] = MAX_SIZE
endfunction

endlibrary
On create, it uses a linear search through the dynamic arrays, searching for free space that will hold the new array. On destroy, it starts from the front and uses a linear search again, to locate the position. This is done to make sure we have the reference to the last bunch (free or used space). It then checks to see if in front or behind has free space. If so, it combines the bunches together.

I could speed up the linear search destroy by added a forward/backward data, but the extra over-head for creation may not be desired. I could also skip part of the linear search in the create method by specifying the first free index. This I will probably do.

Any other thoughts or criticisms?
01-25-2009, 10:53 AM#2
Captain Griffen
Do you actually need differing array sizes? If not you could use a stack implementation (aka: Struct), which'd give you pretty quick O(1) creation and destruction.
01-25-2009, 12:19 PM#3
Anitarf
The create method should have another case for when DataArray[temp] == size. Also, the create method should have the option of initializing the array values, or do that by default.

Organizationally, I'd change the init function to an onInit method and make those three globals into static struct members so the entire arrays code would be contained in one struct.
01-25-2009, 03:52 PM#4
Ammorth
Quote:
Originally Posted by Captain Griffen
Do you actually need differing array sizes? If not you could use a stack implementation (aka: Struct), which'd give you pretty quick O(1) creation and destruction.

Yes I do, part of the reason why I didn't just define an array in the struct I plan to be using this in.

Quote:
Originally Posted by Anitarf
The create method should have another case for when DataArray[temp] == size. Also, the create method should have the option of initializing the array values, or do that by default.

Organizationally, I'd change the init function to an onInit method and make those three globals into static struct members so the entire arrays code would be contained in one struct.

I think initializing the arrays is too costly of a performance, and since people can declare their size, I would only assume they would fill the array instantly (at least thats what I will be doing). I will though add another argument and can do it if the user wants.
01-25-2009, 04:29 PM#5
Vexorian
Use table?
Use type meh extends integer array [3,400000] ?
01-25-2009, 04:38 PM#6
Ammorth
Quote:
Originally Posted by Vexorian
Use table?
Use type meh extends integer array [3,400000] ?

Table per struct instance is not what I want. I would also like to stay away from gamecache for this.

vJass dynamic arrays do not allow for different sizes for the same type, something I need.
01-25-2009, 05:18 PM#7
Vexorian
Quote:
Table per struct instance is not what I want..

why?
Quote:
I would also like to stay away from gamecache for this.
why?

Quote:
vJass dynamic arrays do not allow for different sizes for the same type, something I need.[/b]
Do you really, really need that?


--
You could use what I did for CSCache's dynamic arrays, I doubt anyone can do better than pipe's idea there of using fibonacci heaps and crazy things like that.

You basically have N stacks instead of a single one. Each of these stacks contains indexes for arrays of some size equal to a fibonacci number, for example, Stack[0] contains the indexes of arrays of size 1.., Stack[1] for size 2, Stack[2] for 3, Stack[3] for 5, Stack[4] for 8 , etc...

Then say someone needs to allocate an array of size 9, you have to round it to the next fibonacci number, which would be 13. So you request an array index of size 13. You ask Stack[5] for that index.


Of course, chances are, Stack[5] could be empty, so what to do? Well, you can request an array of size 21 (8+13) you then take this array's first 8 indexes and push them to Stack[4] (Of size 8), keep the rest for what you want to store.

If there aren't stacks of size 6, try with [7], (13+21) , there's one of 12 you can get from it, else try getting a 6-size one from [8], then get the 5-size from the 6-size, etc...

So, you begin with Stack[N] which contains a single dynamic array of size Fib(N), a big enough to cover most of your available space. The remaining Stacks[0..N-1] begin empty. If you ever run out of stacks, you should have used gamecache... After deallocation you need something that would restore indexes of larger sizes.
01-25-2009, 06:38 PM#8
Ammorth
Quote:
Originally Posted by Vexorian
why?

why?

You could use what I did for CSCache's dynamic arrays, I doubt anyone can do better than pipe's idea there of using fibonacci heaps and crazy things like that.

You basically have N stacks instead of a single one. Each of these stacks contains indexes for arrays of some size equal to a fibonacci number, for example, Stack[0] contains the indexes of arrays of size 1.., Stack[1] for size 2, Stack[2] for 3, Stack[3] for 5, Stack[4] for 8 , etc...

Then say someone needs to allocate an array of size 9, you have to round it to the next fibonacci number, which would be 13. So you request an array index of size 13. You ask Stack[5] for that index.


Of course, chances are, Stack[5] could be empty, so what to do? Well, you can request an array of size 21 (8+13) you then take this array's first 8 indexes and push them to Stack[4] (Of size 8), keep the rest for what you want to store.

If there aren't stacks of size 6, try with [7], (13+21) , there's one of 12 you can get from it, else try getting a 6-size one from [8], then get the 5-size from the 6-size, etc...

So, you begin with Stack[N] which contains a single dynamic array of size Fib(N), a big enough to cover most of your available space. The remaining Stacks[0..N-1] begin empty. If you ever run out of stacks, you should have used gamecache... After deallocation you need something that would restore indexes of larger sizes.

Using a table per instance would mean I may have to increase the number of Tables, thus removing the in-line awesomeness of table.

Cause I would have to use Table, and therefore see above.

The Fib sequence is really interesting. I read about powers of 2, but the wasted space is considerable when asking for arrays of 2^n + c where c << 2^(n+1).

My only issue is that the arrays will not be quickly added or destroyed, so space allocation is more of an importance versus creation/destruction times.
01-25-2009, 07:05 PM#9
Anitarf
Ok, now we've come to the point where you need to explain what you need this for.
01-25-2009, 07:17 PM#10
Vexorian
Quote:
Using a table per instance would mean I may have to increase the number of Tables, thus removing the in-line awesomeness of table.
what?
01-25-2009, 07:34 PM#11
Ammorth
Quote:
Originally Posted by Anitarf
Ok, now we've come to the point where you need to explain what you need this for.

I guess table would work fine for what I need it for... but dynamic arrays are sexy.
01-25-2009, 08:09 PM#12
PipeDream
DARY is awesome, use DARY. I told people to use something else for long term stuff because people wanted to use DARY for what structs are used for now. If you really wanted short term arrays, an obstack would be much better.

DARY needs vJass on three accounts - templates for multiple allocation pools, array extension for larger pools, function pointers for less clumsy generic programming. Each of these any given user almost certainly does not need.

But Anitarf is right, you have to explain application.
01-25-2009, 11:29 PM#13
fX_
What is 'dynamic array'?
01-25-2009, 11:52 PM#14
akolyt0r
dynamic allocated (=runtime) arrays with custom size ...
01-26-2009, 12:55 AM#15
Ammorth
I was planning on using it for re-writing part of an inventory system I have been slowly writing ( specifically the part where I store the items a unit carries. Part of my hesitation was that some unit may only have 6 items while others could have 20 to 30. I didn't feel comfortable with with allocating a table instance that would only be holding 6 integers, when a dynamic array wouldn't matter.

My only issue with DARY is that it does not have the vJass wrappers that make it look and work like a native dynamic array would. Part of the beauty of this item system is that the users write the code for their items, which can iherit proprties from sub-classes of items. Making the system feel as standard looking as possible would ease the coding for the users who use the system, including me. If DARY had wrappers and was setup as a struct, I would be more inclined to use it.