This commit is contained in:
Tristan B. Velloza Kildaire 2024-05-02 13:12:47 +00:00 committed by GitHub
commit 1c4a92982f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 442 additions and 0 deletions

View File

@ -11,6 +11,8 @@ import std.datetime.stopwatch : StopWatch, AutoStart;
import core.thread : Thread;
import core.sync.condition : Condition;
import std.functional : toDelegate;
import core.exception : ArrayIndexError;
import core.exception : RangeError;
version(unittest)
{
@ -597,4 +599,444 @@ unittest
// Destroy the map (such that it ends the sweeper
destroy(map);
}
private struct Sector(T)
{
private T[] data;
this(T[] data)
{
this.data = data;
}
public T opIndex(size_t idx)
{
return this.data[idx];
}
public void opIndexAssign(T value, size_t index)
{
this.data[index] = value;
}
// Contract: Obtaining the length must be present
public size_t opDollar()
{
return this.data.length;
}
// Contract: Obtaining the length must be present
@property
public size_t length()
{
return opDollar();
}
public T[] opSlice(size_t start, size_t end)
{
return this.data[start..end];
}
public T[] opSlice()
{
return opSlice(0, opDollar);
}
// Contract: Rezising must be implemented
// TODO: This would then be the very reason for
// using ref actually, as resizing may only
// change a local copy when extding on
// the tail-end "extent" (SectorType)
// Actually should resizing even be done here?
}
// TODO: Make a bit better
import std.traits : hasMember, hasStaticMember, Parameters;
import std.meta : AliasSeq;
private bool isSector(S)()
{
bool s = true;
s = hasMember!(S, "opSlice") && __traits(isSame, Parameters!(S.opSlice), AliasSeq!(size_t, size_t));
return s;
}
public struct View(T, SectorType = Sector!(T))
if(isSector!(SectorType)())
{
private SectorType[] sectors;
// private
// Maybe current size should be here as we
// are a view, we should allow modofication
// but not make any NEW arrays
private size_t curSize;
private size_t computeTotalLen()
{
size_t l;
foreach(SectorType sector; this.sectors)
{
l += sector.opDollar();
}
return l;
}
public size_t opDollar()
{
return this.length;
}
public T opIndex(size_t idx)
{
// Within range of "fake" size
if(!(idx < this.length))
{
throw new ArrayIndexError(idx, this.length);
}
size_t thunk;
foreach(SectorType sector; this.sectors)
{
if(idx-thunk < sector.opDollar())
{
return sector[idx-thunk];
}
else
{
thunk += sector.opDollar();
}
}
// NOTE: This should be unreachable but
// compiler moans and groans
assert(false);
}
public void opIndexAssign(T value, size_t idx)
{
// Within range of "fake" size
if(!(idx < this.length))
{
throw new ArrayIndexError(idx, this.length);
}
size_t thunk;
// TODO: Should be ref, else it is just a local struct copy
// could cheat if sector is never replaced, hence why it works
foreach(SectorType sector; this.sectors)
{
version(unittest)
{
writeln(sector);
writeln("idx: ", idx);
writeln("thunk: ", thunk);
}
if(idx-thunk < sector.opDollar())
{
sector[idx-thunk] = value;
return;
}
else
{
thunk += sector.opDollar();
}
}
}
public T[] opSlice()
{
return this[0..this.length];
}
public T[] opSlice(size_t start, size_t end)
{
// Invariant of start <= end
if(!(start <= end))
{
throw new RangeError("Starting index must be smaller than or equal to ending index");
}
// If the indices are equal, then it is empty
else if(start == end)
{
return [];
}
// Within range of "fake" size
else if(!((start < this.length) && (end <= this.length)))
{
throw new RangeError("start index or end index not under range");
}
T[] collected;
size_t thunk;
foreach(SectorType sector; this.sectors)
{
// If the current sector contains
// both the starting AND ending
// indices
if(start-thunk < sector.opDollar() && end-thunk <= sector.opDollar())
{
return sector[start-thunk..end-thunk];
}
// If the current sector's starting
// index (only) is included
else if(start-thunk < sector.opDollar() && !(end-thunk <= sector.opDollar()))
{
collected ~= sector[start-thunk..$];
}
// If the current sector's ending
// index (only) is included
else if(!(start-thunk < sector.opDollar()) && end-thunk <= sector.opDollar())
{
collected ~= sector[0..end-thunk];
}
// If the current sector's entirety
// is to be included
else
{
collected ~= sector[];
}
thunk += sector.opDollar();
}
return collected;
}
private static bool isArrayAppend(P)()
{
return __traits(isSame, P, T[]);
}
private static bool isElementAppend(P)()
{
return __traits(isSame, P, T);
}
// Append
public void opOpAssign(string op, E)(E value)
if(op == "~" && (isArrayAppend!(E) || isElementAppend!(E)))
{
static if(isArrayAppend!(E))
{
add(value);
}
else
{
add([value]);
}
}
// Takes the data, constructs a kind-of SectorType
// and adds it
private void add(T[] data)
{
// Create a new sector
SectorType sec = SectorType(data);
// Update the tracking size
this.curSize += sec.length;
// Concatenate it to the view
this.sectors ~= SectorType(data);
}
@property
public size_t length()
{
return this.curSize;
}
@property
public void length(size_t size)
{
// TODO: Need we continuously compute this?
// ... we should have a tracking field for
// ... this
size_t actualSize = computeTotalLen();
// On successful exit, update the "fake" size
scope(success)
{
this.curSize = size;
}
// Don't allow sizing up (doesn't make sense for a view)
if(size > actualSize)
{
auto r = new RangeError();
r.msg = "Cannot extend the size of a view past its total size (of all attached sectors)";
throw r;
}
// If nothing changes
else if(size == actualSize)
{
// Nothing
}
// If shrinking to zero
else if(size == 0)
{
// Just drop everything
this.sectors.length = 0;
}
// If shrinking (arbitrary)
else
{
// Sectors from left-to-right to keep
size_t sectorCnt;
// Accumulator
size_t accumulator;
foreach(SectorType sector; this.sectors)
{
accumulator += sector.length;
sectorCnt++;
if(size <= accumulator)
{
break;
}
}
this.sectors.length = sectorCnt;
}
}
}
unittest
{
View!(int) view;
assert(view.opDollar() == 0);
try
{
view[1];
assert(false);
}
catch(ArrayIndexError e)
{
assert(e.index == 1);
assert(e.length == 0);
}
view ~= [1,3,45];
assert(view.opDollar() == 3);
assert(view.length == 3);
view ~= 2;
assert(view.opDollar() == 4);
assert(view.length == 4);
assert(view[0] == 1);
assert(view[1] == 3);
assert(view[2] == 45);
assert(view[3] == 2);
assert(view[0..2] == [1,3]);
assert(view[0..4] == [1,3,45,2]);
// Update elements
view[0] = 71;
view[3] = 50;
// Set size to same size
view.length = view.length;
// Check that update is present
// and size unchanged
int[] all = view[];
assert(all == [71,3,45,50]);
// Truncate by 1 element
view.length = view.length-1;
all = view[];
assert(all == [71,3,45]);
// This should fail
try
{
view[3] = 3;
assert(false);
}
catch(RangeError e)
{
}
// This should fail
try
{
int j = view[3];
assert(false);
}
catch(RangeError e)
{
}
// Up-sizing past real size should not be allowed
try
{
view.length = view.length+1;
assert(false);
}
catch(RangeError e)
{
}
// Size to zero
view.length = 0;
assert(view.length == 0);
assert(view[] == []);
}
unittest
{
View!(int) view;
view ~= 1;
view ~= [2,3,4];
view ~= 5;
assert(view[0..5] == [1,2,3,4,5]);
// test: start <= end invariant broken
try
{
auto j = view[1..0];
assert(false);
}
catch(RangeError e)
{
}
// test: end out of bounds
try
{
auto j = view[1..view.length+1];
assert(false);
}
catch(RangeError e)
{
}
int[] d = [1,2,3];
writeln("according to dlang: ", d[1..2]);
writeln("test lekker: ", view[1..2]);
assert(view[1..2] == [2]);
writeln("test lekker: ", view[1..1]);
assert(view[1..1] == []);
}