Skip to content

Instantly share code, notes, and snippets.

@inmatarian
Created January 4, 2014 19:26
Show Gist options
  • Save inmatarian/8259581 to your computer and use it in GitHub Desktop.
Save inmatarian/8259581 to your computer and use it in GitHub Desktop.
Implementation of a Strided Array, a generalized data structure for accessing a single array through a multiple dimension view.
local stride, stride_accessor, TERMINATOR
stride = {
_VERSION = "stride.lua 1.0",
_DESCRIPTION = [[
Strided arrays are a generalized version of multi-dimensional arrays.
Use this instead of doing a lot of A[1+(y-1)*width+(x-1)] mayhem all over.
Example usage:
my_array = { 1, 2, 3, 4, 5, 6 }
view2d = stride.new(my_array, {3, 2})
]],
new = function(...)
return stride.init(setmetatable({}, stride), ...)
end,
-- Data is an existing data array
-- Shape values should represent each dimension, starting with the lowest first,
-- i.e. { x, y, z }
-- Options is a table of key-value pairs
-- assertions: true runs assert to init conditions
-- boundingmode: string representing how to handle bounds checks
-- "unsafe": performs no secure check of bounds, bleed values
-- "safe": allows going outside of bounds, loses data or returns default
-- "error": disallows going outside of bounds, calls error
-- default: the default value for safe bounding mode
init = function(self, data, shape, options)
-- provide view onto data
self.data = data
-- copy options and shape because those tables could be reused
self.options = {
assertions = true,
boundingmode = "safe",
default = 0,
}
if options then
for k, v in pairs(options) do self.options[k] = v end
end
self.shape = {}
for i = 1, #shape do self.shape[i] = shape[i] end
self.stride = {}
local sv = 1
for i = 1, #shape do
self.stride[#self.stride+1] = sv
sv = sv * shape[i]
end
if self.options.assertions then
assert(#data == sv, "Shape doesn't completely represent data")
end
return self
end,
__index = {
get = function(self, ...)
return stride_accessor(self, TERMINAL, ...)
end,
set = function(self, value, ...)
stride_accessor(self, value, ...)
end,
},
}
do
-- unique value to stand in for nil
TERMINATOR = {}
stride_accessor = function(self, value, ...)
local doassert = false
if self.options.assertions then doassert = assert end
if doassert then doassert(select('#', ...) == #self.stride, "N-Dimensionality is inconsistent.") end
local boundingmode = self.options.boundingmode
local L, stride, shape = 0, self.stride, self.shape
for i = 1, #stride do
local x = tonumber((select(i, ...)))
if doassert then doassert(x~=nil, "N-Dimension must be indexed by a number") end
if boundingmode ~= "unsafe" then
if x < 1 or x > shape[i] then
if boundingmode == "safe" then
if value ~= TERMINAL then
return self.options.default
else
return
end
else
error("N-Dimension must be within bounds")
end
end
end
L = L + stride[i] * (x-1)
end
if value ~= TERMINAL then
self.data[1+L] = value
else
return self.data[1+L]
end
end
end
return stride
local stride = require 'stride'
do -- Testing
local function assert_equal(actual, expected)
if actual ~= expected then
error(("actual %s ~= expected %s"):format(tostring(actual), tostring(expected)), 2)
end
end
local test1 = stride.new({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, {4, 3})
assert_equal(test1:get(1, 1), 1)
assert_equal(test1:get(2, 2), 6)
assert_equal(test1:get(3, 2), 7)
assert_equal(test1:get(4, 3), 12)
local test2 = stride.new({1, 2, 3, 4, 5, 6, 7, 8}, {2, 2, 2})
assert_equal(test2:get(2, 1, 1), 2)
assert_equal(test2:get(1, 2, 1), 3)
assert_equal(test2:get(1, 1, 2), 5)
assert_equal(test2:get(1, 2, 2), 7)
local test3 = stride.new({0, 0, 0, 0, 0, 0}, {3, 2})
local v = 1
for y = 1, 2 do
for x = 1, 3 do
test3:set(v, x, y)
v = v * 2
end
end
v = 1
for i = 1, #test3.data do
assert_equal(test3.data[i], v)
v = v * 2
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment