Sophie

Sophie

distrib > Mandriva > 2010.0 > i586 > media > contrib-release > by-pkgid > 91213ddcfbe7f54821d42c2d9e091326 > files > 25

gap-system-packages-4.4.12-5mdv2010.0.i586.rpm

  
  A Some Tools for Database Handling
  
  Two aims of the tools described in this appendix are
  
  --    speeding   up   selection  functions  such  as  AllCharacterTableNames
        (CTblLib:  AllCharacterTableNames)  for  certain data libraries of GAP
        (with  not  too  many entries), in the sense that users can extend the
        list of attributes that are treated in a special way
  
  --    and  a  programmatic  extension for rendering overviews of information
        about        the        contents       of       databases,       using
        BrowseTableFromDatabaseIdEnumerator (A.2-2).
  
  The  GAP objects introduced for that are database id enumerators (see A.1-1)
  and database attributes (see A.1-2).
  
  Contrary  to the individual interfaces to the GAP manuals (see Section 6.5),
  the  GAP  bibliography (see Section 6.6), and the overviews of GAP packages,
  GAP  methods, and Conway polynomials available in GAP (see Section 6.7), the
  approach  that  will  be  described  here  assumes  a  special way to access
  database  entries.  Thus  it  depends  on  the structure of a given database
  whether  the  tools  described  here  are  useful,  or whether an individual
  interface  fits  better.  Perhaps the examples shown in Sections A.3 and A.4
  give an impression what is possible.
  
  
  A.1 GAP Objects for Database Handling
  
  
  A.1-1 Database Id Enumerators
  
  A  database  id  enumerator  is  a  record  r  with  at  least the following
  components.
  
  identifiers
        a  list  of  "identifiers"  of  the database entries, which provides a
        bijection with these entries,
  
  entry
        a  function  that  takes  r  and an entry in the identifiers list, and
        returns the corresponding database entry,
  
  attributes
        the  record  whose  components are the database attribute records (see
        Section  A.1-2)  for  r;  this components is automatically initialized
        when   r   is  created  with  DatabaseIdEnumerator  (A.1-4);  database
        attributes can be entered with DatabaseAttributeAdd (A.1-5).
  
  If  the  identifiers  list may change over the time (because the database is
  extended or corrected) then the following components are supported. They are
  used by DatabaseIdEnumeratorUpdate (A.1-7).
  
  version
        a  GAP object that describes the version of the identifiers component,
        this  can  be  for  example  a  string describing the time of the last
        change (this time need not coincide with the time of the last update);
        the  default  value  (useful  only  for  the case that the identifiers
        component is never changed) is an empty string,
  
  update
        a  function that takes r as its argument, replaces its identifiers and
        version  values  by  up-to-date  versions if necessary (for example by
        downloading the data), and returns true or false, depending on whether
        the  update  process  was  successful  or  not;  the  default value is
        ReturnTrue (Reference: ReturnTrue),
  
  The following component is optional.
  
  isSorted
        true  means  that the identifiers list is sorted w.r.t. GAP's ordering
        \<; the default is false.
  
  The  idea  behind  database  id  enumerator  objects  is that such an object
  defines  the set of data covered by database attributes (see Section A.1-2),
  it  provides  the  mapping between identifiers and the actual entries of the
  database,  and  it  defines when precomputed data of database attributes are
  outdated.
  
  
  A.1-2 Database Attributes
  
  A database attribute is a record a whose components belong to the aspects of
  defining  the  attribute,  accessing  the  attribute's  data, computing (and
  recomputing)  data,  storing  data  on files, and checking data. (Additional
  parameters  used  for creating browse table columns from database attributes
  are described in Section A.2-1.)
  
  The   following   components  are  defining,  except  description  they  are
  mandatory.
  
  idenumerator
        the database id enumerator to which the attribute a is related,
  
  identifier
        a  string  that  identifies  a  among  all database attributes for the
        underlying    database    id    enumerator    (this    is    used   by
        BrowseTableFromDatabaseIdEnumerator (A.2-2) and when the data of a are
        entered   with  DatabaseAttributeSetData  (A.1-9),  for  example  when
        precomputed values are read from a file),
  
  description
        a   string  that  describes  the  attribute  in  human  readable  form
        (currently just for convenience, the default is an empty string).
  
  The  following components are used for accessing data. Except type, they are
  optional,  but  enough  information  must  be  provided in order to make the
  database  attribute  meaningful. If an individual attributeValue function is
  available  then  this  function  decides  what  is  needed;  for the default
  function   DatabaseAttributeValueDefault   (A.1-6),  at  least  one  of  the
  components     name,     data,     datafile     must     be    bound    (see
  DatabaseAttributeValueDefault (A.1-6) for the behaviour in this case).
  
  type
        one  of  the  strings "values" or "pairs"; the format of the component
        data is different for these cases,
  
  name
        if  bound,  a  string that is the name of a GAP function such that the
        database  attribute  encodes  the  values  of  this  function  for the
        database  entries;  besides  the  computation  of  attribute values on
        demand (see DatabaseAttributeValueDefault (A.1-6)), this component can
        be used by selection functions such as OneCharacterTableName (CTblLib:
        OneCharacterTableName)     or     AllCharacterTableNames     (CTblLib:
        AllCharacterTableNames),  which  take  GAP  functions  and  prescribed
        return  values as their arguments –of course these functions must then
        be prepared to deal with database attributes.
  
  data
        if  bound,  the data for this attribute; if the component type has the
        value  "values"  then the value is a list, where the entry at position
        i,  if  bound,  belongs  to  the i-th entry of the identifiers list of
        idenumerator;  if  type is "pairs" then the value is a record with the
        components  automatic  and  nonautomatic,  and  the  values  of  these
        components  are  lists  such  that  each entry is a list of length two
        whose first entry occurs in the identifiers list of a.idenumerator and
        whose second entry encodes the corresponding attribute value,
  
  datafile
        if  bound, the absolute name of a file that contains the data for this
        attribute,
  
  attributeValue
        a  function  that takes a and an identifiers entry of its idenumerator
        value,  and returns the attribute value for this identifier; typically
        this  is  not  a  table cell data object that can be shown in a browse
        table,    cf.    the    viewValue    component;    the    default   is
        DatabaseAttributeValueDefault  (A.1-6)  (Note  that  using  individual
        attributeValue  functions,  one  can  deal  with  database  attributes
        independent  of  actually stored date, for example without precomputed
        values, such that the values are computed on demand and afterwards are
        cached.),
  
  dataDefault
        a  GAP  object  that  is  regarded  as  the  attribute value for those
        database  entries  for  which  data, datafile, and name do not provide
        values; the default value is an empty string "",
  
  eval
        if this component is bound, the value is assumed to be a function that
        takes  a  and  a value from its data component, and returns the actual
        attribute value; this can be useful if one does not want to create all
        attribute  values  in  advance,  because  this  would be space or time
        consuming;  another  possible  aspect of the eval component is that it
        may  be  used to strip off comments that are perhaps contained in data
        entries,
  
  isSorted
        if  this  component is bound to true and if type is "pairs" then it is
        assumed  that  the two lists in the data record of a are sorted w.r.t.
        GAP's ordering \<; the default is false,
  
  The  following optional components are needed for computing (or recomputing)
  data  with  DatabaseAttributeCompute  (A.1-8).  This  is  useful  mainly for
  databases which can change over the time.
  
  version
        the  GAP  object  that  is  the  version component of the idenumerator
        component at the time when the stored data were entered; this value is
        used  by  DatabaseIdEnumeratorUpdate  (A.1-7) for deciding whether the
        attribute  values  are  outdated;  if  a.datafile  is bound then it is
        assumed  that the version component is set when this file is read, for
        example in the function DatabaseAttributeSetData (A.1-9),
  
  update
        a  function  that takes a as its argument, adjusts its data components
        to  the  current  values  of a.dbidenum if necessary, sets the version
        component  to that of a.dbidenum, and returns true or false, depending
        on whether the update process was successful or not; the default value
        is ReturnTrue (Reference: ReturnTrue),
  
  neededAttributes
        a  list  of attribute identifier strings such that the values of these
        attributes  are  needed  in  the computations for the current one, and
        therefore these should be updated/recomputed in advance; it is assumed
        that  the  neededAttributes  components  of all database attributes of
        a.idenumerator  define  a  partial  ordering;  the default is an empty
        list,
  
  prepareAttributeComputation
        a function with argument a that must be called before the computations
        for the current attribute are started; the default value is ReturnTrue
        (Reference: ReturnTrue),
  
  cleanupAfterAttibuteComputation
        a  function with argument a that must be called after the computations
        for   the  current  attribute  are  finished;  the  default  value  is
        ReturnTrue (Reference: ReturnTrue), and
  
  create
        a  function  that  takes  a  database  attribute  and  an entry in the
        identifiers list of its database id enumerator, and returns either the
        entry that shall be stored in the data component, as the value for the
        given  identifier (if this value shall be stored in the data component
        of  a)  or  the dataDefault component of a (if this value shall not be
        stored); in order to get the actual attribute value, the eval function
        of  a,  if  bound, must be called with the return value. This function
        may  assume  that  the  prepareAttributeComputation  function has been
        called   in  advance,  and  that  the  cleanupAfterAttibuteComputation
        function  will be called later. The create function is not intended to
        compute  an individual attribute value on demand, use a name component
        for that. (A stored name function is used to provide a default for the
        create  function;  without  name  component,  there  is no default for
        create.)
  
  The following optional component is needed for storing data on files.
  
  string
        if  bound,  a function that takes the pair consisting of an identifier
        and  the  return value of the create function for this identifier, and
        returns  a  string  that  shall represent this value when the data are
        printed to a file; the default function returns the String (Reference:
        String) value of the second argument.
  
  The following optional component is needed for checking stored data.
  
  check
        a  function that takes a string that occurs in the identifiers list of
        the  idenumerator  record,  and  returns  true  if the attribute value
        stored  for  this  string  is reasonable, and something different from
        true  if  an error was detected. (One could argue that these tests can
        be performed also when the values are computed, but consistency checks
        may  involve  several  entries;  besides that, checking may be cheaper
        than recomputing.)
  
  
  A.1-3 How to Deal with Database Id Enumerators and Database Attributes
  
  The  idea is to start with a database id enumerator (see A.1-1), constructed
  with  DatabaseIdEnumerator (A.1-4), and to define database attributes for it
  (see A.1-2), using DatabaseAttributeAdd (A.1-5). The attribute values can be
  precomputed  and  stored  on  files, or they are computed when the attribute
  gets defined, or they are computed on demand.
  
  The  function  DatabaseAttributeCompute (A.1-8) can be used to "refresh" the
  attribute  values, that is, all values or selected values can be recomputed;
  this can be necessary for example when the underlying database id enumerator
  gets extended.
  
  In  data files, the function DatabaseAttributeSetData (A.1-9) can be used to
  fill the data component of the attribute.
  
  A.1-4 DatabaseIdEnumerator
  
  > DatabaseIdEnumerator( arec ) _____________________________________function
  Returns:  a shallow copy of the record arec, extended by default values.
  
  For  a  record  arec,  DatabaseIdEnumerator  checks  whether  the  mandatory
  components  of  a  database  id  enumerator (see Section A.1-1) are present,
  initializes the attributes component, sets the defaults for unbound optional
  components (see A.2-1), and returns the resulting record.
  
  A  special  database  attribute  (see  Section  A.1-2) with identifier value
  "self"   is   constructed   automatically   for   the   returned  record  by
  DatabaseIdEnumerator;  its attributeValue function simply returns its second
  argument  (the  identifier).  The  optional components of this attribute are
  derived  from  components of the database id enumerator, so these components
  (see A.2-1) are supported for arec. A typical use of the "self" attribute is
  to   provide   the   first   column   in   browse   tables   constructed  by
  BrowseTableFromDatabaseIdEnumerator (A.2-2).
  
  A.1-5 DatabaseAttributeAdd
  
  > DatabaseAttributeAdd( dbidenum, arec ) ___________________________function
  
  For    a    database   id   enumerator   dbidenum   and   a   record   arec,
  DatabaseAttributeAdd  checks  whether the mandatory components of a database
  attribute,  except  idenumerator,  are  present in arec (see Section A.1-2),
  sets  the idenumerator component, and sets the defaults for unbound optional
  components (see A.2-1).
  
  A.1-6 DatabaseAttributeValueDefault
  
  > DatabaseAttributeValueDefault( attr, id ) ________________________function
  Returns:  the value of the database attribute attr at id.
  
  For a database attribute attr and an entry id of the identifiers list of the
  underlying  database  id enumerator, DatabaseAttributeValueDefault takes the
  data  entry for id, applies the eval function of attr to it if available and
  returns the result.
  
  So the question is how to get the data entry.
  
  First, if the data component of attr is not bound then the file given by the
  datafile   component   of   attr,  if  available,  is  read,  and  otherwise
  DatabaseAttributeCompute  (A.1-8)  is  called; afterwards it is assumed that
  the data component is bound.
  
  The further steps depend on the type value of attr.
  
  If  the  type  value of attr is "pairs" then the data entry for id is either
  contained  in  the automatic or in the nonautomatic list of attr.data, or it
  is  given  by  the  dataDefault  value of attr. (So a perhaps available name
  function is not used to compute the value for a missing data entry.)
  
  If the type value of attr is "values" then the data entry for id is computed
  as  follows. Let n be the position of id in the identifiers component of the
  database  id  enumerator. If the n-th entry of the data component of attr is
  bound  then  take it; otherwise if the name component is bound then apply it
  to id and take the return value; otherwise take the dataDefault value.
  
  If  one  wants to introduce a database attribute where this functionality is
  not  suitable  then  another –more specific– function must be entered as the
  component attributeValue of such an attribute.
  
  A.1-7 DatabaseIdEnumeratorUpdate
  
  > DatabaseIdEnumeratorUpdate( dbidenum ) ___________________________function
  Returns:  true or false.
  
  For    a    database   id   enumerator   dbidenum   (see   Section   A.1-1),
  DatabaseIdEnumeratorUpdate  first  calls  the  update  function of dbidenum.
  Afterwards,  the  update  components  of those of its attributes records are
  called for which the version component differs from that of dbidenum.
  
  The  order in which the database attributes are updates is determined by the
  neededAttributes component.
  
  The  return  value  is  true  if  all these functions return true, and false
  otherwise.
  
  When  DatabaseIdEnumeratorUpdate  has  returned  true, the data described by
  dbidenum and its database attributes are consistent and up to date.
  
  A.1-8 DatabaseAttributeCompute
  
  > DatabaseAttributeCompute( dbidenum, attridentifier ) _____________function
  Returns:  true or false.
  
  This  function returns false if dbidenum is not a database id enumerator, or
  if   it   does   not   have  a  database  attribute  with  identifier  value
  attridentifier, or if this attribute does not have a create function.
  
  Otherwise  the  prepareAttributeComputation  function  is  called,  the data
  entries    for    the    database    attribute    are    (re)computed,   the
  cleanupAfterAttibuteComputation function is called, and true is returned.
  
  If  the type value of the database attribute is "pairs" then only the values
  of  the  data.automatic  list  are recomputed, the data.nonautomatic list is
  left  unchanged.  If  the  type  value  is  "values"  then  all  values  are
  recomputed.
  
  A.1-9 DatabaseAttributeSetData
  
  > DatabaseAttributeSetData( dbidenum, attridentifier, version, data ) function
  
  Let dbidenum be a database id enumerator (see Section A.1-1), attridentifier
  be  a  string  that  is  the  identifier  value  of  a database attribute of
  dbidenum,  data  be  the  data  list  or  record  for the database attribute
  (depending  on  its  type  value),  and version be the corresponding version
  value.
  
  DatabaseAttributeSetData  sets  the  data  and  version  components  of  the
  attribute. This function can be used for example in data files.
  
  
  A.2 Using Database Attributes for Browse Tables
  
  
  A.2-1 Browse Relevant Components of Database Attributes
  
  The  following  optional  components of database id enumerators and database
  attributes are used by BrowseTableFromDatabaseIdEnumerator (A.2-2).
  
  viewLabel
        if      bound,      a      table     cell     data     object     (see
        BrowseData.IsBrowseTableCellData   (4.2-1))   that   gives   a   short
        description  of  the  attribute,  which is used as the column label in
        browse   tables   created   with   BrowseTableFromDatabaseIdEnumerator
        (A.2-2); the default for database attributes is the name component, if
        bound,  and  otherwise  the  identifier  component;  the  default  for
        database id enumerators is the string "name",
  
  viewValue
        if  bound,  a  function  that  takes  the output of the attributeValue
        function    and    returns    a    table   cell   data   object   (see
        BrowseData.IsBrowseTableCellData (4.2-1)) that is used as the entry of
        the    corresponding    column   in   browse   tables   created   with
        BrowseTableFromDatabaseIdEnumerator  (A.2-2);  the  default  is String
        (Reference: String),
  
  viewSort
        if  bound,  a  comparison  function  that takes two database attribute
        values and returns true if the first value is regarded as smaller than
        the  second  when  the  column  corresponding  to the attribute in the
        browse   table   constructed   by  BrowseTableFromDatabaseIdEnumerator
        (A.2-2)  gets  sorted,  and  false  otherwise; the default is GAP's \<
        operation,
  
  sortParameters
        if  bound,  a  list  in  the  same  format  as  the  last  argument of
        BrowseData.SetSortParameters,   which   is   used   for   the   column
        corresponding  to  the  attribute  in  the browse table constructed by
        BrowseTableFromDatabaseIdEnumerator  (A.2-2);  the default is an empty
        list,
  
  widthCol
        if  bound,  the width of the column in the browse table constructed by
        BrowseTableFromDatabaseIdEnumerator  (A.2-2);  if  a  column  width is
        prescribed  this  way  then  the function stored in the attributeValue
        component  must  return either a list of attribute lines that fit into
        the  column or a plain string (which then gets formatted as required);
        there  is no default for this component, meaning that the column width
        is  computed  as  the maximum of the widths of the column label and of
        all entries in the column if no value is bound,
  
  align
        if  bound,  the  alignment  of  the values in the column of the browse
        table   constructed  by  BrowseTableFromDatabaseIdEnumerator  (A.2-2);
        admissible     values     are     substrings     of     "bclt",    see
        BrowseData.IsBrowseTableCellData  (4.2-1);  the  default  is right and
        vertically  centered, but note that if the viewValues function returns
        a  record  (see  BrowseData.IsBrowseTableCellData  (4.2-1))  then  the
        alignment prescribed by this record is preferred,
  
  categoryValue
        if  bound,  a  function that is similar to the viewValue component but
        may  return a different value; for example if the column in the browse
        table  belongs  to  a  property  and  the  viewValue  function returns
        something  like  "+"  or  "-", it may be useful that the category rows
        show  a  textual description of the property values; the default value
        is  the  viewValue  component;  if the value is a record then its rows
        component  is  taken  for  forming  category  rows, if the value is an
        attribute  line  (see  NCurses.IsAttributeLine  (2.2-3)) then there is
        exactly  this  category  row, and otherwise the value is regarded as a
        list  of attribute lines, which is either concatenated to one category
        row  or  turned  into  individual  category  rows,  depending  on  the
        sortParameters value.
  
  A.2-2 BrowseTableFromDatabaseIdEnumerator
  
  > BrowseTableFromDatabaseIdEnumerator( dbidenum, labelids, columnids[, header[, footer]] ) function
  Returns:  a  record  that  can be used as the input of NCurses.BrowseGeneric
            (4.3-1).
  
  For  a  database id enumerator dbidenum (see Section A.1-1), a list labelids
  and  a  nonempty  list columnids of identifier values of database attributes
  stored  in  dbidenum  which  are  used to provide row label columns and main
  table  columns,  BrowseTableFromDatabaseIdEnumerator  returns a browse table
  (see BrowseData.IsBrowseTable (4.2-3)) whose columns are given by the values
  of the specified database attributes.
  
  If  the  optional  arguments  header  and footer are given then they must be
  lists  or  functions or records such that they are admissible for the header
  and  footer  components  of  the  work  record  of  the  browse  table,  see
  BrowseData.IsBrowseTable (4.2-3).
  
  
  A.3 Example: Database Id Enumerators and Database Attributes
  
  As  an  example  for the functions introduced in this appendix, we introduce
  the  database  of  small integers. For that, we fix a positive integer n and
  consider  the  integers  from  1  to n as the entries of our database. Using
  these  integers  as  their  own  identifiers,  we  construct the database id
  enumerator.
  
  ---------------------------  Example  ----------------------------
    gap> n:= 100;;
    gap> smallintenum1:= DatabaseIdEnumerator( rec(
    >      identifiers:= [ 1 .. n ],
    >      entry:= function( dbidenum, id ) return id; end,
    >    ) );;
  ------------------------------------------------------------------
  
  Examples  of  attributes for this database are the properties whether or not
  an  integer  is  a  prime  or  a prime power. There are global GAP functions
  IsPrimeInt   (Reference:   IsPrimeInt)   and   IsPrimePowerInt   (Reference:
  IsPrimePowerInt)  for  computing  these  properties,  so we can define these
  database  attributes  via  a  name component; we choose "values" as the type
  value,  so  the  values (true or false) are stored in a list of length n for
  each of the two database attributes.
  
  ---------------------------  Example  ----------------------------
    gap> DatabaseAttributeAdd( smallintenum1, rec(
    >      identifier:= "primes",
    >      type:= "values",
    >      name:= "IsPrimeInt",
    >    ) );
    gap> DatabaseAttributeAdd( smallintenum1, rec(
    >      identifier:= "prime powers",
    >      type:= "values",
    >      name:= "IsPrimePowerInt",
    >    ) );
  ------------------------------------------------------------------
  
  Similarly, we consider the prime factors as a database attribute.
  
  ---------------------------  Example  ----------------------------
    gap> DatabaseAttributeAdd( smallintenum1, rec(
    >      identifier:= "factors",
    >      type:= "values",
    >      name:= "Factors",
    >    ) );
  ------------------------------------------------------------------
  
  Another  example of an attribute of integers is the residue modulo 11. We do
  not  want  to introduce a global GAP function for computing the value, so we
  use the create component in order to define the attribute; again, the values
  (integers from 0 to 10) are stored in a list of length n.
  
  ---------------------------  Example  ----------------------------
    gap> DatabaseAttributeAdd( smallintenum1, rec(
    >      identifier:= "residue mod 11",
    >      type:= "values",
    >      create:= function( attr, id ) return id mod 11; end,
    >    ) );
  ------------------------------------------------------------------
  
  Some integers are values of Factorial (Reference: Factorial), and we want to
  record  this  information  and show it in a browse table. For most integers,
  nothing  is stored and shown for this attribute, so we choose the type value
  "pairs"  and precompute the information for the data component. (The default
  for  the dataDefault component is an empty string, which is fine; so we need
  not prescribe this component.)
  
  ---------------------------  Example  ----------------------------
    gap> factorialdata:= function( n )
    >      local result, i, f;
    >      result:= [];  i:= 1;  f:= 1;;
    >      while f <= n do
    >        Add( result, [ f, i ] );  i:= i + 1;  f:= f * i;
    >      od;
    >      return result;
    >    end;;
    gap> DatabaseAttributeAdd( smallintenum1, rec(
    >      identifier:= "inverse factorial",
    >      type:= "pairs",
    >      data:= rec( automatic:= factorialdata( n ), nonautomatic:= [] ),
    >      isSorted:= true,
    >    ) );
  ------------------------------------------------------------------
  
  We use this setup for creating a browse table. The integers are shown as the
  first  column,  using  the "self" attribute. This attribute can be used as a
  column  of row labels (useful if we want to keep the column visible when one
  scrolls  the table to the right) or as a column in the main table (useful if
  we want to search for the values); here we choose the former possibility.
  
  ---------------------------  Example  ----------------------------
    gap> t1:= BrowseTableFromDatabaseIdEnumerator( smallintenum1,
    >           [ "self" ],
    >           [ "primes", "prime powers", "factors", "residue mod 11",
    >             "inverse factorial" ] );;
  ------------------------------------------------------------------
  
  The following session shows some of the features of the browse table.
  
  ---------------------------  Example  ----------------------------
    gap> nop:= [ 14, 14, 14, 14, 14, 14 ];;  # ``do nothing''
    gap> sample_session:= Concatenation(
    >         # categorize by the first column, expand categories, wait, reset
    >         nop, "scsc", nop, "X", nop, "!",
    >         # sort the residue column, wait, reset
    >         "scrrrso", nop, "!",
    >         # categorize by the inverse factorial column
    >         "rscsrdx", nop, "!",
    >         # and quit the application
    >         "qQ" );;
    gap> BrowseData.SetReplay( sample_session );
    gap> NCurses.BrowseGeneric( t1 );
    gap> BrowseData.SetReplay( false );
    gap> Unbind( t1.dynamic.replay );
  ------------------------------------------------------------------
  
  (Note  that  the  last  statement above is necessary to run the session more
  than  once.)  The  result is not too bad but we can improve the table, using
  the optional components of database attributes, as follows.
  
  --    The  strings  "true" and "false" shown for the Boolean valued database
        attributes  can be replaced by the perhaps more suggestive strings "+"
        and "-" (or perhaps an empty string instead of "-").
  
  --    The alignment of values inside their columns can be customized.
  
  --    When  the  browse  table is categorized by a column then the values in
        this  column  do  usually  not  provide suitable category rows; we can
        prescribe individual category values.
  
  --    The column labels can be customized.
  
  --    Where  the  lexicographic  order  is not appropriate for sorting table
        entries, we can prescribe an individual comparison function.
  
  --    Sort parameters can be customized.
  
  --    We  can  prescribe  the  width  of  a  column, and thus distribute the
        attribute  values  for this column to several rows when the values are
        too long.
  
  --    Finally,  in  the call of BrowseTableFromDatabaseIdEnumerator (A.2-2),
        we can add a header to the browse table.
  
  We  create  a new database id enumerator and the corresponding browse table,
  in order to be able to compare the behaviour of the two objects. However, we
  assume that the variables n and factorialdata are already available.
  
  ---------------------------  Example  ----------------------------
    gap> smallintenum2:= DatabaseIdEnumerator( rec(
    >      identifiers:= [ 1 .. n ],
    >      entry:= function( dbidenum, id ) return id; end,
    >      viewLabel:= "",
    >    ) );;
    gap> DatabaseAttributeAdd( smallintenum2, rec(
    >      identifier:= "primes",
    >      type:= "values",
    >      name:= "IsPrimeInt",
    >      viewLabel:= "prime?",
    >      viewValue:= value -> BrowseData.ReplacedEntry( value,
    >          [ true, false ], [ "+", "-" ] ),
    >      sortParameters:= [ "add counter on categorizing", "yes" ],
    >      align:= "c",
    >      categoryValue:= value -> BrowseData.ReplacedEntry( value,
    >          [ true, false ], [ "prime", "nonprime" ] ),
    >    ) );
    gap> DatabaseAttributeAdd( smallintenum2, rec(
    >      identifier:= "prime powers",
    >      type:= "values",
    >      name:= "IsPrimePowerInt",
    >      viewLabel:= "prime power?",
    >      viewValue:= value -> BrowseData.ReplacedEntry( value,
    >          [ true, false ], [ "+", "-" ] ),
    >      sortParameters:= [ "add counter on categorizing", "yes" ],
    >      align:= "c",
    >      categoryValue:= value -> BrowseData.ReplacedEntry( value,
    >          [ true, false ], [ "prime power", "not prime power" ] ),
    >    ) );
    gap> DatabaseAttributeAdd( smallintenum2, rec(
    >      identifier:= "factors",
    >      type:= "values",
    >      name:= "Factors",
    >      viewLabel:= "factors",
    >      viewValue:= value -> JoinStringsWithSeparator( List( value, String ),
    >                                                     " * "),
    >      widthCol:= 10,
    >    ) );
    gap> DatabaseAttributeAdd( smallintenum2, rec(
    >      identifier:= "residue mod 11",
    >      type:= "values",
    >      create:= function( attr, id ) return id mod 11; end,
    >      viewSort:= BrowseData.SortAsIntegers,
    >      categoryValue:= res -> Concatenation( String( res ), " mod 11" ),
    >    ) );
    gap> DatabaseAttributeAdd( smallintenum2, rec(
    >      identifier:= "inverse factorial",
    >      type:= "pairs",
    >      data:= rec( automatic:= factorialdata( n ), nonautomatic:= [] ),
    >      isSorted:= true,
    >      categoryValue:= function( k )
    >        if k = "" then
    >          return "(no factorial)";
    >        else
    >          return Concatenation( String( k ), "!" );
    >        fi;
    >      end,
    >    ) );
    gap> t2:= BrowseTableFromDatabaseIdEnumerator( smallintenum2,
    >           [ "self" ],
    >           [ "primes", "prime powers", "factors", "residue mod 11",
    >             "inverse factorial" ],
    >           t -> BrowseData.HeaderWithRowCounter( t, "Small integers", n ) );;
  ------------------------------------------------------------------
  
  We run the same session as with the browse table for smallintenum1.
  
  ---------------------------  Example  ----------------------------
    gap> BrowseData.SetReplay( sample_session );
    gap> NCurses.BrowseGeneric( t2 );
    gap> BrowseData.SetReplay( false );
    gap> Unbind( t2.dynamic.replay );
  ------------------------------------------------------------------
  
  Another  possibility  to  change  the  look  of  the table is to combine the
  columns  for  the  two  Boolean valued database attributes in one column, by
  showing  the string "+" for prime powers, as before, and showing this string
  in boldface red if the number in question is a prime. We implement this idea
  in  the  following  database attribute. However, note that this can be a bad
  idea  because  text  attributes  may be not supported in the user's terminal
  (see  Section  2.1-7),  or  the  user  may  have  difficulties  to see or to
  distinguish colors; also, it must be documented which information is encoded
  in  the  table,  and the column label might be not sufficient for explaining
  what  the  text  attributes  mean.  Alternatively, we could show for example
  combined  symbols  such  as  ++,  +-,  --  for  primes,  prime  powers,  and
  non-prime-powers,  respectively.  (We  see  that  besides  these issues, the
  required  GAP  code  is  more  involved than what is needed for the examples
  above.)
  
  ---------------------------  Example  ----------------------------
    gap> DatabaseAttributeAdd( smallintenum2, rec(
    >      identifier:= "primes & prime powers",
    >      type:= "values",
    >      create:= function( attr, id )
    >        if   IsPrimeInt( id ) then
    >          return 2;
    >        elif IsPrimePowerInt( id ) then
    >          return 1;
    >        else
    >          return 0;
    >        fi;
    >      end,
    >      viewLabel:= [ NCurses.attrs.BOLD + NCurses.ColorAttr( "red", -1 ),
    >                    "prime", NCurses.attrs.NORMAL, " power?" ],
    >      viewValue:= value -> BrowseData.ReplacedEntry( value,
    >          [ 0, 1, 2 ], [ "-", "+",
    >                         [ NCurses.attrs.BOLD + NCurses.ColorAttr( "red", -1 ),
    >                           true, "+",
    >                           NCurses.ColorAttr( "red", -1 ), false ] ] ),
    >      sortParameters:= [ "add counter on categorizing", "yes" ],
    >      align:= "c",
    >      categoryValue:= value -> BrowseData.ReplacedEntry( value,
    >          [ 0, 1, 2 ],
    >          [ "not prime power", "prime power, not prime", "prime" ] ),
    >    ) );
    gap> t3:= BrowseTableFromDatabaseIdEnumerator( smallintenum2,
    >           [ "self" ],
    >           [ "primes & prime powers", "residue mod 11",
    >             "inverse factorial" ],
    >           t -> BrowseData.HeaderWithRowCounter( t, "Small integers", n ) );;
    gap> sample_session2:= Concatenation(
    >         # categorize by the first column, expand categories, wait, reset
    >         nop, "scsc", nop, "X", nop, "!", "Q" );;
    gap> BrowseData.SetReplay( sample_session2 );
    gap> NCurses.BrowseGeneric( t3 );
    gap> BrowseData.SetReplay( false );
    gap> Unbind( t3.dynamic.replay );
  ------------------------------------------------------------------
  
  Now  we  want to consider the database as extendible, that is, we want to be
  able  to increase n after constructing the database attributes. For that, we
  use  n  as  the  version  value  of  the database id enumerator, and provide
  version and update components for all attributes.
  
  Again, we start the construction from scratch.
  
  ---------------------------  Example  ----------------------------
    gap> smallintenum3:= DatabaseIdEnumerator( rec(
    >     identifiers:= [ 1 .. n ],
    >     entry:= function( dbidenum, id ) return id; end,
    >     viewLabel:= "",
    >     version:= n,
    >     update:= function( dbidenum )
    >         dbidenum.identifiers:= [ 1 .. n ];
    >         dbidenum.version:= n;
    >         return true;
    >       end,
    >     ) );;
    gap> updateByUnbindData:= function( attr )
    >      Unbind( attr.data );
    >      return true;
    >    end;;
    gap> DatabaseAttributeAdd( smallintenum3, rec(
    >     identifier:= "primes",
    >     type:= "values",
    >     name:= "IsPrimeInt",
    >     viewLabel:= "prime?",
    >     viewValue:= value -> BrowseData.ReplacedEntry( value,
    >         [ true, false ], [ "+", "-" ] ),
    >     sortParameters:= [ "add counter on categorizing", "yes" ],
    >     align:= "c",
    >     categoryValue:= value -> BrowseData.ReplacedEntry( value,
    >         [ true, false ], [ "prime", "nonprime" ] ),
    >     version:= n,
    >     update:= updateByUnbindData,
    >     ) );
    gap> DatabaseAttributeAdd( smallintenum3, rec(
    >     identifier:= "prime powers",
    >     type:= "values",
    >     name:= "IsPrimePowerInt",
    >     viewLabel:= "prime power?",
    >     viewValue:= value -> BrowseData.ReplacedEntry( value,
    >         [ true, false ], [ "+", "-" ] ),
    >     sortParameters:= [ "add counter on categorizing", "yes" ],
    >     align:= "c",
    >     categoryValue:= value -> BrowseData.ReplacedEntry( value,
    >         [ true, false ], [ "prime power", "not prime power" ] ),
    >     version:= n,
    >     update:= updateByUnbindData,
    >     ) );
    gap> DatabaseAttributeAdd( smallintenum3, rec(
    >     identifier:= "factors",
    >     type:= "values",
    >     name:= "Factors",
    >     viewLabel:= "factors",
    >     viewValue:= value -> JoinStringsWithSeparator( List( value, String ),
    >                                                    " * "),
    >     widthCol:= 10,
    >     version:= n,
    >     update:= updateByUnbindData,
    >     ) );
    gap> DatabaseAttributeAdd( smallintenum3, rec(
    >     identifier:= "residue mod 11",
    >     type:= "values",
    >     create:= function( attr, id ) return id mod 11; end,
    >     viewSort:= BrowseData.SortAsIntegers,
    >     categoryValue:= res -> Concatenation( String( res ), " mod 11" ),
    >     version:= n,
    >     update:= updateByUnbindData,
    >     ) );
    gap> DatabaseAttributeAdd( smallintenum3, rec(
    >     identifier:= "inverse factorial",
    >     type:= "pairs",
    >     data:= rec( automatic:= factorialdata( n ), nonautomatic:= [] ),
    >     isSorted:= true,
    >     categoryValue:= function( k )
    >       if k = "" then
    >         return "(no factorial)";
    >       else
    >         return Concatenation( String( k ), "!" );
    >       fi;
    >     end,
    >     version:= n,
    >     update:= function( attr )
    >         attr.data.automatic:= factorialdata( n );
    >         return true;
    >       end,
    >     ) );
  ------------------------------------------------------------------
  
  Now  we  can  change the set of database entries by assigning a new value to
  the variable n, and then calling DatabaseIdEnumeratorUpdate (A.1-7).
  
  ---------------------------  Example  ----------------------------
    gap> n:= 200;;
    gap> DatabaseIdEnumeratorUpdate( smallintenum3 );
    true
    gap> t4:= BrowseTableFromDatabaseIdEnumerator( smallintenum3,
    >   [ "self" ], [ "primes", "prime powers", "factors", "residue mod 11",
    >     "inverse factorial" ],
    >   t -> BrowseData.HeaderWithRowCounter( t, "Small integers", n ) );;
    gap> BrowseData.SetReplay( sample_session );
    gap> NCurses.BrowseGeneric( t4 );
    gap> BrowseData.SetReplay( false );
    gap> Unbind( t4.dynamic.replay );
  ------------------------------------------------------------------
  
  
  A.4 Example: An Overview of the GAP Library of Transitive Groups
  
  The  example  shown  in  this section deals with GAP's Library of Transitive
  Permutation  Groups, see 'Reference: Transitive Permutation Groups'. Section
  A.4-1  introduces  a  browse table application for viewing information about
  the contents of this library, which is based on database attributes. Section
  A.4-2  introduces functions based on database attributes that can be used to
  select  groups  from the library. Finally, Section A.4-3 describes how these
  two functionalities are implemented.
  
  A.4-1 BrowseTransitiveGroupsInfo
  
  > BrowseTransitiveGroupsInfo( [arec] ) _____________________________function
  Returns:  the list of "clicked" groups.
  
  This   function  shows  the  contents  of  the  GAP  Library  of  Transitive
  Permutation Groups in a browse table.
  
  The  table  rows  correspond to the groups. If no argument is given then the
  columns of the table contain information about the degree of the permutation
  representation, group order, names for the group, primitivity, transitivity,
  and  sign.  Otherwise,  the  argument  arec  must be a record; the component
  choice of arec can be used to prescribe columns, the value of this component
  must  then  be  a  list  of  strings  that are identifier values of database
  attributes    (see    A.1-2)    for    the    IdEnumerator    component   of
  TransitiveGroupsData (A.4-3), see Section A.4-4 for examples.
  
  The return value is the list of transitive groups whose table rows have been
  "clicked" in visual mode.
  
  The  full  functionality  of  the  function NCurses.BrowseGeneric (4.3-1) is
  available.
  
  ---------------------------  Example  ----------------------------
    gap> c:= [ NCurses.keys.ENTER ];;
    gap> BrowseData.SetReplay( Concatenation(
    >        "scrrrr/5", c,     # search for transitivity 5,
    >        "nn", c,           # go to the third occurrence, click on it,
    >        "Q" ) );;          # and quit the browse table
    gap> BrowseTransitiveGroupsInfo();
    [ M(12) ]
    gap> BrowseData.SetReplay( false );
  ------------------------------------------------------------------
  
  A.4-2 TransitiveGroupsData.AllTransitiveGroups
  
  > TransitiveGroupsData.AllTransitiveGroups( fun, res[, ...] ) ______function
  > TransitiveGroupsData.OneTransitiveGroup( fun, res[, ...] ) _______function
  Returns:  the  list of groups from the GAP Library of Transitive Permutation
            Groups with the given properties, or one such group, or fail.
  
  These   functions   are   analogues   of   AllTransitiveGroups   (Reference:
  AllTransitiveGroups) and OneTransitiveGroup (Reference: OneTransitiveGroup).
  The  only  difference is that they are based on the database attributes that
  are defined in TransitiveGroupsData (A.4-3).
  
  Besides  those GAP attributes such as Size (Reference: Size) and IsPrimitive
  (Reference:   IsPrimitive)   for   which  special  support  is  provided  in
  AllTransitiveGroups  (Reference: AllTransitiveGroups) and OneTransitiveGroup
  (Reference:  OneTransitiveGroup),  database  attributes are defined for some
  other     GAP    properties,    see    A.4-4.    One    could    speed    up
  TransitiveGroupsData.AllTransitiveGroups  for  given  conditions  by  adding
  further precomputed database attributes.
  
  After  defining  a  database attribute, it is automatically used in calls to
  TransitiveGroupsData.AllTransitiveGroups. In order to make the values appear
  in  the  table shown by BrowseTransitiveGroupsInfo (A.4-1), one has to enter
  an  argument record that contains the name of the database attributes in its
  choice list.
  
  ---------------------------  Example  ----------------------------
    gap> TransitiveGroupsData.AllTransitiveGroups(
    >      NrMovedPoints, [ 5 .. 28 ],
    >      IsSimpleGroup, true, IsAbelian, true );
    [ C(5) = 5, C(7) = 7, C(11)=11, C(13)=13, C(17)=17, C(19)=19, C(23) ]
  ------------------------------------------------------------------
  
  A.4-3 TransitiveGroupsData
  
  > TransitiveGroupsData_______________________________________global variable
  
  This is a record that contains the data needed by BrowseTransitiveGroupsInfo
  (A.4-1),      TransitiveGroupsData.AllTransitiveGroups      (A.4-2),     and
  TransitiveGroupsData.OneTransitiveGroup  (A.4-2). The component IdEnumerator
  contains  a  database  id  enumerator  (see  A.1-1)  for  the GAP Library of
  Transitive Permutation Groups.
  
  Note   that   although  NrTransitiveGroups  (Reference:  NrTransitiveGroups)
  returns 1 when it is called with the argument 1, TransitiveGroup (Reference:
  TransitiveGroup)  runs  into  an error when it is called with first argument
  equal  to  1,  and  the  degree  1  is  explicitly  excluded from results of
  AllTransitiveGroups  (Reference: AllTransitiveGroups) and OneTransitiveGroup
  (Reference:  OneTransitiveGroup).  For  the  sake  of  consistency with this
  inconsistency  in  the  GAP  Library  of  Transitive  Permutation Groups, we
  exclude  the  degree 1 from TransitiveGroupsData. (Those who want to include
  this  degree can change the value of TransitiveGroupsData.MinimalDegree from
  its default value 2 to 1 in the file app/transbrowse.g of the package.)
  
  
  A.4-4 Additional Database Attributes for Transitive Groups
  
  Database  attributes  for  the  following  GAP  properties  are  defined  in
  TransitiveGroupsData     (A.4-3):    IsAbelian    (Reference:    IsAbelian),
  IsPerfectGroup   (Reference:   IsPerfectGroup),   IsSimpleGroup  (Reference:
  IsSimpleGroup), and IsSolvableGroup (Reference: IsSolvableGroup). The values
  of  these  database  attributes  are  precomputed  and  stored  in  the file
  app/transdbattr.g of the package.
  
  So   the  above  GAP  properties  have  special  support  as  conditions  in
  TransitiveGroupsData.AllTransitiveGroups             (A.4-2)             and
  TransitiveGroupsData.OneTransitiveGroup       (A.4-2),      contrary      to
  AllTransitiveGroups  (Reference: AllTransitiveGroups) and OneTransitiveGroup
  (Reference:  OneTransitiveGroup).  In  practice,  the difference is that the
  former  functions  need not construct and check those transitive groups that
  do not have the properties in question.
  
  These  database  attributes  can also be used as columns in the Browse table
  shown by BrowseTransitiveGroupsInfo (A.4-1), for example as follows.
  
  ---------------------------  Example  ----------------------------
    gap> n:= [ 14, 14, 14 ];;  # ``do nothing'' input (means timeout)
    gap> BrowseData.SetReplay( Concatenation(
    >        "scrrrsc", n, n,        # categorize by solvability info
    >        "!", n,                 # reset
    >        "scrrrrsc", n, n,       # categorize by abelianity info
    >        "Q" ) );;               # quit the browse table
    gap> BrowseTransitiveGroupsInfo( rec( choice:= [ "degree", "size",
    >      "names", "IsSolvableGroup", "IsAbelian", "IsPerfectGroup",
    >      "IsSimpleGroup" ] ) );;
    gap> BrowseData.SetReplay( false );
  ------------------------------------------------------------------
  
  The  data  in  the  file  app/transdbattr.g are lists of Booleans, which are
  encoded  as  strings via HexStringBlistEncode, and these strings are decoded
  with  BlistStringDecode. Note that for most of the groups in the library are
  not  abelian,  not  perfect, not simple, but solvable; Therefore in fact the
  inverse  values  of  the solvability info are actually stored –this yields a
  shorter string.
  
  ---------------------------  Example  ----------------------------
    gap> TransitiveGroupsData.MinimalDegree;
    2
    gap> attrs:= TransitiveGroupsData.IdEnumerator.attributes;;
    gap> oldlen:= SizeScreen();;  SizeScreen( [ 60 ] );;
    gap> HexStringBlistEncode( attrs.IsAbelian.data );
    "D88400040Es0503s0480s040406s252010s0720s0C3EsF30803s7A040\
    5s8B20s1302s0740E0sFFsFFsFFsFFsFFsFFsFFsFFsFFsFFsFFsFFs40C\
    0s1910s0B1AsFFs2B18sE74040sFFsFF"
    gap> SizeScreen( oldlen );;
  ------------------------------------------------------------------
  
  Removing  the  datafile  component  from  the four database attributes would
  yield the situation that attribute values are computed at runtime. Computing
  the  values  for  all groups in the library –for example by categorizing the
  Browse  table  by  one  of  the  columns  that  correspond to these database
  attributes–  will  require  several seconds. Note that if we assume that the
  information  about solvability is already known, the test for IsPerfectGroup
  (Reference: IsPerfectGroup) is needed only for the few nonsolvable groups in
  the  library  but  each such test is expensive; on the other hand, each test
  for  IsAbelian (Reference: IsAbelian) is cheap but such tests are needed for
  the  many solvable groups in the library, in particular these groups must be
  constructed for the tests.