func (dt *Table) RowsByStringIdx(colIdx int, str string, contains, ignoreCase bool) []int {
	col := dt.Cols[colIdx]
	lowstr := strings.ToLower(str)
	var idxs []int
	for i := 0; i < dt.Rows; i++ {
		val := col.StringVal1D(i)
		has := false
		switch {
		case contains && ignoreCase:
			has = strings.Contains(strings.ToLower(val), lowstr)
		case contains:
			has = strings.Contains(val, str)
		case ignoreCase:
			has = strings.EqualFold(val, str)
		default:
			has = (val == str)
		}
		if has {
			idxs = append(idxs, i)
		}
	}
	return idxs
}
RowsByStringIdx returns the list of rows that have given
string value in given column index.
if contains, only checks if row contains string; if ignoreCase, ignores case.
Use named args for greater clarity.
package etable
import (
	"fmt"
	"log"
	"math"
	"strings"
	"github.com/emer/etable/etensor"
	"github.com/goki/ki/ints"
	"github.com/goki/ki/ki"
	"github.com/goki/ki/kit"
)
type Table struct {
	Cols       []etensor.Tensor  `view:"no-inline" desc:"columns of data, as etensor.Tensor tensors"`
	ColNames   []string          `desc:"the names of the columns"`
	Rows       int               `inactive:"+" desc:"number of rows, which is enforced to be the size of the outer-most dimension of the column tensors"`
	ColNameMap map[string]int    `view:"-" desc:"the map of column names to column numbers"`
	MetaData   map[string]string `desc:"misc meta data for the table.  We use lower-case key names following the struct tag convention:  name = name of table; desc = description; read-only = gui is read-only; precision = n for precision to write out floats in csv.  For Column-specific data, we look for ColName: prefix, specifically ColName:desc = description of the column contents, which is shown as tooltip in the etview.TableView, and :width for width of a column"`
}
var KiT_Table = kit.Types.AddType(&Table{}, TableProps)
func (dt *Table) NumRows() int {
	return dt.Rows
}
func (dt *Table) IsValidRow(row int) bool {
	if row < 0 || row >= dt.Rows {
		return false
	}
	return true
}
func (dt *Table) IsValidRowTry(row int) error {
	if row < 0 || row >= dt.Rows {
		return fmt.Errorf("etable.Table Row: %v is not valid for table with Rows: %v\n", row, dt.Rows)
	}
	return nil
}
func (dt *Table) NumCols() int {
	return len(dt.Cols)
}
func (dt *Table) Col(i int) etensor.Tensor {
	return dt.Cols[i]
}
func (dt *Table) ColByName(name string) etensor.Tensor {
	i, ok := dt.ColNameMap[name]
	if !ok {
		return nil
	}
	return dt.Cols[i]
}
func (dt *Table) ColByNameTry(name string) (etensor.Tensor, error) {
	i, err := dt.ColIdxTry(name)
	if err != nil {
		return nil, err
	}
	return dt.Cols[i], nil
}
func (dt *Table) ColIdx(name string) int {
	i, ok := dt.ColNameMap[name]
	if !ok {
		return -1
	}
	return i
}
func (dt *Table) ColIdxTry(name string) (int, error) {
	i, ok := dt.ColNameMap[name]
	if !ok {
		return 0, fmt.Errorf("etable.Table ColIdx: column named: %v not found", name)
	}
	return i, nil
}
func (dt *Table) ColIdxsByNames(names []string) []int {
	nc := len(names)
	if nc == 0 {
		return nil
	}
	cidx := make([]int, nc)
	for i, cn := range names {
		cidx[i] = dt.ColIdx(cn)
	}
	return cidx
}
func (dt *Table) ColIdxsByNamesTry(names []string) ([]int, error) {
	nc := len(names)
	if nc == 0 {
		return nil, fmt.Errorf("etable.Table ColsByNamesIdxs: no column names provided")
	}
	cidx := make([]int, nc)
	var err error
	for i, cn := range names {
		cidx[i], err = dt.ColIdxTry(cn)
		if err != nil {
			return nil, err
		}
	}
	return cidx, nil
}
func (dt *Table) ColName(i int) string {
	return dt.ColNames[i]
}
func (dt *Table) UpdateColNameMap() {
	nc := dt.NumCols()
	dt.ColNameMap = make(map[string]int, nc)
	for i, nm := range dt.ColNames {
		dt.ColNameMap[nm] = i
	}
	if len(dt.ColNameMap) != nc {
		log.Printf("Warning: etable.Table has multiple columns with same name!\n%v\n", dt.ColNames)
	}
}
func (dt *Table) AddCol(tsr etensor.Tensor, name string) error {
	if !tsr.IsRowMajor() {
		return fmt.Errorf("tensor must be RowMajor organized")
	}
	dt.Cols = append(dt.Cols, tsr)
	dt.ColNames = append(dt.ColNames, name)
	dt.UpdateColNameMap()
	rows := ints.MaxInt(1, dt.Rows)
	tsr.SetNumRows(rows)
	return nil
}
func (dt *Table) DeleteColName(name string) error {
	ci, err := dt.ColIdxTry(name)
	if err != nil {
		return err
	}
	dt.DeleteColIdx(ci)
	return nil
}
func (dt *Table) DeleteColIdx(idx int) {
	dt.Cols = append(dt.Cols[:idx], dt.Cols[idx+1:]...)
	dt.ColNames = append(dt.ColNames[:idx], dt.ColNames[idx+1:]...)
	dt.UpdateColNameMap()
}
func (dt *Table) AddRows(n int) {
	dt.SetNumRows(dt.Rows + n)
}
func (dt *Table) SetNumRows(rows int) {
	dt.Rows = rows  
	rows = ints.MaxInt(1, rows)
	for _, tsr := range dt.Cols {
		tsr.SetNumRows(rows)
	}
}
func (dt *Table) SetFromSchema(sc Schema, rows int) {
	nc := len(sc)
	dt.Cols = make([]etensor.Tensor, nc)
	dt.ColNames = make([]string, nc)
	dt.Rows = rows  
	rows = ints.MaxInt(1, rows)
	for i := range dt.Cols {
		cl := &sc[i]
		dt.ColNames[i] = cl.Name
		sh := append([]int{rows}, cl.CellShape...)
		dn := append([]string{"row"}, cl.DimNames...)
		tsr := etensor.New(cl.Type, sh, nil, dn)
		dt.Cols[i] = tsr
	}
	dt.UpdateColNameMap()
}
func NewTable(name string) *Table {
	et := &Table{}
	et.SetMetaData("name", name)
	return et
}
func New(sc Schema, rows int) *Table {
	dt := &Table{}
	dt.SetFromSchema(sc, rows)
	return dt
}
func (dt *Table) Schema() Schema {
	nc := dt.NumCols()
	sc := make(Schema, nc)
	for i := range dt.Cols {
		cl := &sc[i]
		tsr := dt.Cols[i]
		cl.Name = dt.ColNames[i]
		cl.Type = tsr.DataType()
		cl.CellShape = tsr.Shapes()[1:]
		cl.DimNames = tsr.DimNames()[1:]
	}
	return sc
}
func (dt *Table) Clone() *Table {
	sc := dt.Schema()
	cp := New(sc, dt.Rows)
	for i, cl := range dt.Cols {
		ccl := cp.Cols[i]
		ccl.CopyFrom(cl)
	}
	cp.CopyMetaDataFrom(dt)
	return cp
}
func (dt *Table) AppendRows(dt2 *Table) {
	shared := false
	strow := dt.NumRows()
	for iCol := range dt.Cols {
		colName := dt.ColName(iCol)
		if dt2.ColIdx(colName) != -1 {
			if !shared {
				shared = true
				dt.AddRows(dt2.NumRows())
			}
			for iRow := 0; iRow < dt2.NumRows(); iRow++ {
				dt.CopyCell(colName, iRow+strow, dt2, colName, iRow)
			}
		}
	}
}
func (dt *Table) SetMetaData(key, val string) {
	if dt.MetaData == nil {
		dt.MetaData = make(map[string]string)
	}
	dt.MetaData[key] = val
}
func (dt *Table) CopyMetaDataFrom(cp *Table) {
	nm := len(cp.MetaData)
	if nm == 0 {
		return
	}
	if dt.MetaData == nil {
		dt.MetaData = make(map[string]string, nm)
	}
	for k, v := range cp.MetaData {
		dt.MetaData[k] = v
	}
}
const (
	Contains bool = true
	Equals = false
	IgnoreCase = true
	UseCase = false
)