go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/nodes/pkg/types/table.go (about)

     1  /*
     2  
     3  Copyright (c) 2023 - Present. Will Charczuk. All rights reserved.
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository.
     5  
     6  */
     7  
     8  package types
     9  
    10  import (
    11  	"encoding/csv"
    12  	"fmt"
    13  	"io"
    14  )
    15  
    16  // TableFromCSVReader returns a table from a reader that holds csv data.
    17  func TableFromCSVReader(data io.Reader) (*Table, error) {
    18  	output := new(Table)
    19  
    20  	csvr := csv.NewReader(data)
    21  
    22  	headers, err := csvr.Read()
    23  	if err != nil {
    24  		return nil, err
    25  	}
    26  	for _, header := range headers {
    27  		output.Columns = append(output.Columns, TableColumn{
    28  			Name: header,
    29  		})
    30  	}
    31  	var row []string
    32  	for {
    33  		row, err = csvr.Read()
    34  		if err == io.EOF {
    35  			break
    36  		}
    37  		if err != nil {
    38  			return nil, err
    39  		}
    40  		for index, colValue := range row {
    41  			if index < len(output.Columns) {
    42  				output.Columns[index].Values = append(output.Columns[index].Values, colValue)
    43  			}
    44  		}
    45  	}
    46  	return output, nil
    47  }
    48  
    49  // TableFromCSV returns a table from a csv.
    50  func TableFromCSV(data [][]string) (*Table, error) {
    51  	output := new(Table)
    52  	if len(data) == 0 {
    53  		return output, nil
    54  	}
    55  
    56  	headers := data[0]
    57  	for _, header := range headers {
    58  		output.Columns = append(output.Columns, TableColumn{
    59  			Name: header,
    60  		})
    61  	}
    62  	if len(data) > 1 {
    63  		for _, row := range data[1:] {
    64  			for index, colValue := range row {
    65  				if index < len(output.Columns) {
    66  					output.Columns[index].Values = append(output.Columns[index].Values, colValue)
    67  				}
    68  			}
    69  		}
    70  	}
    71  	return output, nil
    72  }
    73  
    74  // TableFromJSON returns a table from a json object.
    75  func TableFromJSON(obj map[string]any) (*Table, error) {
    76  	if obj == nil {
    77  		return nil, nil
    78  	}
    79  
    80  	output := new(Table)
    81  
    82  	rawColumns, ok := obj["columns"]
    83  	if !ok {
    84  		return nil, fmt.Errorf("raw table field missing: 'columns'")
    85  	}
    86  
    87  	typedRawColumns, _ := rawColumns.([]any)
    88  	for _, rawColumn := range typedRawColumns {
    89  		if typedRawColumn, ok := rawColumn.(map[string]any); ok {
    90  			tableColumn, err := TableColumnFromJSON(typedRawColumn)
    91  			if err != nil {
    92  				return nil, err
    93  			}
    94  			if tableColumn != nil {
    95  				output.Columns = append(output.Columns, *tableColumn)
    96  			}
    97  		}
    98  	}
    99  	return output, nil
   100  }
   101  
   102  // TableColumnFromJSON returns a table column from a json object.
   103  func TableColumnFromJSON(obj map[string]any) (*TableColumn, error) {
   104  	if obj == nil {
   105  		return nil, nil
   106  	}
   107  	output := new(TableColumn)
   108  	output.Name, _ = obj["name"].(string)
   109  	output.ValueType, _ = obj["value_type"].(string)
   110  
   111  	rawValues, ok := obj["values"]
   112  	if !ok {
   113  		return nil, fmt.Errorf("raw table column field missing: 'values'")
   114  	}
   115  	if typedRawValues, ok := rawValues.([]any); ok {
   116  		output.Values = append(output.Values, typedRawValues...)
   117  	}
   118  	return output, nil
   119  }
   120  
   121  // Table is a column oriented store of data.
   122  //
   123  // Specifically it breaks the data into columns, and those columns have values
   124  // which you can think of as rows.
   125  //
   126  // This makes inserting a new column very easy (and fast!), but it also means
   127  // that some operations are kind of finicky (specifically inserting rows).
   128  type Table struct {
   129  	Columns []TableColumn `json:"columns"`
   130  }
   131  
   132  // RowCount returns the maximum number of rows
   133  // in any of the columns of the table.
   134  func (t *Table) RowCount() (maxRows int) {
   135  	if len(t.Columns) == 0 {
   136  		return
   137  	}
   138  	maxRows = len(t.Columns[0].Values)
   139  	for _, c := range t.Columns[1:] {
   140  		if len(c.Values) > maxRows {
   141  			maxRows = len(c.Values)
   142  		}
   143  	}
   144  	return
   145  }
   146  
   147  // Rows returns the values of the table organized
   148  // by row (instead of by column.)
   149  func (t *Table) Rows() [][]any {
   150  	var output [][]any
   151  	if len(t.Columns) == 0 {
   152  		return output
   153  	}
   154  	var maxRows int = len(t.Columns[0].Values)
   155  	for _, c := range t.Columns[1:] {
   156  		if len(c.Values) > maxRows {
   157  			maxRows = len(c.Values)
   158  		}
   159  	}
   160  	output = make([][]any, 0, maxRows)
   161  	for rowIndex := 0; rowIndex < maxRows; rowIndex++ {
   162  		row := make([]any, 0, len(t.Columns))
   163  		for _, c := range t.Columns {
   164  			if rowIndex >= len(c.Values) {
   165  				row = append(row, nil)
   166  				continue
   167  			}
   168  			row = append(row, c.Values[rowIndex])
   169  		}
   170  		output = append(output, row)
   171  	}
   172  	return output
   173  }
   174  
   175  // Set sets the value for a given row and column.
   176  //
   177  // If the row is beyond the current row count for a given column,
   178  // nil values are added to satisfy the "density" of the table.
   179  func (t *Table) Set(row, col int, value any) error {
   180  	if col < 0 {
   181  		return fmt.Errorf("table column index invalid: %d", col)
   182  	}
   183  	if col >= len(t.Columns) {
   184  		return fmt.Errorf("table column index out of range: %d", col)
   185  	}
   186  	if row < 0 {
   187  		return fmt.Errorf("table row index invalid: %d", row)
   188  	}
   189  	vlen := len(t.Columns[col].Values)
   190  	if row >= vlen {
   191  		missing := (row + 1) - vlen
   192  		for x := 0; x < missing; x++ {
   193  			t.Columns[col].Values = append(t.Columns[col].Values, nil)
   194  		}
   195  	}
   196  	t.Columns[col].Values[row] = value
   197  	return nil
   198  }
   199  
   200  // Delete deletes a value at a given row and column.
   201  func (t *Table) Delete(row, col int) error {
   202  	if col < 0 {
   203  		return fmt.Errorf("table column index invalid: %d", col)
   204  	}
   205  	if col >= len(t.Columns) {
   206  		return fmt.Errorf("table column index out of range: %d", col)
   207  	}
   208  	if row < 0 {
   209  		return fmt.Errorf("table row index invalid: %d", row)
   210  	}
   211  	if row >= len(t.Columns[col].Values) {
   212  		return fmt.Errorf("table row index out of range: %d", row)
   213  	}
   214  	t.Columns[col].Values[row] = nil
   215  	return nil
   216  }
   217  
   218  // InsertColumn insets a new column at a given index.
   219  func (t *Table) InsertColumn(columnIndex int, newColumn TableColumn) error {
   220  	if columnIndex < 0 {
   221  		return fmt.Errorf("table column index invalid: %d", columnIndex)
   222  	}
   223  	if columnIndex > len(t.Columns) {
   224  		return fmt.Errorf("table column index out of range: %d", columnIndex)
   225  	}
   226  	if columnIndex >= len(t.Columns) {
   227  		t.Columns = append(t.Columns, newColumn)
   228  	} else {
   229  		before := t.Columns[:columnIndex]
   230  		after := t.Columns[columnIndex:]
   231  		t.Columns = append(before, append([]TableColumn{newColumn}, after...)...)
   232  	}
   233  	return nil
   234  }
   235  
   236  // DeleteColumn deletes a column at a given index.
   237  func (t *Table) DeleteColumn(columnIndex int) error {
   238  	if columnIndex < 0 {
   239  		return fmt.Errorf("table column index invalid: %d", columnIndex)
   240  	}
   241  	if columnIndex >= len(t.Columns) {
   242  		return fmt.Errorf("table column index out of range: %d", columnIndex)
   243  	}
   244  	before := t.Columns[:columnIndex]
   245  	after := t.Columns[columnIndex+1:]
   246  	t.Columns = append(before, after...)
   247  	return nil
   248  }
   249  
   250  // InsertRow inserts a row at a given index.
   251  func (t *Table) InsertRow(rowIndex int, rowValues []any) error {
   252  	if rowIndex < 0 {
   253  		return fmt.Errorf("table insert row  index invalid: %d", rowIndex)
   254  	}
   255  	for index := range t.Columns {
   256  		var rowValue any
   257  		if index < len(rowValues) {
   258  			rowValue = rowValues[index]
   259  		}
   260  		if len(t.Columns[index].Values) >= rowIndex {
   261  			t.Columns[index].Values = append(t.Columns[index].Values[:rowIndex], append([]any{rowValue}, t.Columns[index].Values[rowIndex:]...)...)
   262  		} else {
   263  			missing := (rowIndex - len(t.Columns[index].Values))
   264  			for x := 0; x < missing; x++ {
   265  				t.Columns[index].Values = append(t.Columns[index].Values, nil)
   266  			}
   267  			t.Columns[index].Values = append(t.Columns[index].Values, rowValue)
   268  		}
   269  	}
   270  	return nil
   271  }
   272  
   273  // DeleteRow deletes a row at a given index.
   274  func (t *Table) DeleteRow(rowIndex int) error {
   275  	if rowIndex < 0 {
   276  		return fmt.Errorf("table column index invalid: %d", rowIndex)
   277  	}
   278  	for columnIndex := range t.Columns {
   279  		if rowIndex >= len(t.Columns[columnIndex].Values) {
   280  			continue
   281  		}
   282  		before := t.Columns[columnIndex].Values[:rowIndex]
   283  		after := t.Columns[columnIndex].Values[rowIndex+1:]
   284  		t.Columns[columnIndex].Values = append(before, after...)
   285  	}
   286  	return nil
   287  }
   288  
   289  // ReorderColumn swaps the column order from a given old index to a given new index.
   290  func (t *Table) ReorderColumn(oldColumn, newColumn int) error {
   291  	if oldColumn < 0 {
   292  		return fmt.Errorf("table old column index invalid: %d", oldColumn)
   293  	}
   294  	if oldColumn >= len(t.Columns) {
   295  		return fmt.Errorf("table old column index out of range: %d", oldColumn)
   296  	}
   297  	if newColumn < 0 {
   298  		return fmt.Errorf("table new column index invalid: %d", newColumn)
   299  	}
   300  	if newColumn >= len(t.Columns) {
   301  		return fmt.Errorf("table new column index out of range: %d", newColumn)
   302  	}
   303  	t.Columns[oldColumn], t.Columns[newColumn] = t.Columns[newColumn], t.Columns[oldColumn]
   304  	return nil
   305  }
   306  
   307  // ReorderRow swaps the row order from a given old index to a given new index.
   308  func (t *Table) ReorderRow(oldRow, newRow int) error {
   309  	if len(t.Columns) == 0 {
   310  		return fmt.Errorf("table reorder row; cannot re-order empty table")
   311  	}
   312  	minColumnLength := len(t.Columns[0].Values)
   313  	for _, col := range t.Columns[1:] {
   314  		if len(col.Values) < minColumnLength {
   315  			minColumnLength = len(col.Values)
   316  		}
   317  	}
   318  	if oldRow < 0 {
   319  		return fmt.Errorf("table old row index invalid: %d", oldRow)
   320  	}
   321  	if oldRow >= minColumnLength {
   322  		return fmt.Errorf("table old row index out of range: %d", oldRow)
   323  	}
   324  	if newRow < 0 {
   325  		return fmt.Errorf("table new row index invalid: %d", newRow)
   326  	}
   327  	if newRow >= minColumnLength {
   328  		return fmt.Errorf("table new row index out of range: %d", newRow)
   329  	}
   330  	for index := range t.Columns {
   331  		t.Columns[index].Values[oldRow], t.Columns[index].Values[newRow] = t.Columns[index].Values[newRow], t.Columns[index].Values[oldRow]
   332  	}
   333  	return nil
   334  }
   335  
   336  // SetColumnName sets the column name for a column at a given index.
   337  func (t *Table) SetColumnName(columnIndex int, columnName string) error {
   338  	if columnIndex < 0 {
   339  		return fmt.Errorf("table column index invalid: %d", columnIndex)
   340  	}
   341  	if columnIndex >= len(t.Columns) {
   342  		return fmt.Errorf("table column index out of range: %d", columnIndex)
   343  	}
   344  	t.Columns[columnIndex].Name = columnName
   345  	return nil
   346  }
   347  
   348  // ApplyOps applies operations to the table.
   349  func (t *Table) ApplyOps(ops ...TableOp) error {
   350  	var err error
   351  	for _, op := range ops {
   352  		switch op.Type {
   353  		case TableOpTypeSet:
   354  			err = t.Set(op.Row, op.Col, op.Value)
   355  		case TableOpTypeSetColumnName:
   356  			err = t.SetColumnName(op.Col, op.Name)
   357  		case TableOpTypeDelete:
   358  			err = t.Delete(op.Row, op.Col)
   359  		case TableOpTypeInsertColumn:
   360  			err = t.InsertColumn(op.Col, TableColumn{
   361  				Name:      op.Name,
   362  				ValueType: op.ValueType,
   363  				Values:    op.Values,
   364  			})
   365  		case TableOpTypeInsertRow:
   366  			err = t.InsertRow(op.Row, op.Values)
   367  		case TableOpTypeDeleteColumn:
   368  			err = t.DeleteColumn(op.Col)
   369  		case TableOpTypeDeleteRow:
   370  			err = t.DeleteRow(op.Row)
   371  		case TableOpTypeReorderColumn:
   372  			err = t.ReorderColumn(op.Old, op.New)
   373  		case TableOpTypeReorderRow:
   374  			err = t.ReorderRow(op.Old, op.New)
   375  		default:
   376  			err = fmt.Errorf("invalid table op type: %v", op.Type)
   377  		}
   378  		if err != nil {
   379  			return err
   380  		}
   381  	}
   382  	return nil
   383  }
   384  
   385  // Range returns a subset of the table data according to a given range predicate.
   386  func (t *Table) Range(r TableRange) [][]any {
   387  	output := [][]any{}
   388  	for row := r.Top; row <= r.Bottom; row++ {
   389  		var rowValues []any
   390  		for col := r.Left; col <= r.Right; col++ {
   391  			if col < len(t.Columns) && row < len(t.Columns[col].Values) {
   392  				rowValues = append(rowValues, t.Columns[col].Values[row])
   393  				continue
   394  			}
   395  			rowValues = append(rowValues, nil)
   396  		}
   397  		output = append(output, rowValues)
   398  	}
   399  	return output
   400  }
   401  
   402  // TableColumn is a vector of data.
   403  type TableColumn struct {
   404  	Name      string `json:"name"`
   405  	ValueType string `json:"value_type"`
   406  	Values    []any  `json:"values"`
   407  }
   408  
   409  // TableOp is an operation a user can take on a table.
   410  //
   411  // NOTE(wc): The format here is kind of gross because we don't have the ability
   412  // to do the typescript thing where we discriminate types based on a string field value.
   413  //
   414  // We could use interfaces and do a bunch of careful implementing but this is fine for now.
   415  type TableOp struct {
   416  	// type determines what function to call
   417  	Type string `json:"type"`
   418  	// the column name typically
   419  	Name string `json:"name"`
   420  	// the colmn value type typically
   421  	ValueType string `json:"value_type"`
   422  
   423  	// used for various commands
   424  	Col int `json:"col"`
   425  	Row int `json:"row"`
   426  
   427  	// used for reodering
   428  	Old int `json:"old"`
   429  	New int `json:"new"`
   430  
   431  	// for setting the value
   432  	Value any `json:"value"`
   433  	// for inserting rows and columns
   434  	Values []any `json:"values"`
   435  }
   436  
   437  // TableOpType is a type of operation on a table.
   438  type TableOpType string
   439  
   440  // TableOp types.
   441  const (
   442  	TableOpTypeSet           = "set"
   443  	TableOpTypeSetColumnName = "set_column_name"
   444  	TableOpTypeDelete        = "delete"
   445  	TableOpTypeInsertColumn  = "insert_column"
   446  	TableOpTypeDeleteColumn  = "delete_column"
   447  	TableOpTypeInsertRow     = "insert_row"
   448  	TableOpTypeDeleteRow     = "delete_row"
   449  	TableOpTypeReorderColumn = "reorder_column"
   450  	TableOpTypeReorderRow    = "reorder_row"
   451  )
   452  
   453  // TableInfo is the table metdata less the actual data.
   454  type TableInfo struct {
   455  	Rows    int      `json:"rows"`
   456  	Columns []string `json:"columns"`
   457  }
   458  
   459  // TableRange holds visible coordinates.
   460  type TableRange struct {
   461  	Top    int `json:"top"`
   462  	Left   int `json:"left"`
   463  	Bottom int `json:"bottom"`
   464  	Right  int `json:"right"`
   465  }