foreach iteration order in tables

Started by aXXo, Jul 09, 2016, 07:05 AM

Previous topic - Next topic

aXXo

myTable <- { "one" : 1, "two" : 2, "three" : 3, "four" : 4, "five" : 5, "six" : 6}

foreach( idx, num in myTable )
print( idx + " " + num );

Prints

[SCRIPT]  six 6
[SCRIPT]  five 5
[SCRIPT]  three 3
[SCRIPT]  one 1
[SCRIPT]  two 2
[SCRIPT]  four 4

I was expecting the table to iterate in the same order as it was created, or more or less some other deterministic order which can be manipulated. Is there a way for a table to iterate in some specific order under foreach loop?

Stormeus

@aXXo Tables act like dictionaries in Python and are unordered by design. It'd be more appropriate to use an array for tracking ordered elements.

To answer your question though, yes, you can manipulate the very definition of tables to iterate in a specific order. If you really, really needed to do this, you could absolutely abuse metamethods and table delegation to coerce ordering of indices. However, this requires overriding what's essentially half of the metamethods used by default for handling table slots. The whole thing is a mess and I wouldn't recommend using this code verbatim since it's barely tested.

/* HACK HACK HACK HACK HACK HACK HACK HACK
   HACK HACK HACK HACK HACK HACK HACK HACK
   HACK HACK HACK HACK HACK HACK HACK HACK
   HACK HACK HACK HACK HACK HACK HACK HACK
   HACK HACK HACK HACK HACK HACK HACK HACK
   HACK HACK HACK HACK HACK HACK HACK HACK
   HACK HACK HACK HACK HACK HACK HACK HACK
   HACK HACK HACK HACK HACK HACK HACK HACK
   HACK HACK HACK HACK HACK HACK HACK HACK

   EVERYTHING IS WRONG WITH THIS CODE
   EVERYTHING IS WRONG WITH THIS CODE
   EVERYTHING IS WRONG WITH THIS CODE
   EVERYTHING IS WRONG WITH THIS CODE
   EVERYTHING IS WRONG WITH THIS CODE
   EVERYTHING IS WRONG WITH THIS CODE
   EVERYTHING IS WRONG WITH THIS CODE
   EVERYTHING IS WRONG WITH THIS CODE
   EVERYTHING IS WRONG WITH THIS CODE
   EVERYTHING IS WRONG WITH THIS CODE */

class OrderedTableIterator
{
    _table = null;

    constructor(_table) {
        this._table = _table;
    }

    function _nexti(prevkey) {
        local _indices        = _table._indices;
        local _reverseIndices = _table._reverseIndices;
        local _values         = _table._values;

        if (_indices.len() == 0) {
            return null;
        }
        else if (prevkey == null) {
            return _indices[0];
        }
        else if (_reverseIndices[prevkey] < _indices.len() - 1) {
            return _indices[_reverseIndices[prevkey] + 1];
        }
        else {
            return null;
        }
    }

    function _get(key) {
        return _table._get(key);
    }
}

OrderedTable <- {
    function iterator() {
        return ::OrderedTableIterator(this);
    }

    function _newslot(key, value) {
        _indices.push(key);
        _reverseIndices[key] <- _indices.len() - 1;
        _values[key] <- value;
    }

    function _delslot(key) {
        local index = _reverseIndices[key];
       
        _indices.remove(index);
        delete _reverseIndices[key];
    }

    function _set(key, val) {
        if (key in _reverseIndices) {
            _values[key] = val;
        }
        else {
            throw("Setting value for an invalid key " + key);
        }
    }

    function _get(key) {
        if (key in _reverseIndices) {
            return _values[key];
        }
        else if (key == "iterator") {
            return iterator;
        }
        else if (key == "_indices") {
            return _indices;
        }
        else if (key == "_reverseIndices") {
            return _reverseIndices;
        }
        else if (key == "_values") {
            return _values;
        }
        else if (key == "_get") {
            return _get;
        }
        else {
            throw("Getting value for an invalid key " + key);
        }
    }

    function _typeof() {
        return "OrderedTable";
    }
};

function ordered() {
    local someTable = {
        _indices = []
        _reverseIndices = {}
        _values = {}
    }

    someTable.setdelegate(OrderedTable);
    return someTable;
}

myTable <- ordered();

myTable.one <- 1;
myTable.two <- 2;
myTable.three <- 3;
myTable.four <- 4;
myTable.five <- 5;
myTable.six <- 6;
delete myTable.four;

print("myTable.three = " + myTable.three);

print("Iteration test:");
foreach (key, val in myTable.iterator()) {
    print("~~ myTable." + key + " -> " + val);
}

print("myTable.four should not appear");

print("------------------------------------");

myOtherTable <- ordered();
myOtherTable.setdelegate(OrderedTable);

myOtherTable.a <- "b";

print("myOtherTable.a = " + myOtherTable.a);
print("Iteration test:");
foreach (key, val in myOtherTable.iterator()) {
    print("~~ myOtherTable." + key + " -> " + val);
}

print("done")

This creates a new function, ordered() for creating a table ordered by time of insertion. Note that you need to call into an iterator method to get a proper iterator for a foreach operation.

The following output is produced:



The code was also written in about 45 minutes at 4am local time. By the time I wake up I'll probably regret having written this at all.

DizzasTeR

If you really are in need of ordered iteration, then your first approach could for a for loop, but if things doesn't work with that you can go on with the metamethods like Storm did above, but I'm surprised to see and know that foreach iteration isn't going in order initially

Stormeus

@Doom_Kill3R Tables use associative indexing to identify keys more efficiently, whereas arrays actually use positional indexing. The concept of ordering doesn't apply to tables as a result, and adding ordering to them would unnecessarily inflate either memory or processing requirements, or both.