diff options
Diffstat (limited to 'Data/Libraries/Penlight/lua/pl/OrderedMap.lua')
-rw-r--r-- | Data/Libraries/Penlight/lua/pl/OrderedMap.lua | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/Data/Libraries/Penlight/lua/pl/OrderedMap.lua b/Data/Libraries/Penlight/lua/pl/OrderedMap.lua new file mode 100644 index 0000000..379c44f --- /dev/null +++ b/Data/Libraries/Penlight/lua/pl/OrderedMap.lua @@ -0,0 +1,167 @@ +--- OrderedMap, a map which preserves ordering. +-- +-- Derived from `pl.Map`. +-- +-- Dependencies: `pl.utils`, `pl.tablex`, `pl.class`, `pl.List`, `pl.Map` +-- @classmod pl.OrderedMap + +local tablex = require 'pl.tablex' +local utils = require 'pl.utils' +local List = require 'pl.List' +local index_by,tsort,concat = tablex.index_by,table.sort,table.concat + +local class = require 'pl.class' +local Map = require 'pl.Map' + +local OrderedMap = class(Map) +OrderedMap._name = 'OrderedMap' + +local rawset = rawset + +--- construct an OrderedMap. +-- Will throw an error if the argument is bad. +-- @param t optional initialization table, same as for @{OrderedMap:update} +function OrderedMap:_init (t) + rawset(self,'_keys',List()) + if t then + local map,err = self:update(t) + if not map then error(err,2) end + end +end + +local assert_arg,raise = utils.assert_arg,utils.raise + +--- update an OrderedMap using a table. +-- If the table is itself an OrderedMap, then its entries will be appended. +-- if it s a table of the form `{{key1=val1},{key2=val2},...}` these will be appended. +-- +-- Otherwise, it is assumed to be a map-like table, and order of extra entries is arbitrary. +-- @tab t a table. +-- @return the map, or nil in case of error +-- @return the error message +function OrderedMap:update (t) + assert_arg(1,t,'table') + if OrderedMap:class_of(t) then + for k,v in t:iter() do + self:set(k,v) + end + elseif #t > 0 then -- an array must contain {key=val} tables + if type(t[1]) == 'table' then + for _,pair in ipairs(t) do + local key,value = next(pair) + if not key then return raise 'empty pair initialization table' end + self:set(key,value) + end + else + return raise 'cannot use an array to initialize an OrderedMap' + end + else + for k,v in pairs(t) do + self:set(k,v) + end + end + return self +end + +--- set the key's value. This key will be appended at the end of the map. +-- +-- If the value is nil, then the key is removed. +-- @param key the key +-- @param val the value +-- @return the map +function OrderedMap:set (key,val) + if rawget(self, key) == nil and val ~= nil then -- new key + self._keys:append(key) -- we keep in order + rawset(self,key,val) -- don't want to provoke __newindex! + else -- existing key-value pair + if val == nil then + self._keys:remove_value(key) + rawset(self,key,nil) + else + self[key] = val + end + end + return self +end + +OrderedMap.__newindex = OrderedMap.set + +--- insert a key/value pair before a given position. +-- Note: if the map already contains the key, then this effectively +-- moves the item to the new position by first removing at the old position. +-- Has no effect if the key does not exist and val is nil +-- @int pos a position starting at 1 +-- @param key the key +-- @param val the value; if nil use the old value +function OrderedMap:insert (pos,key,val) + local oldval = self[key] + val = val or oldval + if oldval then + self._keys:remove_value(key) + end + if val then + self._keys:insert(pos,key) + rawset(self,key,val) + end + return self +end + +--- return the keys in order. +-- (Not a copy!) +-- @return List +function OrderedMap:keys () + return self._keys +end + +--- return the values in order. +-- this is relatively expensive. +-- @return List +function OrderedMap:values () + return List(index_by(self,self._keys)) +end + +--- sort the keys. +-- @func cmp a comparison function as for @{table.sort} +-- @return the map +function OrderedMap:sort (cmp) + tsort(self._keys,cmp) + return self +end + +--- iterate over key-value pairs in order. +function OrderedMap:iter () + local i = 0 + local keys = self._keys + local idx + return function() + i = i + 1 + if i > #keys then return nil end + idx = keys[i] + return idx,self[idx] + end +end + +--- iterate over an ordered map (5.2). +-- @within metamethods +-- @function OrderedMap:__pairs +OrderedMap.__pairs = OrderedMap.iter + +--- string representation of an ordered map. +-- @within metamethods +function OrderedMap:__tostring () + local res = {} + for i,v in ipairs(self._keys) do + local val = self[v] + local vs = tostring(val) + if type(val) ~= 'number' then + vs = '"'..vs..'"' + end + res[i] = tostring(v)..'='..vs + end + return '{'..concat(res,',')..'}' +end + +return OrderedMap + + + |