## Copyright (C) 2015 Daniel Kraft
## GNU Octave datastructure package.
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see .
## -*- texinfo -*-
## @deftypefn {Function File} address@hidden =} stack ()
## @deftypefnx {Function File} address@hidden =} stack (@var{values})
##
## Construct a new object @var{s} of the @code{stack} class. If called
## with the argument @var{values}, the initial content of the stack
## can be set from a scalar, vector or cell array.
##
## Stack is a datastructure that allows efficient appending and removing
## of elements from the end. This is achieved by overallocating a cell array,
## such that addition of elements to the end is amortized constant in time.
## Removing elements is guaranteed to be constant.
##
## Note that the stack is implemented in m-file code, so that operations
## carry some overhead for interpretation. This class is most useful
## if large elements have to be stored and the maximum size of the array
## is not known a-priori.
% FIXME: Add documentation for the individual functions, although it is
% seemingly currently not displayed at all by Octave.
classdef stack < handle
% Define constants that determine the resizing behaviour.
properties (Access = private, Constant = true)
minSize = 16;
resizeFactor = 2;
endproperties
properties (Access = private)
values = {};
sz = 0;
endproperties
methods
function s = stack (initial)
if (nargin > 0)
s.set (initial);
else
s.values = cell (s.minSize, 1);
s.sz = 0;
endif
endfunction
function res = isempty (this)
res = (this.sz == 0);
endfunction
function [real, reserved] = size (this)
real = this.sz;
reserved = length (this.values);
endfunction
function display (this)
fprintf ("%s = stack: size %u, allocated %u\n", ...
inputname(1), this.sz, length (this.values));
endfunction
function v = top (this)
if (this.isempty ())
error ("the stack is empty");
endif
v = this.values{this.sz};
endfunction
function push (this, val)
inds = struct ("type", "()", "subs", {{this.sz+1}});
this.subsasgn (inds, val);
endfunction
function v = pop (this)
if (this.isempty ())
error ("the stack is empty");
endif
if (nargout > 0)
v = this.values{this.sz};
endif
% Set the 'deleted' entry to a small value. If the entry was something
% like a large array, this allows to immediately free the memory instead
% of keeping it around.
this.values{this.sz} = {};
--this.sz;
endfunction
function clear (this)
this.sz = 0;
endfunction
function reserve (this, sz, tighten = false)
sz = max (sz, this.sz);
if (sz == length (this.values))
return;
endif
if (sz < length (this.values) && !tighten)
return;
endif
assert (sz >= this.sz);
this.values = resize (this.values, [sz, 1]);
endfunction
% Define functions related to indexing. They do the main work.
function r = end (this, indexPos, numInd)
if (numInd != 1)
error ("stack can only have one index");
endif
assert (indexPos, 1);
r = this.sz;
endfunction
function v = subsref (this, ind)
% FIXME: Somehow make this not fail when trying to call
% methods via the dot notation!
if (!strcmp (ind.type, "()"))
error ("invalid indexing of stack");
endif
inds = ind.subs;
if (numel (inds) != 1)
error ("stack can only have one index");
endif
if (strcmp (inds, ":"))
v = this.values(1 : this.sz);
else
inds = cell2mat (inds);
if (any (inds < 1 | inds > this.sz))
error ("stack index out of bounds");
endif
if (isscalar (inds))
v = this.values{inds};
else
v = this.values(inds);
endif
endif
endfunction
function this = subsasgn (this, ind, rhs)
if (!strcmp (ind.type, "()"))
error ("invalid indexing of stack");
endif
inds = ind.subs;
if (numel (inds) != 1)
error ("stack can only have one index");
endif
if (strcmp (inds, ":"))
this.set (rhs);
else
inds = cell2mat (inds);
if (any (inds < 1))
error ("stack index out of bounds");
endif
% Resize if necessary.
alloc = length (this.values);
if (alloc < max (inds))
alloc = max (max (inds), this.resizeFactor * alloc);
this.values = resize (this.values, [alloc, 1]);
endif
assert (max (inds) <= length (this.values));
% Assign a scalar as-is but translate the RHS to a cell array
% (i. e., allow using a vector) if we do range assignment.
if (isscalar (inds))
this.values{inds} = rhs;
else
this.values(inds) = this.rhsToCell (rhs);
endif
this.sz = max ([this.sz, inds]);
endif
endfunction
endmethods
methods (Access = private)
function set (this, rhs)
% Make sure that we have at least the minimum initial size.
this.sz = length (rhs);
alloc = max (this.minSize, this.sz);
this.values = cell (alloc, 1);
this.values(1 : this.sz) = this.rhsToCell (rhs);
endfunction
endmethods
methods (Access = private, Static = true)
function rhs = rhsToCell (rhs)
n = numel (rhs);
if (n > 0 && !isvector (rhs))
error ("invalid stack assignment right-hand side");
endif
if (!isscalar (rhs) && !strcmp (class (rhs), "cell"))
rhs = reshape (rhs, [n, 1]);
rhs = mat2cell (rhs, ones (n, 1));
endif
endfunction
endmethods
endclassdef
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Demo.
% Demonstrate how pushing to the stack is algorithmically better
% than using an ordinary array "naively".
%!demo
%! k = 100;
%! ns = round (logspace (log10 (10), log10 (500), 10));
%!
%! timesStack = NA (size (ns));
%! timesArray = NA (size (ns));
%! for i = 1 : length (ns)
%!
%! t1 = cputime ();
%! s = stack ();
%! for j = 1 : ns(i)
%! push (s, rand (k, k));
%! endfor
%! t2 = cputime ();
%! timesStack(i) = t2 - t1;
%!
%! t1 = cputime ();
%! s = [];
%! for j = 1 : ns(i)
%! s(1 : k, 1 : k, end + 1) = rand (k, k);
%! endfor
%! t2 = cputime ();
%! timesArray(i) = t2 - t1;
%!
%! printf ("Times for n = %3d: stack %6.3f, array %6.3f\n", ...
%! ns(i), timesStack(i), timesArray(i));
%! endfor
%!
%! loglog (ns, timesStack, "bo", ns, timesArray, "ro");
%! legend ("Stack", "Array");
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Tests.
% Test basic stack operations.
%!test
%! s = stack ();
%! assert (size (s), 0);
%! assert (isempty (s));
%! toPush = {42, "foobar", {}, [], rand(2, 2), 1:5};
%!
%! for i = 1 : length (toPush)
%! push (s, toPush{i});
%! assert (size (s), i);
%! assert (!isempty (s));
%! assert (top (s), toPush{i});
%! endfor
%!
%! for i = length (toPush) : -1 : 1
%! assert (size (s), i);
%! assert (!isempty (s));
%! val = pop (s);
%! assert (val, toPush{i});
%! endfor
%!
%! assert (size (s), 0);
%! assert (isempty (s));
%!error
%! s = stack ();
%! top (s);
%!error
%! s = stack ();
%! pop (s);
% Initialisation and retrieval with (:) indexing.
%!test
%! s = stack (5);
%! assert (s(:), {5});
%!
%! s = stack ([1, 2, 3]);
%! assert (s(:), {1; 2; 3});
%!
%! R = rand (2, 2);
%! s = stack ({"abc", {}, R});
%! assert (s(:), {"abc"; {}; R});
%!
%! s(:) = 5;
%! assert (s(:), {5});
%!
%! s(:) = {"abc"; 42};
%! assert (s(:), {"abc"; 42});
%!
%! s(:) = {};
%! assert (isempty (s));
%!error
%! stack (rand (2, 2));
%!error
%! s = stack ();
%! s(:) = [1, 2; 3, 4];
% Indexing by single entries and ranges, including end.
%!test
%! s = stack (1 : 10);
%! push (s, {});
%! assert (size (s), 11);
%! for i = 1 : 10
%! assert (s(i), i);
%! endfor
%! assert (s(3 : 5), {3; 4; 5});
%! assert (s(end), {});
%! assert (s(end - 2 : end - 1), {9; 10});
%!error
%! s = stack (1 : 10);
%! s(0);
%!error
%! s = stack (1 : 10);
%! s(end + 1);
%!error
%! s = stack (1 : 10);
%! s(1.5);
%!error
%! s = stack (1 : 10);
%! s(1, 2);
%!error
%! s = stack (1 : 10);
%! s();
%!error
%! s = stack (1 : 10);
%! s{5};
% Assigning to entries and ranges, including after the end.
%!test
%! s = stack (1 : 3);
%! s(2) = {"abc"};
%! assert (s(:), {1; {"abc"}; 3});
%! s(1 : 2) = {"abc"};
%! assert (s(:), {"abc"; "abc"; 3});
%! s(end : end + 2) = 42;
%! assert (s(:), {"abc"; "abc"; 42; 42; 42});
%!
%! s = stack ();
%! s(10) = 1;
%! assert (size (s), 10);
%! assert (s(10), 1);
%!error
%! s = stack ();
%! s(0) = 5;
%!error
%! s = stack ();
%! s(1, 2) = 5;
%!error
%! s = stack ();
%! s{1} = 5;
%!error
%! s = stack ();
%! s(1 : 5) = 1 : 6;
%!error
%! s = stack ();
%! s(1 : 5) = rand (2, 2);
% Tests for reserve and the expected reserving in general.
%!test
%! minSize = 16;
%! sizingFactor = 2;
%!
%! s = stack (42);
%! [a, b] = size (s);
%! assert ([a, b], [1, minSize]);
%!
%! s(minSize) = 5;
%! [a, b] = size (s);
%! assert ([a, b], [minSize, minSize]);
%!
%! push (s, 1);
%! [a, b] = size (s);
%! assert ([a, b], [minSize+1, sizingFactor*minSize]);
%!
%! s(:) = 1 : 5;
%! [a, b] = size (s);
%! assert ([a, b], [5, minSize]);
%!
%! reserve (s, 3);
%! assert (s(:), {1; 2; 3; 4; 5});
%! [a, b] = size (s);
%! assert ([a, b], [5, minSize]);
%!
%! reserve (s, 3, true);
%! assert (s(:), {1; 2; 3; 4; 5});
%! [a, b] = size (s);
%! assert ([a, b], [5, 5]);
%!
%! reserve (s, 100);
%! assert (s(:), {1; 2; 3; 4; 5});
%! [a, b] = size (s);
%! assert ([a, b], [5, 100]);