HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Exgroup script feedback

04-04-2009, 08:18 PM#1
0zyx0
I have recently been working on a script, declaring a struct called exgroup. Exgroup meand extended group, and it is meant to replace normal groups during certain circumstances. The key feature of it, is that a unit can be added multiple times to the same exgroup. This is very useful in some situations. It allows weighted randomness when picking a random unit, and a function will be executed multiple times on certain units, when calling exgroup's equivalent to ForGroup().

Firstly, here is the documentation:

Expand JASS:

And here is the script itself:
Expand JASS:

I would like to get feedback about this script, its usefullnes, what should be added, and all kinds of feedback, really. I am planning to submit this as a resource, but I would like to have some comments first.

An improved version can be found below.
04-06-2009, 02:55 AM#2
Anitarf
I'd make it linked list based instead of array based, it would facilitate a much higher exgroup limit.
04-06-2009, 04:05 AM#3
Vexorian
I think a more appropriate name is "multigroup"
04-06-2009, 07:52 AM#4
0zyx0
I recreated the system from scratch, to use multiple unitgroups instead of arrays, adding speed and functionallity, but removing functions related to randomness. I haven't had time to create a new documentation yet.

Collapse JASS:
library Multigroup requires GroupUtils
globals
    private constant integer MAX_MEMBERSHIPS = 5
endglobals

public function interface UnitAction takes unit u returns nothing
public function interface UnitFilter takes unit u returns boolean

struct multigroup
    private group array content [MAX_MEMBERSHIPS]
    private integer groupcount = 0
    private integer membershiplimit = MAX_MEMBERSHIPS
    boolean wantRelease = false
    UnitFilter accept
    
    method containsUnit takes unit u returns boolean
        return IsUnitInGroup(u, .content[0])
    endmethod
    method operator isEmpty takes nothing returns boolean
        return .content[0] == null
    endmethod
    method operator unitCount takes nothing returns integer
        local integer i = 0
        local integer j = 0
        loop
            exitwhen i == .groupcount
            set j = j + CountUnitsInGroup(.content[i])
            set i = i + 1
        endloop
        return j
    endmethod
    
    method operator asGroup takes nothing returns group
        return .content[0]
    endmethod
    
    method operator membershipLimit takes nothing returns integer
        return .membershiplimit
    endmethod
    method operator membershipLimit= takes integer j returns nothing
        local integer i = .groupcount
        set .membershiplimit = j
        if j < .groupcount then
            loop
                exitwhen i == j
                call ReleaseGroup(.content[i])
                set i = i-1
            endloop
        endif
    endmethod
    
    method operator membershipMax takes nothing returns integer
        return .groupcount
    endmethod
    
    static method create takes nothing returns multigroup
        local multigroup exg = multigroup.allocate()
        local integer i = 1
        loop
            exitwhen i == MAX_MEMBERSHIPS
            set exg.content[i] = null
            set i = i+1
        endloop
        set exg.groupcount = 1
        return exg
    endmethod
    method onDestroy takes nothing returns nothing
        local integer i = 0
        loop
            exitwhen i == .groupcount
            call ReleaseGroup(.content[i])
            set .content[i] = null
            set i = i+1
        endloop
    endmethod
//=========================================================================================
// Single Unit methods  
      
    method addUnit takes unit u returns boolean
        local integer i = 0
        if .accept != 0 then
            if .accept.evaluate(u) == false then
                return false
            endif
        endif
        loop
            exitwhen i == .membershiplimit
            if .content[i] == null then
                set .content[i] = NewGroup()
                set .groupcount = i+1
            endif
            if IsUnitInGroup(u, .content[i]) == false then
                call GroupAddUnit(.content[i], u)
                return true
            endif
            set i = i+1
        endloop
        return false
    endmethod
    
    method removeUnit takes unit u returns boolean
        local integer i = .groupcount-1
        if IsUnitInGroup(u, .content[0]) == false then
            return false
        endif
        loop
            exitwhen i < 0
            if IsUnitInGroup(u, .content[i]) then
                call GroupRemoveUnit(.content[i], u)
                if IsUnitGroupEmptyBJ(.content[i]) then
                    call ReleaseGroup(.content[i])
                    set .content[i] = null
                    set .groupcount = .groupcount - 1
                endif
                return true
            endif
            set i = i-1
        endloop
        return false
    endmethod
    
    method removeUnitAll takes unit u returns boolean
        local integer i = .groupcount
        loop
            exitwhen i == 0
            call GroupRemoveUnit(.content[i], u)
            set i = i+1
            if IsUnitGroupEmptyBJ(.content[i]) then
                call ReleaseGroup(.content[i])
                set .content[i] = null
                set .groupcount = .groupcount - 1
            endif
            set i = i-1
        endloop
        if IsUnitInGroup(u, .content[0]) then
            call GroupRemoveUnit(.content[0], u)
            if IsUnitGroupEmptyBJ(.content[0]) then
                call ReleaseGroup(.content[0])
                set .content[0] = null
                set .groupcount = .groupcount - 1
            endif
            return true
        endif
        return false
    endmethod
//=========================================================================================
// Group methods
    
    method addGroup takes group g returns nothing
        local unit u
        local group g2 = NewGroup()
        loop
            exitwhen IsUnitGroupEmptyBJ(g)
            set u = FirstOfGroup(g)
            call .addUnit(u)
            call GroupAddUnit(g2, u)
            call GroupRemoveUnit(g, u)
        endloop
        loop
            exitwhen IsUnitGroupEmptyBJ(g2)
            set u = FirstOfGroup(g2)
            call GroupAddUnit(g, u)
            call GroupRemoveUnit(g2, u)
        endloop
        call ReleaseGroup(g2)
        set g2 = null
        set u = null
    endmethod
    
    method removeGroup takes group g returns nothing
        local unit u
        local group g2 = NewGroup()
        loop
            exitwhen IsUnitGroupEmptyBJ(g)
            set u = FirstOfGroup(g)
            call .removeUnit(u)
            call GroupAddUnit(g2, u)
            call GroupRemoveUnit(g, u)
        endloop
        loop
            exitwhen IsUnitGroupEmptyBJ(g2)
            set u = FirstOfGroup(g2)
            call GroupAddUnit(g, u)
            call GroupRemoveUnit(g2, u)
        endloop
        call ReleaseGroup(g2)
        set g2 = null
        set u = null
    endmethod
    
    method removeGroupAll takes group g returns nothing
        local unit u
        local group g2 = NewGroup()
        loop
            exitwhen IsUnitGroupEmptyBJ(g)
            set u = FirstOfGroup(g)
            call .removeUnitAll(u)
            call GroupAddUnit(g2, u)
            call GroupRemoveUnit(g, u)
        endloop
        loop
            exitwhen IsUnitGroupEmptyBJ(g2)
            set u = FirstOfGroup(g2)
            call GroupAddUnit(g, u)
            call GroupRemoveUnit(g2, u)
        endloop
        call ReleaseGroup(g2)
        set g2 = null
        set u = null
    endmethod
    
    static method fromGroup takes group g returns multigroup
        local multigroup mg = multigroup.create()
        call mg.addGroup(g)
        return mg
    endmethod
    
//=========================================================================================
// Clearing methods

    method clear takes nothing returns nothing
        local integer i = 0
        loop
            exitwhen i == .groupcount
            call ReleaseGroup(.content[i])
            set i = i+1
        endloop
    endmethod
    
    method clearDuplicates takes nothing returns nothing
        local integer i = 1
        loop
            exitwhen i == .groupcount
            call ReleaseGroup(.content[i])
            set i = i+1
        endloop
    endmethod
    
//=========================================================================================
// Interaction between multiple multigroups

    method addMultigroup takes multigroup exg returns nothing
        local integer i = 0
        loop
            exitwhen i == exg.groupcount
            call .addGroup(exg.content[i])
            set i = i+1
        endloop
    endmethod
    
    method removeMultigroup takes multigroup exg returns nothing
        local integer i = 0
        loop
            exitwhen i == exg.groupcount
            call .removeGroup(exg.content[i])
            set i = i+1
        endloop
    endmethod
    
    method clone takes nothing returns multigroup
        local multigroup exg = multigroup.create()
        call exg.addMultigroup(this)
        return exg
    endmethod

//=========================================================================================
// Methods with a function interface argument

    method for takes UnitAction ua returns nothing
        local integer i = 0
        local group g
        local unit u
        loop
            exitwhen i == .groupcount
            set g = NewGroup()
            loop
                set u = FirstOfGroup(.content[i])
                exitwhen u == null
                call ua.execute(u)
                call GroupRemoveUnit(.content[i], u)
                call GroupAddUnit(g, u)
            endloop
            call ReleaseGroup(.content[i])
            set .content[i] = g
            set i = i+1
        endloop
        set g = null
        set u = null
    endmethod
    
    method filterOut takes UnitFilter uf returns nothing
        local unit u
        local group g = NewGroup()
        loop
            set u = FirstOfGroup(.content[0])
            exitwhen u == null
            call GroupRemoveUnit(.content[0], u)
            if uf.evaluate(u) then
                if .removeUnit(u) then
                    call GroupAddUnit(g, u)
                endif
            else
                call GroupAddUnit(g, u)
            endif
        endloop
        call ReleaseGroup(.content[0])
        set .content[0] = g
        set g = null
    endmethod
    
    method filterOutAll takes UnitFilter uf returns nothing
        local unit u
        local group g = NewGroup()
        loop
            set u = FirstOfGroup(.content[0])
            exitwhen u == null
            call GroupRemoveUnit(.content[0], u)
            if uf.evaluate(u) then
                call .removeUnitAll(u)
            else
                call GroupAddUnit(g, u)
            endif
        endloop
        call ReleaseGroup(.content[0])
        set .content[0] = g
        set g = null
    endmethod
    
    method filterAdd takes UnitFilter uf returns nothing
        local integer i = 0
        local group g = NewGroup()
        local unit u
        loop
            set u = FirstOfGroup(.content[0])
            exitwhen u == null
            if uf.evaluate(u) then
                call .addUnit(u)
            endif
            call GroupRemoveUnit(.content[0], u)
            call GroupAddUnit(g, u)
        endloop
        call ReleaseGroup(.content[0])
        set .content[0] = g
        set g = null
        set u = null
    endmethod
    
endstruct
endlibrary
04-06-2009, 09:34 AM#5
PipeDream
It seems to me like the multiple entries implementations are complicated. Your goals of weighted randomness and multiple execution can be done by unique entries with counts, assuming you have some place to put the counts.

To do random weighted sampling, pick a random entry and accept it with probability the desired weight, else try again.

Then you have:
Psample = 1/N
Paccept = w_i
Ptotal = (w_i / N) / (sum_i w_i / N) = w_i

But for N equal weights you'll have to reject N samples, potentially inefficient. Also depends on being able to uniformly sample the collection, which I don't think you can do with groups.

The other approach is bisection. Compile all your elements into a real array v, where v[i] = v[i-1] + w_i, v[-1] = 0. This is an O(N) construction step, which, if you're doing a lot of sampling and not much group twiddling, may be irrelevant. Then pick one random real, and bisect in O(log N) to find the interval containing that real, or assume even distribution to try to find it faster.

These aren't as fast as what you had before, but they do open the door for non integer counts.