|
#############################################################################
##
## tools/io.gi
## Copyright (C) 2013-2022 James D. Mitchell
##
## Licensing information can be found in the README file of this package.
##
#############################################################################
##
#############################################################################
# Internal functions - for reading and writing generators to a file
#############################################################################
# This function sets the component <decoder> of the IO file object <f> to a
# function that can be called on the file object to decode its contents. This
# is not called before anything is read from <f>.
SEMIGROUPS.FileDecoder := function(f)
local char;
if not IsBound(f!.decoder) then
Assert(1, IsBound(f!.rpos) and f!.rpos = 1);
char := IO_ReadBlock(f, 1);
if char in ["t", "p", "b"] then
# Use old style decoding
f!.decoder := x -> SEMIGROUPS.ReadGeneratorsLine(Chomp(IO_ReadLine(x)));
else
# Use IO pickling
f!.decoder := IO_Unpickle;
fi;
# Rewind, this is probably a bad idea.
f!.rpos := 1;
f!.rdata := f!.rdata + 1;
fi;
return f!.decoder;
end;
# This function converts a line in an old style "generators" file into the
# corresponding object.
SEMIGROUPS.ReadGeneratorsLine := function(line)
local i, k, out, m, deg, f, j;
i := 2;
k := 0;
out := [];
while i < Length(line) do
m := Int([line[i]]); # blocksize
deg := Int(NormalizedWhitespace(line{[i + 1 .. m + i]})); # max domain
f := line{[m + i + 1 .. i + m * (deg + 1)]};
k := k + 1;
out[k] := EmptyPlist(deg);
for j in [1 .. deg] do
Add(out[k], Int(NormalizedWhitespace(f{[(j - 1) * m + 1 .. j * m]})));
od;
i := i + m * (deg + 1) + 1;
od;
if IsEmpty(line) then
return IO_Nothing;
elif line[1] = 't' then # transformations
Apply(out, TransformationNC);
elif line[1] = 'p' then # partial perms
Apply(out, DensePartialPermNC);
elif line[1] = 'b' then # bipartitions
Apply(out, BIPART_NC);
fi;
return out;
end;
# This function converts a transformation/partial perm/bipartition collection
# <coll> in a line of an old style "generators" file and writes that line to
# the file <file>, using IO_WriteLine.
SEMIGROUPS.WriteGeneratorsLine := function(file, coll)
local append, line, deg, nrdigits, x, i;
append := function(str, pt, m)
local i, j;
i := String(pt);
for j in [1 .. m - Length(i)] do
Append(str, " ");
od;
Append(str, i);
return str;
end;
if IsTransformationCollection(coll) then
line := "t";
for x in coll do
deg := String(DegreeOfTransformation(x));
nrdigits := Length(deg);
Append(line, String(nrdigits));
Append(line, deg);
for i in [1 .. DegreeOfTransformation(x)] do
append(line, i ^ x, nrdigits);
od;
od;
elif IsPartialPermCollection(coll) then
line := "p";
for x in coll do
deg := String(DegreeOfPartialPerm(x));
nrdigits := Length(String(Maximum(DegreeOfPartialPerm(x),
CodegreeOfPartialPerm(x))));
Append(line, String(nrdigits));
append(line, deg, nrdigits);
for i in [1 .. DegreeOfPartialPerm(x)] do
append(line, i ^ x, nrdigits);
od;
od;
elif IsBipartitionCollection(coll) then
line := "b";
for x in coll do
deg := String(2 * DegreeOfBipartition(x));
nrdigits := Length(deg);
Append(line, String(nrdigits));
Append(line, deg);
for i in IntRepOfBipartition(x) do
append(line, i, nrdigits);
od;
od;
fi;
if IO_WriteLine(file, line) = fail then
# Cannot test this line
return IO_Error;
else
return IO_OK;
fi;
end;
#############################################################################
# User functions - for reading and writing generators to a file
#############################################################################
InstallGlobalFunction(ReadGenerators,
function(arg...)
local name, line_nr, file, decoder, i, obj, out;
if Length(arg) = 1 then
name := arg[1];
line_nr := 0; # Read all data
elif Length(arg) = 2 then
name := arg[1];
line_nr := arg[2];
else
ErrorNoReturn("there should be 1 or 2 arguments");
fi;
if IsString(name) then
name := UserHomeExpand(name);
file := IO_CompressedFile(name, "r");
if file = fail then
ErrorNoReturn("could not open the file ", name);
fi;
elif IsFile(name) then
file := name;
else
ErrorNoReturn("the 1st argument is not a string or a file");
fi;
if not (IsInt(line_nr) and line_nr >= 0) then
ErrorNoReturn("the 2nd argument is not a positive integer");
fi;
decoder := SEMIGROUPS.FileDecoder(file);
if line_nr <> 0 then
# When the first arg is a file this means get the line_nr-th entry from the
# last read entry and not necessarily from the start of the file.
i := 0;
repeat
i := i + 1;
obj := decoder(file);
# This is potentially more costly than necessary, since we fully
# decode every line, and then just throw it away.
until i = line_nr or obj = IO_Nothing;
if IsString(arg[1]) then
IO_Close(file);
fi;
if obj = IO_Nothing then
ErrorNoReturn("the file only has ", i - 1, " further entries");
fi;
return obj;
else
i := 0;
out := [];
obj := decoder(file);
while obj <> IO_Nothing do
i := i + 1;
out[i] := obj;
obj := decoder(file);
od;
if IsString(arg[1]) then
IO_Close(file);
fi;
return out;
fi;
end);
InstallGlobalFunction(WriteGenerators,
function(arg...)
local name, collcoll, mode, file, encoder, coll;
if Length(arg) = 2 then
name := arg[1];
collcoll := arg[2];
mode := "w";
encoder := fail;
elif Length(arg) = 3 then
if IsString(arg[3]) then
name := arg[1];
collcoll := arg[2];
mode := arg[3];
encoder := fail;
elif IsFunction(arg[3]) then
name := arg[1];
collcoll := arg[2];
mode := "w";
encoder := arg[3];
else
ErrorNoReturn("the 3rd argument is not a string or a function");
fi;
elif Length(arg) = 4 then
name := arg[1];
collcoll := arg[2];
mode := arg[3];
encoder := arg[4];
else
ErrorNoReturn("there should be 2, 3, or 4 arguments");
fi;
if mode <> "a" and mode <> "w" then
ErrorNoReturn("the 3rd argument is not \"a\" or \"w\"");
elif IsString(name) then
name := UserHomeExpand(name);
file := IO_CompressedFile(name, mode);
if file = fail then
# Cannot test this
ErrorNoReturn("couldn't open the file ", name);
fi;
elif IsFile(name) then
file := name;
else
ErrorNoReturn("the 1st argument is not a string or a file");
fi;
if not IsList(collcoll) or IsEmpty(collcoll) then
if IsString(name) then
IO_Close(file);
fi;
ErrorNoReturn("the 2nd argument is not a non-empty list");
fi;
if encoder = fail then
encoder := IO_Pickle;
elif not IsFunction(encoder) then
if IsString(name) then
IO_Close(file);
fi;
ErrorNoReturn("the 3rd or 4th argument is not a function");
fi;
# Check encoder is consistent with <coll>
if encoder = SEMIGROUPS.WriteGeneratorsLine
and ForAny(collcoll, x -> not (IsTransformationCollection(x)
or IsPartialPermCollection(x)
or IsBipartitionCollection(x))) then
if IsString(name) then
IO_Close(file);
fi;
ErrorNoReturn("the 2nd argument is incompatible with the file format");
fi;
for coll in collcoll do
if IsSemigroup(coll)
and not (IsReesMatrixSemigroup(coll)
or IsReesZeroMatrixSemigroup(coll)) then
coll := GeneratorsOfSemigroup(coll);
# We could use a smaller generating set (i.e. GeneratorsOfMonoid,
# GeneratorsOfInverseSemigroup etc) but we have no way of knowing which
# generators we wrote, so better always use GeneratorsOfSemigroup.
fi;
if encoder(file, coll) = IO_Error then
# Cannot test this line
return IO_Error;
fi;
od;
if IsString(name) then
IO_Close(file);
fi;
return IO_OK;
end);
InstallGlobalFunction(IteratorFromGeneratorsFile,
function(filename)
local file, decoder, record;
filename := UserHomeExpand(filename);
file := IO_CompressedFile(filename, "r");
if file = fail then
return fail;
fi;
decoder := SEMIGROUPS.FileDecoder(file);
record := rec(file := file,
filename := filename,
decoder := decoder,
current := decoder(file));
record.NextIterator := function(iter)
local next;
next := iter!.current;
iter!.current := iter!.decoder(iter!.file);
return next;
end;
record.IsDoneIterator := function(iter)
if iter!.current = IO_Nothing then
if not iter!.file!.closed then
IO_Close(iter!.file);
fi;
return true;
else
return false;
fi;
end;
record.ShallowCopy := function(iter)
return rec(file := IO_CompressedFile(iter!.filename, "r"),
filename := iter!.filename,
decoder := iter!.decoder,
current := iter!.decoder(~.file));
end;
return IteratorByFunctions(record);
end);
################################################################################
# User functions for multiplication tables
################################################################################
InstallGlobalFunction(ReadMultiplicationTable,
function(arg...)
local name, line_nr, file, ReadMultiplicationTableLine, i, line, lines;
if Length(arg) = 1 then
name := arg[1];
line_nr := 0;
elif Length(arg) = 2 then
name := arg[1];
line_nr := arg[2];
else
ErrorNoReturn("there should be 1 or 2 arguments");
fi;
if IsString(name) then
name := UserHomeExpand(name);
file := IO_CompressedFile(name, "r");
if file = fail then
ErrorNoReturn("could not open the file \"", name, "\"");
fi;
elif IsFile(name) then
file := name;
else
ErrorNoReturn("the 1st argument is not a string or a file");
fi;
if not (IsInt(line_nr) and line_nr >= 0) then
ErrorNoReturn("the 2nd argument is not a positive integer");
fi;
ReadMultiplicationTableLine := SEMIGROUPS.ReadMultiplicationTableLine;
if line_nr <> 0 then
i := 0;
repeat
i := i + 1;
line := IO_ReadLine(file);
until i = line_nr or line = "";
if IsString(arg[1]) then
IO_Close(file);
elif line = "" then
ErrorNoReturn("the file only has ", i - 1, " lines");
fi;
return ReadMultiplicationTableLine(Chomp(line), RootInt(Length(line)));
else
lines := IO_ReadLines(file);
if IsString(arg[1]) then
IO_Close(file);
fi;
Apply(lines, line -> ReadMultiplicationTableLine(Chomp(line),
RootInt(Length(line))));
return lines;
fi;
end);
SEMIGROUPS.ReadMultiplicationTableLine := function(line, n)
local table, vals, row, entry, i, j;
table := EmptyPlist(n);
vals := [0 .. n - 1];
for i in vals do
row := EmptyPlist(n);
for j in vals do
entry := IntChar(line[i * n + j + 1]);
if entry = 0 then
row[j + 1] := 10;
else
row[j + 1] := entry;
fi;
od;
table[i + 1] := row;
od;
return table;
end;
InstallGlobalFunction(WriteMultiplicationTable,
function(arg...)
local name, coll, mode, file, str, i, j, k, n;
if Length(arg) = 2 then
name := arg[1];
coll := arg[2];
mode := "w";
elif Length(arg) = 3 then
name := arg[1];
coll := arg[2];
mode := arg[3];
else
ErrorNoReturn("there must be 2 or 3 arguments");
fi;
if mode <> "a" and mode <> "w" then
ErrorNoReturn("the 3rd argument is not \"a\" or \"w\"");
elif IsString(name) then
name := UserHomeExpand(name);
file := IO_CompressedFile(name, mode);
if file = fail then
# Cannot test this
ErrorNoReturn("couldn't open the file ", name);
fi;
elif IsFile(name) then
file := name;
else
ErrorNoReturn("the 1st argument is not a string or a file");
fi;
for i in [1 .. Length(coll)] do
n := Size(coll[i]); # Don't assume all multiplication tables are same size.
if not (IsRectangularTable(coll[i]) and IsInt(coll[i][1][1])) then
if IsString(name) then
IO_Close(file);
fi;
ErrorNoReturn("the 2nd argument is not a collection of rectangular ",
"tables containing only integers");
elif not n < 256 then
if IsString(name) then
IO_Close(file);
fi;
ErrorNoReturn("the 2nd argument is not a collection of rectangular ",
"tables with at most 255 rows");
fi;
str := EmptyString(n ^ 2 + 1); # + 1 for newline character
for j in [1 .. n] do
for k in [1 .. n] do
if not (0 < coll[i][j][k] and coll[i][j][k] <= n) then
if IsString(name) then
IO_Close(file);
fi;
ErrorNoReturn("the 2nd argument is not a collection of ",
"rectangular tables with integer entries from [1, 2, ",
"..., n] (where n equals the number of rows of the ",
"table)");
elif coll[i][j][k] = 10 then
Add(str, '\000'); # We use CharInt(0) since CharInt(10) is newline.
else
Add(str, CharInt(coll[i][j][k]));
fi;
od;
od;
Add(str, '\n');
if IO_Write(file, str) = fail then
# Cannot test this line
return IO_Error;
fi;
od;
if IsString(name) then
IO_Close(file);
fi;
return IO_OK;
end);
InstallGlobalFunction(IteratorFromMultiplicationTableFile,
function(str)
local file, line, record, n;
file := IO_CompressedFile(UserHomeExpand(str), "r");
if file = fail then
return fail;
fi;
line := IO_ReadLine(file);
n := RootInt(Length(line));
line := SEMIGROUPS.ReadMultiplicationTableLine(Chomp(line), n);
record := rec(file := file, current := line);
record.NextIterator := function(iter)
local next;
next := iter!.current;
iter!.current := IO_ReadLine(iter!.file);
if iter!.current <> "" then
iter!.current
:= SEMIGROUPS.ReadMultiplicationTableLine(Chomp(iter!.current), n);
fi;
return next;
end;
record.IsDoneIterator := function(iter)
if iter!.current = "" then
if not iter!.file!.closed then
IO_Close(iter!.file);
fi;
return true;
else
return false;
fi;
end;
# TODO use iter here? Store things in the iterator?
record.ShallowCopy := function(_)
local file, line;
file := IO_CompressedFile(UserHomeExpand(str), "r");
line := SEMIGROUPS.ReadMultiplicationTableLine(Chomp(IO_ReadLine(file)), n);
return rec(file := file, current := line);
end;
return IteratorByFunctions(record);
end);
[ Dauer der Verarbeitung: 0.31 Sekunden
(vorverarbeitet)
]
|