bc81851998-03-23Fredrik Noring // Table.pmod by Fredrik Noring, 1998
a580e12000-09-27Fredrik Hübinette (Hubbe) #pike __REAL_VERSION__
7523392002-02-26Martin Nilsson #define TABLE_ERR(msg) error("(Table) "+msg+"\n")
bc81851998-03-23Fredrik Noring 
8c16882001-01-05Henrik Grubbström (Grubba) //! ADT.Table is a generic module for manipulating tables. //! //! Each table contains one or several columns. //! Each column is associated with a name, the column name. //! Optionally, one can provide a column type. The Table module can do a number //! of operations on a given table, like computing the sum of a column, //! grouping, sorting etc. //! //! All column references are case insensitive. A column can be referred to by //! its position (starting from zero). All operations are non-destructive. That //! means that a new table object will be returned after, for example, a sort.
7dc3162001-04-27Henrik Grubbström (Grubba) //! The table base-class.
bc81851998-03-23Fredrik Noring class table {
9eaf1d2008-06-28Martin Nilsson  protected private mapping fieldmap; protected private array table, fields, types;
bc81851998-03-23Fredrik Noring 
9eaf1d2008-06-28Martin Nilsson  protected private array|int remap(array|string|int cs, int|void forgive)
bc81851998-03-23Fredrik Noring  { array v = ({}); int ap = arrayp(cs); if(!ap) cs = ({ cs }); foreach(cs, string|int f) if(zero_type(intp(f)?f:fieldmap[lower_case(f)])) { if(!forgive) TABLE_ERR("Unknown field '"+f+"'"); } else v += ({ intp(f)?f:fieldmap[lower_case(f)] }); return ap?v:v[0]; }
563bd72004-01-11Martin Nilsson  this_program copy(array|void tab, array|void fie, array|void typ)
bc81851998-03-23Fredrik Noring  {
563bd72004-01-11Martin Nilsson  return this_program(tab||table,fie||fields,typ||types);
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method returns a binary string representation of the table. It is //! useful when one wants to store a the table, for example in a file.
bc81851998-03-23Fredrik Noring  string encode() { return encode_value(([ "table":table,"fields":fields,"types":types ])); }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method returns a table object from a binary string //! representation of a table, as returned by @[encode()].
bc81851998-03-23Fredrik Noring  object decode(string s) { mapping m = decode_value(s); return copy(m->table, m->fields, m->types); } mixed cast(string type) { switch(type) { case "array": return copy_value(table); case "string":
563bd72004-01-11Martin Nilsson  return ASCII->encode(this);
bc81851998-03-23Fredrik Noring  } }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method returns the column names for the table. The case used when //! the table was created will be returned. array(string) _indices()
bc81851998-03-23Fredrik Noring  { return copy_value(fields); }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method returns the contents of a table as a two dimensional array. //! The format is an array of rows. Each row is an array of columns. array(array) _values()
bc81851998-03-23Fredrik Noring  { return copy_value(table); }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method returns the number of rows in the table.
bc81851998-03-23Fredrik Noring  int _sizeof() { return sizeof(table); }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method reverses the rows of the table and returns a //! new table object.
bc81851998-03-23Fredrik Noring  object reverse() { return copy(predef::reverse(table), fields, types); }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method returns the contents of a given column as an array. array col(int|string column)
bc81851998-03-23Fredrik Noring  {
57e1342001-01-05Henrik Grubbström (Grubba)  return copy_value(predef::column(table, remap(column)));
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method returns the contents of a given row as an array. array row(int row_number)
bc81851998-03-23Fredrik Noring  {
8c16882001-01-05Henrik Grubbström (Grubba)  return copy_value(table[row_number]);
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! Same as @[col()]. array `[](int|string column)
bc81851998-03-23Fredrik Noring  {
6096ef2001-01-05Henrik Grubbström (Grubba)  return col(column);
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method compares two tables. They are equal if the contents //! of the tables and the column names are equal. The column name //! comparison is case insensitive. int `==(object table)
bc81851998-03-23Fredrik Noring  {
a294d21998-03-23Fredrik Noring  return (equal(Array.map(fields, lower_case),
8c16882001-01-05Henrik Grubbström (Grubba)  Array.map(indices(table), lower_case)) &&
7bffb72002-09-21Martin Stjernholm  equal(this_program::table, values(table)));
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method appends two tables. The table given as an argument will be //! added at the bottom of the current table. Note, the column names must //! be equal. The column name comparison is case insensitive. object append_bottom(object table)
bc81851998-03-23Fredrik Noring  {
8c16882001-01-05Henrik Grubbström (Grubba)  if(!equal(Array.map(indices(table), lower_case),
bc81851998-03-23Fredrik Noring  Array.map(fields, lower_case))) TABLE_ERR("Table fields are not equal.");
7bffb72002-09-21Martin Stjernholm  return copy(this_program::table+values(table), fields, types);
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method appends two tables. The table given as an argument will be //! added on the right side of the current table. Note that the number of //! rows in both tables must be equal. object append_right(object table)
bc81851998-03-23Fredrik Noring  {
7bffb72002-09-21Martin Stjernholm  if(sizeof(table) != sizeof(this_program::table))
bc81851998-03-23Fredrik Noring  TABLE_ERR("Table sizes are not equal.");
8c16882001-01-05Henrik Grubbström (Grubba)  array v = values(table);
7bffb72002-09-21Martin Stjernholm  for(int r = 0; r < sizeof(this_program::table); r++) v[r] = this_program::table[r] + v[r];
8c16882001-01-05Henrik Grubbström (Grubba)  return copy(v, fields+indices(table), types+table->all_types());
bc81851998-03-23Fredrik Noring  }
9eaf1d2008-06-28Martin Nilsson  protected private mixed op_col(function f, int|string c, mixed ... args)
bc81851998-03-23Fredrik Noring  { c = remap(c); mixed x = table[0][c]; for(int r = 1; r < sizeof(table); r++) f(x, table[r][c], @args); return x; } mixed sum_col(int|string c) { return `+(@column(table, remap(c))); } mixed average_col(int|string c) { return sum_col(c)/sizeof(table); } mixed min_col(int|string c) { return op_col(min, c); } mixed max_col(int|string c) { return op_col(max, c); }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method returns a new table object with the selected columns only. object select(int|string ... columns)
bc81851998-03-23Fredrik Noring  { array t = ({});
6096ef2001-01-05Henrik Grubbström (Grubba)  columns = remap(columns);
bc81851998-03-23Fredrik Noring  for(int r = 0; r < sizeof(table); r++)
6096ef2001-01-05Henrik Grubbström (Grubba)  t += ({ rows(table[r], columns) }); return copy(t, rows(fields, columns), rows(types, columns));
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! Like @[select()], but the given @[columns] will not be in the //! resulting table. object remove(int|string ... columns)
bc81851998-03-23Fredrik Noring  {
8c16882001-01-05Henrik Grubbström (Grubba)  return select(@remap(fields) - remap(columns, 1));
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method calls the function for each row. If the function //! returns zero, the row will be thrown away. If the function //! returns something non-zero, the row will be kept. The result //! will be returned as a new table object. object where(array(int|string)|int|string columns, function f, mixed ... args)
bc81851998-03-23Fredrik Noring  { array t = ({});
ff9f9d1998-06-18Fredrik Noring  f = f || lambda(mixed x) { return x; };
8c16882001-01-05Henrik Grubbström (Grubba)  columns = remap(arrayp(columns)?columns:({ columns }));
bc81851998-03-23Fredrik Noring  foreach(table, mixed row)
8c16882001-01-05Henrik Grubbström (Grubba)  if(f(@rows(row, columns), @args))
bc81851998-03-23Fredrik Noring  t += ({ row }); return copy(t, fields, types); }
56cd002001-10-28Martin Nilsson  //! This method calls the function @[f] for each column each time a
8c16882001-01-05Henrik Grubbström (Grubba)  //! non uniqe row will be joined. The table will be grouped by the //! columns not listed. The result will be returned as a new table object.
563bd72004-01-11Martin Nilsson  this_program group(mapping(int|string:function)|function f, mixed ... args)
bc81851998-03-23Fredrik Noring  { if(!sizeof(table))
563bd72004-01-11Martin Nilsson  return this;
bc81851998-03-23Fredrik Noring 
fbfdd91998-05-09Fredrik Noring  if(functionp(f)) {
bc8bfd1998-05-10Fredrik Noring  if(!arrayp(args[0])) args[0] = ({ args[0] });
fbfdd91998-05-09Fredrik Noring  f = mkmapping(args[0], allocate(sizeof(args[0]), f)); args = args[1..]; }
bc81851998-03-23Fredrik Noring  mapping m = ([]); array cs = remap(indices(f)); f = mkmapping(cs, values(f)); array(int) keys = indices(fields) - cs; foreach(table, array row) { string key = encode_value(rows(row, keys)); if(array a = m[key]) foreach(cs, int c) a[c] = f[c](a[c], row[c], @args); else m[key] = copy_value(row); } return copy(values(m), fields, types); }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method sums all equal rows. The table will be grouped by the //! columns not listed. The result will be returned as a new table object.
563bd72004-01-11Martin Nilsson  this_program sum(int|string ... columns)
bc81851998-03-23Fredrik Noring  {
8c16882001-01-05Henrik Grubbström (Grubba)  return group(`+, columns);
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method groups by the given columns and returns a table with only //! unique rows. When no columns are given, all rows will be unique. A new //! table object will be returned.
563bd72004-01-11Martin Nilsson  this_program distinct(int|string ... columns)
bc81851998-03-23Fredrik Noring  {
8c16882001-01-05Henrik Grubbström (Grubba)  if(!sizeof(columns))
bc81851998-03-23Fredrik Noring  return sum();
8c16882001-01-05Henrik Grubbström (Grubba)  array f = remap(fields) - remap(columns);
c6570b1999-11-24Fredrik Hübinette (Hubbe)  mapping m = mkmapping(f, Array.map(f, lambda(mixed unused)
bc81851998-03-23Fredrik Noring  { return lambda(mixed x1, mixed x2) { return x1; }; }));
fbfdd91998-05-09Fredrik Noring  return group(m);
bc81851998-03-23Fredrik Noring  }
56cd002001-10-28Martin Nilsson  //! This method calls the function @[f] for all rows in the table.
8c16882001-01-05Henrik Grubbström (Grubba)  //! The value returned will replace the values in the columns given //! as argument to map. If the function returns an array, several //! columns will be replaced. Otherwise the first column will be //! replaced. The result will be returned as a new table object. object map(function f, array(int|string)|int|string columns, mixed ... args)
bc81851998-03-23Fredrik Noring  {
8c16882001-01-05Henrik Grubbström (Grubba)  int ap = arrayp(columns);
bc81851998-03-23Fredrik Noring  array t = copy_value(table);
8c16882001-01-05Henrik Grubbström (Grubba)  if(!catch(columns = remap(ap?columns:({ columns })))) {
bc81851998-03-23Fredrik Noring  for(int r = 0; r < sizeof(t); r++) {
8c16882001-01-05Henrik Grubbström (Grubba)  mixed v = f(@rows(t[r], columns), @args);
bc81851998-03-23Fredrik Noring  if(arrayp(v)) for(int i = 0; i < sizeof(v); i++)
8c16882001-01-05Henrik Grubbström (Grubba)  t[r][columns[i]] = v[i];
bc81851998-03-23Fredrik Noring  else
8c16882001-01-05Henrik Grubbström (Grubba)  t[r][columns[0]] = v;
bc81851998-03-23Fredrik Noring  } } return copy(t, fields, types); }
9eaf1d2008-06-28Martin Nilsson  protected private this_program _sort(int is_reversed, int|string ... cs)
bc81851998-03-23Fredrik Noring  { if(!sizeof(cs))
563bd72004-01-11Martin Nilsson  return this;
bc81851998-03-23Fredrik Noring  int c; array t = copy_value(table);
35436e1999-12-21Fredrik Noring  if(!catch(c = remap(cs[-1]))) {
bc81851998-03-23Fredrik Noring  mapping m = ([]);
35436e1999-12-21Fredrik Noring  for(int r = 0; r < sizeof(t); r++) {
bc81851998-03-23Fredrik Noring  mixed d; if(!m[d = t[r][c]]) m[d] = ({ t[r] }); else m[d] += ({ t[r] }); } array i = indices(m), v = values(m);
7acbb92004-09-25Thomas Gusenleitner  if(types[c] && types[c]->type=="num") i = (array(float))i;
bc81851998-03-23Fredrik Noring  predef::sort(i, v);
35436e1999-12-21Fredrik Noring  t = (is_reversed ? predef::reverse(v) : v)*({});
bc81851998-03-23Fredrik Noring  }
35436e1999-12-21Fredrik Noring  return is_reversed ? copy(t, fields, types)->rsort(@cs[0..(sizeof(cs)-2)]) : copy(t, fields, types)->sort(@cs[0..(sizeof(cs)-2)]);
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method sorts the table in ascendent order on one or several columns //! and returns a new table object. The left most column is sorted last. Note //! that the sort is stable. //! //! @seealso //! @[rsort()] //! object sort(int|string ... columns)
35436e1999-12-21Fredrik Noring  {
8c16882001-01-05Henrik Grubbström (Grubba)  return _sort(0, @columns);
35436e1999-12-21Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! Like @[sort()], but in descending order. object rsort(int|string ... columns)
bc81851998-03-23Fredrik Noring  {
8c16882001-01-05Henrik Grubbström (Grubba)  return _sort(1, @columns);
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method truncates the table to the first @[n] rows and returns //! a new object.
fbfdd91998-05-09Fredrik Noring  object limit(int n)
bc81851998-03-23Fredrik Noring  { return copy(table[0..(n-1)], fields, types); }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method renames the column named @[from] to @[to] and //! returns a new table object. Note that @[from] can be the column //! position.
fbfdd91998-05-09Fredrik Noring  object rename(string|int from, string to)
bc81851998-03-23Fredrik Noring  {
fbfdd91998-05-09Fredrik Noring  array a = copy_value(fields); a[remap(from)] = to; return copy(table, a, types);
bc81851998-03-23Fredrik Noring  }
8c16882001-01-05Henrik Grubbström (Grubba)  //! This method gives the type for the given @[column]. //! //! If a second argument is given, the old type will be replaced //! with @[type]. The column type is only used when the table is displayed. //! The format is as specified in @[create()]. mapping type(int|string column, void|mapping type)
bc81851998-03-23Fredrik Noring  { if(query_num_arg() == 2)
8c16882001-01-05Henrik Grubbström (Grubba)  types[remap(column)] = copy_value(type); return copy_value(types[remap(column)]);
bc81851998-03-23Fredrik Noring  }
24b58e1998-06-26Fredrik Noring  array all_types() { return copy_value(types); }
cd8f622001-05-10Henrik Grubbström (Grubba)  //! The @[ADT.Table.table] class takes two or three arguments:
8c16882001-01-05Henrik Grubbström (Grubba)  //!
cd8f622001-05-10Henrik Grubbström (Grubba)  //! @param table //! The first argument is a two-dimensional array consisting of //! one array of columns per row. All rows must have the same //! number of columns as specified in @[column_names].
8c16882001-01-05Henrik Grubbström (Grubba)  //!
cd8f622001-05-10Henrik Grubbström (Grubba)  //! @param column_names //! This argument is an array of column names associated with each //! column in the table. References by column name are case insensitive. //! The case used in @[column_names] will be used when the table is //! displayed. A column can also be referred to by its position, //! starting from zero.
8c16882001-01-05Henrik Grubbström (Grubba)  //!
cd8f622001-05-10Henrik Grubbström (Grubba)  //! @param column_types //! This is an optional array of mappings. The column type //! information is only used when displaying the table. Currently, only the
cbe8c92003-04-07Martin Nilsson  //! keyword @expr{"type"@} is recognized. The type can be specified as //! @expr{"text"@} or @expr{"num"@} (numerical). Text columns are left
cd8f622001-05-10Henrik Grubbström (Grubba)  //! adjusted, whereas numerical columns are right adjusted. If a mapping //! in the array is 0 (zero), it will be assumed to be a text column. //! If @[column_types] is omitted, all columns will displayed as text.
8c16882001-01-05Henrik Grubbström (Grubba)  //!
cd8f622001-05-10Henrik Grubbström (Grubba)  //! See @[ADT.Table.ASCII.encode()] on how to display a table.
8c16882001-01-05Henrik Grubbström (Grubba)  //! //! @seealso
cd8f622001-05-10Henrik Grubbström (Grubba)  //! @[ADT.Table.ASCII.encode()]
8c16882001-01-05Henrik Grubbström (Grubba)  //! void create(array(array) table, array(string) column_names,
f076de2001-01-05Henrik Grubbström (Grubba)  array(mapping(string:string))|void column_types)
8c16882001-01-05Henrik Grubbström (Grubba)  { if(!arrayp(table))
bc81851998-03-23Fredrik Noring  TABLE_ERR("Table not array");
8c16882001-01-05Henrik Grubbström (Grubba)  if(!arrayp(column_names))
bc81851998-03-23Fredrik Noring  TABLE_ERR("Fields not array");
8c16882001-01-05Henrik Grubbström (Grubba)  if(sizeof(table) && sizeof(table[0]) != sizeof(column_names))
bc81851998-03-23Fredrik Noring  TABLE_ERR("Table and field sizes differ");
8c16882001-01-05Henrik Grubbström (Grubba)  foreach(column_names, string s)
bc81851998-03-23Fredrik Noring  if(!stringp(s)) TABLE_ERR("Field name not string");
7bffb72002-09-21Martin Stjernholm  this_program::table = copy_value(table);
8c16882001-01-05Henrik Grubbström (Grubba)  fields = copy_value(column_names); types = allocate(sizeof(column_names));
bc81851998-03-23Fredrik Noring 
8c16882001-01-05Henrik Grubbström (Grubba)  if(column_types)
bc81851998-03-23Fredrik Noring  for(int i = 0; i < sizeof(fields); i++)
8c16882001-01-05Henrik Grubbström (Grubba)  if(!column_types[i] || mappingp(column_types[i])) types[i] = copy_value(column_types[i]);
bc81851998-03-23Fredrik Noring  else TABLE_ERR("Field type not mapping");
8c16882001-01-05Henrik Grubbström (Grubba)  array(int) a = indices(allocate(sizeof(fields)));
bc81851998-03-23Fredrik Noring  fieldmap = mkmapping(Array.map(fields, lower_case), a); } } object Separated = class {
9eaf1d2008-06-28Martin Nilsson  protected private string _string(mixed x) { return (string)x; }
bc81851998-03-23Fredrik Noring  object decode(string s, void|mapping options) { string rowsep = options->rowsep||"\n"; string colsep = options->colsep||"\t"; array t = Array.map(s/rowsep, `/, colsep); return table(t[1..], t[0], options->types); } mixed encode(object t, void|mapping options) {
7b74091998-06-24Fredrik Noring  options = options || ([]);
bc81851998-03-23Fredrik Noring  string rowsep = options->rowsep||"\n"; string colsep = options->colsep||"\t"; return Array.map(({ indices(t) }) + values(t), lambda(array r, string colsep) { return Array.map(r, _string)*colsep; }, colsep)*rowsep; } }();
08be912001-04-25Henrik Grubbström (Grubba) //! @module ASCII
7dc3162001-04-27Henrik Grubbström (Grubba) //! @ignore
bc81851998-03-23Fredrik Noring object ASCII = class {
7dc3162001-04-27Henrik Grubbström (Grubba) //! @endignore
bc81851998-03-23Fredrik Noring  object decode(string s, void|mapping options) {
cb1a4e1999-12-21Fredrik Noring  // Yet to be done.
bc81851998-03-23Fredrik Noring  return 0; }
08be912001-04-25Henrik Grubbström (Grubba)  //! @decl string encode(object table, void|mapping options) //!
cbe8c92003-04-07Martin Nilsson  //! This method returns a table represented in ASCII suitable for //! human eyes. @[options] is an optional mapping. If the keyword //! @expr{"indent"@} is used with a number, the table will be //! indented with that number of space characters.
08be912001-04-25Henrik Grubbström (Grubba) 
bc81851998-03-23Fredrik Noring  string encode(object t, void|mapping options) { options = options || ([]); mapping sizes = ([]); array fields = indices(t);
2a93fc2002-10-12Henrik Grubbström (Grubba)  string indent = " " * options->indent;
bc81851998-03-23Fredrik Noring  t = t->copy(({ fields }) + values(t));
cb1a4e1999-12-21Fredrik Noring  for(int field = 0; field < sizeof(fields); field++) t = (t->map(lambda(mixed m, int field, mapping sizes) { m = (string)m; sizes[field] = max(sizeof(m), sizes[field]); return m; }, field, field, sizes)->
fbfdd91998-05-09Fredrik Noring  map(lambda(string s, string size, int num)
cb1a4e1999-12-21Fredrik Noring  { return sprintf("%"+(num?"":"-")+size+"s", s); },
fbfdd91998-05-09Fredrik Noring  field, (string)sizes[field], (t->type(field)||([]))->type == "num"));
bc81851998-03-23Fredrik Noring  string l = (indent+"-"+ Array.map(values(sizes), lambda(int n)
2a93fc2002-10-12Henrik Grubbström (Grubba)  { return "-" * n; })*"---"+"-");
bc81851998-03-23Fredrik Noring  array table = values(t); return (indent+" "+table[0]*" "+"\n"+l+"\n"+ Array.map(table[1..], lambda(array row, string indent) { return indent+" "+row*" "; },
fbfdd91998-05-09Fredrik Noring  indent)*"\n"+(sizeof(table)>1?"\n":"")+l+"\n");
bc81851998-03-23Fredrik Noring  }
7dc3162001-04-27Henrik Grubbström (Grubba) //! @ignore
bc81851998-03-23Fredrik Noring }();
7dc3162001-04-27Henrik Grubbström (Grubba) //! @endignore
bc81851998-03-23Fredrik Noring 
08be912001-04-25Henrik Grubbström (Grubba) //! @endmodule
bc81851998-03-23Fredrik Noring // Experimental object SQL = class { object decode(array t, void|mapping options) { // Yet to be done return 0; } array encode(object t, void|mapping options) { options = options||([]); string tablename = options->tablename||"sql_encode_default_table"; array queries = ({}); string fields = indices(t)*", "; foreach(values(t), array row) queries += ({ "insert into "+tablename+" ("+fields+") " "values("+ Array.map(row, lambda(mixed x) { return "'"+(string)x+"'"; })*", "+ ")" }); return queries; } }();