Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[object] Table


  • Please log in to reply
20 replies to this topic
Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009

WARNING: This is old thread. It is continued here.

Update 17.11.2011:
Latest version by hoppfrosch is here.

 
 

______________________
Original post: (obsolete code!)
I'm not sure is this for Scripts & Functions or Ask for Help forum. Anyway, this is the first time I tried to code a object for AHK_L, so I would like to hear:
- Am I doing it right?
- Am I going in right direction?
- other comments and suggestions.

Main purpose of object is to search the columns in a database for a specified string. Can set/get cells, do a search through multiple columns, add rows, etc. Not finished, just a prerelease. Here is what I have done for now;

oTable.ahk



;===Description=========================================================================
; [object] Table - prerelease by Learning one


;===Functions===========================================================================
Table_ObjCreate(FilePath,ColumnNames, ColumnsDelimiter="`t", RowsDelimiter= "`n") { ; creates object and its methods
static base := Table_Base("SetCell GetCell Save SaveAs Open Reload Search SearchColumn ChangeDelimiters Get Set Add Replace", "Table_m")
obj := Object("base", base)

IfNotExist, %FilePath%
FileAppend, , %FilePath%, UTF-8
else
{
oFile := FileOpen(FilePath, "r `n", "UTF-8")
obj.c := oFile.Read()
oFile.Close()
}
obj.cn := ColumnNames, obj.fp := FilePath
obj.cd := ColumnsDelimiter, obj.rd := RowsDelimiter
return obj
}

Table_Base(list, prefix) {
base := Object()
Loop Parse, list, %A_Space%
base[A_LoopField] := prefix A_LoopField
Return base
}

Table_mGetCell(obj, ColumnName, RowNumber) {
ColumnsDelimiter := obj.cd, RowsDelimiter := obj.rd, ColumnNames := obj.cn
Var := obj.c

ColIndex := Table_ColumnNames2Num(obj ,ColumnName)
if ColIndex =
return
Loop, parse, var, %RowsDelimiter%
{
if (A_Index = RowNumber)
{
CurRow := A_LoopField
Loop, parse, CurRow, %ColumnsDelimiter%
{
if (A_Index = ColIndex)
return A_LoopField
}
}
}
}

Table_mSetCell(obj, ColumnName, RowNumber, NewCellContents, Save=1) {
ColumnsDelimiter := obj.cd, RowsDelimiter := obj.rd, ColumnNames := obj.cn
Var := obj.c

ColIndex := Table_ColumnNames2Num(obj ,ColumnName)
if ColIndex =
return
Loop, parse, var, %RowsDelimiter%
{
if (A_Index = RowNumber)
{
CurRow := A_LoopField
Loop, parse, CurRow, %ColumnsDelimiter%
{
if (A_Index = ColIndex)
NewCurRow .= NewCellContents ColumnsDelimiter
else
NewCurRow .= A_LoopField ColumnsDelimiter
}
NewCurRow := Trim(NewCurRow, ColumnsDelimiter)
NewTableContents .= NewCurRow RowsDelimiter
}
else
NewTableContents .= A_LoopField RowsDelimiter
}

NewTableContents := Trim(NewTableContents, RowsDelimiter)
obj.c := NewTableContents
if Save
obj.Save()
}

Table_mSearchColumn(obj, ColumnsToSearch, StringToSearch) {
ColumnsDelimiter := obj.cd, RowsDelimiter := obj.rd, ColumnNames := obj.cn
Var := obj.c

ColNumbersToSearch := Table_ColumnNames2Num(obj ,ColumnsToSearch)
if ColNumbersToSearch =
return

Loop, parse, var, %RowsDelimiter%
{
CurRow := A_LoopField
if CurRow is space
continue
Loop, parse, CurRow, %ColumnsDelimiter%
{
if A_Index in %ColNumbersToSearch%
{
if A_LoopField contains %StringToSearch%
FoundRowsList .= CurRow RowsDelimiter
}
}
}
Return Trim(FoundRowsList, RowsDelimiter)
}

Table_mSearch(obj, StringToSearch) {
ColumnsDelimiter := obj.cd, RowsDelimiter := obj.rd, ColumnNames := obj.cn
Var := obj.c

Loop, parse, var, %RowsDelimiter%
{
if A_LoopField contains %StringToSearch%
FoundRowsList .= A_LoopField RowsDelimiter
}
Return Trim(FoundRowsList, RowsDelimiter)
}

Table_mSave(obj) {
Contents := obj.c, FilePath := obj.fp
oFile := FileOpen(FilePath, "w `n", "UTF-8") ; creates a new file, overwriting any existing file.
oFile.Write(Contents)
oFile.Close()
}

Table_mSaveAs(obj,NewFilePath) {
Contents := obj.c
oFile := FileOpen(NewFilePath, "w `n", "UTF-8") ; creates a new file, overwriting any existing file.
oFile.Write(Contents)
oFile.Close()
}

Table_mOpen(obj) {
FilePath := obj.fp
Run, notepad "%FilePath%"
}

Table_mReload(obj) {
FilePath := obj.fp
oFile := FileOpen(FilePath, "r `n", "UTF-8")
obj.c := oFile.Read()
oFile.Close()
}

Table_mChangeDelimiters(obj, NewColumnsDelimiter, NewRowsDelimiter, Save=1) {
ColumnsDelimiter := obj.cd, RowsDelimiter := obj.rd, ColumnNames := obj.cn
Var := obj.c
StringReplace, var, var, %ColumnsDelimiter%, %NewColumnsDelimiter%, all
StringReplace, var, var, %RowsDelimiter%, %NewRowsDelimiter%, all
obj.c := var
obj.cd := NewColumnsDelimiter, obj.rd := NewRowsDelimiter
if Save
obj.Save()
}

Table_mGet(obj) {
return obj.c
}

Table_mSet(obj, NewTableContents, Save=1) {
obj.c := NewTableContents
if Save
obj.Save()
}

Table_mAdd(obj, NewRow, Save=1) {
RowsDelimiter := obj.rd, Contents := obj.c
NewRow := Trim(NewRow, RowsDelimiter)
obj.c := Contents RowsDelimiter NewRow
if Save
obj.Save()
}

Table_mReplace(obj, SearchText, ReplaceText="", Save=1) {
Var := obj.c
StringReplace, Var, Var, %SearchText%, %ReplaceText%, All
obj.c := var
if Save
obj.Save()
return ErrorLevel
}

Table_ColumnNames2Num(obj, ColumnsToSearch) {
ColumnNames := obj.cn
Loop, parse, ColumnsToSearch, |
{
CurColName := A_LoopField
Loop, parse, ColumnNames, |
{
if (A_LoopField = CurColName)
Found .= A_Index ","
}
}
return Trim(Found, ",")
}

Testing script:

#Include, oTable.ahk

;===Auto-execute========================================================================
FilePath := A_ScriptDir "\MyTable.txt"
IfNotExist, %FilePath%
Gosub, CreateSampleFile

oTable := Table_ObjCreate(FilePath, "First name|Last name|Occupation|Notes") ; create table object from file, define column names
return


;===Hotkeys for testing==================================================================
F1::MsgBox, % oTable.SearchColumn("Occupation","Driver") ; search for "Driver" (match only in "occupation" column)
F2::MsgBox, % oTable.SearchColumn("First name|Last name","Jack") ; search for "Jack" (match in "First name" and "Last name" columns)
F3::MsgBox, % oTable.Search("Driver") ; search for "driver" (match anywhere)

F4::MsgBox, % oTable.Get() ; get object's contents and show it in MsgBox
F5::oTable.Open() ; open object's FilePath in notepad
F6::oTable.Add("Mary" A_Tab "Gills" A_Tab "Nurse" A_Tab) ; adds a new row

F7::MsgBox, % oTable.GetCell("Last name",4) ; get value from ["last name" column, 4. row]
F8::oTable.SetCell("First name",1,"Bobby") ; set ["first name" column, 1. row] to value "Bobby"


/*;===some other examples===
oTable.Reload() ; reads object's file again and stores it in object's contents
oTable.Save() ; saves object's contents in its file
oTable.SaveAs(A_ScriptDir "\MyTable backup.txt") ; saves object's contents in new file (make a backup)
oTable.ChangeDelimiters("|","#") ; change columns and rows delimiters
*/


;===Subroutines=========================================================================
CreateSampleFile:
SampleFileContents =
(
Jack%A_Tab%Gates%A_Tab%Driver%A_Tab%
Mark%A_Tab%Weber%A_Tab%Student%A_Tab%His father is a driver.
Jim%A_Tab%Tucker%A_Tab%Driver%A_Tab%
Jill%A_Tab%Lochte%A_Tab%Artist%A_Tab%
Jessica%A_Tab%Hickman%A_Tab%Student%A_Tab%
Mary%A_Tab%Jones%A_Tab%Teacher%A_Tab%Her favorite song is "Driver"
Lenny%A_Tab%Stark%A_Tab%Driver%A_Tab%
Jack%A_Tab%Black%A_Tab%Actor%A_Tab%
Tony%A_Tab%Jackman%A_Tab%Surfer%A_Tab%
Jonny%A_Tab%Poor%A_Tab%Beggar%A_Tab%
)

oFile := FileOpen(FilePath, "w `n", "UTF-8")
oFile.Write(SampleFileContents)
oFile.Close()
return



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ExitApp
Pause::
Suspend
Pause,,1
return

Escape::
Suspend
ExitApp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009
3 days passed, 90 views, no replies. As I said, this is the first time I tried to code a object for AHK_L, so a really need some critics (both positive and negative) before I go on...

guest3456
  • Guests
  • Last active:
  • Joined: --

3 days passed, 90 views, no replies. As I said, this is the first time I tried to code a object for AHK_L, so a really need some critics (both positive and negative) before I go on...


can't help you out, but i really wish there was some type of standardized way/documentation of dealing with objects in AHK_L, seems like there is a lot of confusion and i certainly have no resolution

a4u
  • Guests
  • Last active:
  • Joined: --

... really need some critics (both positive and negative) before I go on...

OK - you presented your object in somewhat of a cunfusing manner. An object (specifically one that has class-like definition) is somewhat like a function library - I'd suggest presenting the library separate from the example usage (ex. [AHK_L] Arrays)

Also, you might want to mention that you can just run the script - you don't have to create an external file - since the script does that for you.

Otherwise, looks good (haven't tried it though :wink: ). Personally I like to group my object together with brackets, and indent the methods (but that's just code appearance).

majkinetor
  • Moderators
  • 4512 posts
  • Last active: Jul 29 2016 12:40 AM
  • Joined: 24 May 2006
   obj.cn := ColumnNames, obj.fp := FilePath
   obj.cd := ColumnsDelimiter, obj.rd := RowsDelimiter
could be done shorter as
obj := Object("base", base, "cn", ColumnNames, "fp", FilePath, cd .....)


I also don't see a reson for

   Contents := obj.c, FilePath := obj.fp
   oFile := FileOpen(FilePath, "w `n", "UTF-8")   ; creates a new file, overwriting any existing file.
   oFile.Write(Contents)
   oFile.Close()

When it coudl be done with object references in the first place. You should fetch object fields into local variables only if they are used repeatdly, altho I think this level of optimisation is usually not necessary.

So, above should definitelly be:

f := FileOpen(Fobj.fp, "w `n", "UTF-8"), f.Write(obj.c), f.Close()

This is very apparent in your code like:

Var := obj.c
   StringReplace, Var, Var, %SearchText%, %ReplaceText%, All
   obj.c := var

This is not very mutch in OO spirit but it might has its merrits in AHK due to simplicity:
oTable.Add("Mary" A_Tab   "Gills" A_Tab "Nurse" A_Tab)

In OO inteface, table should have row objects. The semantically valid way would be to crate row object, populate it, and then add it:

   oRow := oTable.NewRow() ;returns row object with the same scheme as table 
   oRow.field[1] := "Mary", oRow.field[2] := "Gillls", oRow.field[3] := "Nurse"
  ; or oRow.AddFields("Mary", "Gills", "Nurse")
   oTable.AddRow(oRow)
  ; or much simpler: 
   oTable.AddRow( oTable.NewRow().AddFields("Mary", "Gills", "Nurse") )
  ; or even simpler
   oTable.AddRow("Mary", "Gills", "Nurse")    
   ; makes row object and populate its fields with given strings, then adds row object to the table


In
Table.SetCell("First name",1,"Bobby")   ; set ["first name" column, 1. row] to value "Bobby"
It would be probably better if you could use:

oTable[1,"First name"] := "Bobby"
or more verbose
oRow := oTable[1]
 oRow["First Name"] := "Bobby"

Those are just some quick thoughts based on how actual OO libraries around are usually implemented.
Now, given the fact that AHKL is not OO language stricty speaking some of those might be harder to implement then in mainstreem languages so even in this state its not that bad. The most important note is the last one - you are changing specific object so the change should be going trough it, not trough its "class method" how its usually called in pure OO langs.

At the end, I don't think that file is necessary - you should work with string only and if user needs to save to file he can do it in one line of code. It also limits the usability of the "class"
Posted Image

Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009
Thank you for feedback. :)

@majkinetor: regarding rows as objects - should table object be constructed like this?
Table object members
	; Properties
		ColumnsDelimiter, RowsDelimiter, FilePath, Encoding, etc.
	; Methods
		SearchColumn, Save, SaveAs, Open, Reload, etc.
	
	Rows object members
		; Properties
			1 ; 1. row contents
			2 ; 2.  row contents
			; etc (row object as array)
		; Methods
			Insert		; (built in AHK_L) 
			Remove		; (built in AHK_L)
; syntax examples
var := oTable.Rows.7				; get 7. row contents
oTable.Rows.7 := "seven"		; set 7. row contents


majkinetor
  • Moderators
  • 4512 posts
  • Last active: Jul 29 2016 12:40 AM
  • Joined: 24 May 2006
oTable.Rows.7 := "seven"
is incorrect because Rows.7 means 7th row, which is row object, not a field.

So, it should be

oTable.Rows.7.1 := "seven.1"
   oTable.Rows.7.2 := "seven.2" ;should rise an error if there is no 2nd column, so "field" should be "property" with getter and setter functions.
 msgbox % oTable.Rows.Count

ColumnsDelimiter, RowsDelimiter

This is redudant. ColumnDelimiter is RowDelimiter once you adopt the convention that column names are specified as first row.

Rows object members
; Properties
1 ; 1. row contents
2 ; 2. row contents


Ideally, its represented as two properties:
row[1] or row["Coloumn 1 Name"]

Again, I don't think you need to work with files. At the end, table objects usually have extra functions like Writeto(FileName, encoding) or LoadFrom(FileName) so those don't need to be part of interface. The only reason would be real time savings handled by object itself but thats not very frequent operation.

SearchColumn is perhaps better as Search with aditional parameter (search direction). This is how its done in VBA for Excel for example.

Rows usually have Append & Insert functions along with Delete.
You should also have an option to add/remove Column (or at least append)
Posted Image

Tuncay
  • Members
  • 1945 posts
  • Last active: Feb 08 2015 03:49 PM
  • Joined: 07 Nov 2006

3 days passed, 90 views, no replies.

Thats nothing against this: http://www.autohotke...p=249830#249830

You want replies post broken script Wink you want views post a good one


... sorry for beeing off-topic.

No signature.


Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009
@majkinetor, I can't figure out how to create fields like

oTable.Rows.7.1
oTable.Rows.7.2
etc.

EDIT: I figured it out :D

@Tuncay: that's really very demotivating, especially fincs' first reply :lol:

Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009
Here is the beginning of new construction - rows as objects, fields as elements. I guess it's more in OO spirit now. Syntax:
MsgBox % oTable.Rows.3.2	; get value from [3. row, 2. column]
oTable.Rows.1.1 := "Bobby"	; set [1. row, 1. column] to "Bobby"
I'm unsure about SearchColumn method. Should I return search results as:
- object - current solution in code below - sounds more "proper" but more complicated - not in AHK simplicity spirit
- string - simpler solution for a user
Variable =		; column names are specified as first row
(
First name%A_Tab%Last name%A_Tab%Occupation%A_Tab%Notes
Jack%A_Tab%Gates%A_Tab%Driver%A_Tab%
Mark%A_Tab%Weber%A_Tab%Student%A_Tab%His father is a driver.
Jim%A_Tab%Tucker%A_Tab%Driver%A_Tab%
Jill%A_Tab%Lochte%A_Tab%Artist%A_Tab%
Jessica%A_Tab%Hickman%A_Tab%Student%A_Tab%
Mary%A_Tab%Jones%A_Tab%Teacher%A_Tab%Her favorite song is "Driver".
Lenny%A_Tab%Stark%A_Tab%Driver%A_Tab%
Jack%A_Tab%Black%A_Tab%Actor%A_Tab%His wife is artist.
Tony%A_Tab%Jackman%A_Tab%Surfer%A_Tab%
Jonny%A_Tab%Poor%A_Tab%Beggar%A_Tab%
)

oTable := Table_ObjCreate(Variable)
;MsgBox % oTable.Rows.3.2	; get value from [3. row, 2. column]
;MsgBox % oTable.Rows.1.1 := "Bobby"	; set [1. row, 1. column] to "Bobby"


; search "Last name" column for containing "man" string
oFound := oTable.SearchColumn("Last name", "man")
;MsgBox % oFound.1.3
MsgBox % Obj2String(oFound)


; search "Occupation" and "Notes" columns for containing "Driver" or "artist" strings. "|" is query delimiter.
oFound2 := oTable.SearchColumn("Occupation|Notes", "Driver|artist")
MsgBox % Obj2String(oFound2)



;===Helper function======
Obj2String(obj, ColumnsDelimiter="`t", RowsDelimiter= "`n") {
	For k,v in obj
	{
		For k2,v2 in obj[k]
		FoundRow .= v2 ColumnsDelimiter
		FoundRow := Trim(FoundRow, ColumnsDelimiter)
		Found .= FoundRow RowsDelimiter
		FoundRow =
	}
	return Trim(Found, RowsDelimiter)
}

;===oTable Functions====================================================================
Table_ObjCreate(InputVariable, ColumnsDelimiter="`t", RowsDelimiter= "`n") {	; not finished
	
	static base := Table_Base("SearchColumn", "Table_m")
	oTable := Object("base", base)
	oRows := Object()
	oColumnNames := Object()
	
	Loop, parse, InputVariable, %RowsDelimiter%
	{
		CurRow := A_LoopField
		if A_index = 1	; column names are specified as first row
		{
			Loop, parse, CurRow, %ColumnsDelimiter%
			oColumnNames.Insert(A_LoopField)
			continue
		}
		RowNum := A_index-1
		%RowNum% := Object()
		Loop, parse, CurRow, %ColumnsDelimiter%
		%RowNum%.Insert(A_LoopField)
		oRows.Insert(%RowNum%)
	}
	
	oTable.Rows := oRows, oTable.ColumnNames := oColumnNames
	return oTable
}

Table_Base(list, prefix) {
	base := Object()
	Loop Parse, list, %A_Space%
	base[A_LoopField] := prefix A_LoopField
	Return base
}

Table_mSearchColumn(oTable, ColumnsToSearch, StringToSearch) {	; not finished
	; implemented "if field contains" for now. To add: exact match, regEx match, multiple filters.
	oFound := Object()
	ColumnsToSearch := Table_ColumnNames2Num(oTable, ColumnsToSearch)
	StringReplace, StringToSearch, StringToSearch, `,, `,`,, all
	StringReplace, StringToSearch, StringToSearch, |, `,, all
	For k in oTable.Rows
	{
		Loop, parse, ColumnsToSearch, |
		{
			CurField := oTable.Rows[k][A_LoopField]
			if CurField contains %StringToSearch%
			{
				%k% := Object()	
				For	k2,v2 in oTable.Rows[k]
				%k%.Insert(v2)
				oFound.Insert(%k%)
			}
		}
	}
	return oFound	; I'm returning search results as a object - is that a overkill? - not in AHK simplicity spirit...
}

Table_ColumnNames2Num(oTable, ColumnsToSearch) {
	StringReplace, ColumnsToSearch, ColumnsToSearch, `,, `,`,, all
	StringReplace, ColumnsToSearch, ColumnsToSearch, |, `,, all
	For k,v in oTable.ColumnNames
	{
		if v in %ColumnsToSearch%
		Found .= k "|"
	}
	return Trim(Found, "|")
}


majkinetor
  • Moderators
  • 4512 posts
  • Last active: Jul 29 2016 12:40 AM
  • Joined: 24 May 2006
It is much better now.

I'm unsure about SearchColumn method. Should I return search results as:
- object - current solution in code below - sounds more "proper" but more complicated - not in AHK simplicity spirit
- string


Definitely object. You should add to all objects u created (table, row, search [i.e. filtered_table]) ToString() function which will :

- if casted on table or search object return back string used in constructor (not the same string ofc, but the most actual one, perhaps with some other changes [u could let user change delimiter for instance]).
- if casted on row return back only that row as string

So, for the end user its the same. To get the string he would need only to specify:
oFound := oTable.SearchColumn("Last name", "man").ToString()


Table and search objects need count property.

You could also think about more filtering options, for instance regex filtering of table, returning subset etc.. altho now with object oriented interface its easy to get :

oFilteredTable := oTable.FromScheme() ; create empty table with the same number and names of cols and same delimiter
  loop, % oTable.Rows.Count
        if oTable.Rows[i,1] ~= "^[0-9]+$"
            oFilteredTable.Rows.Add(oTable.Rows[i])

 msgbox %  oFilteredTable.ToString()

Posted Image

Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009
Little progress, but new questions...
Variable =		; column names are specified as first row
(
First name%A_Tab%Last name%A_Tab%Occupation%A_Tab%Notes
Jack%A_Tab%Gates%A_Tab%Driver%A_Tab%
Mark%A_Tab%Weber%A_Tab%Student%A_Tab%His father is a driver.
Jim%A_Tab%Tucker%A_Tab%Driver%A_Tab%
Jill%A_Tab%Lochte%A_Tab%Artist%A_Tab%
Jessica%A_Tab%Hickman%A_Tab%Student%A_Tab%
Mary%A_Tab%Jones%A_Tab%Teacher%A_Tab%Her favorite song is "Driver".
Lenny%A_Tab%Stark%A_Tab%Driver%A_Tab%
Jack%A_Tab%Black%A_Tab%Actor%A_Tab%His wife is artist.
Tony%A_Tab%Jackman%A_Tab%Surfer%A_Tab%
Jonny%A_Tab%Poor%A_Tab%Beggar%A_Tab%
)


oTable := Table_ObjCreate(Variable)	; create table object
;MsgBox % oTable.Rows.3.2	; get value from [3. row, 2. column]
;MsgBox % oTable.Rows.1.1 := "Bobby"	; set [1. row, 1. column] to "Bobby"

MsgBox % String1 := oTable.ToString()	; convert whole table back to string.


; search "Last name" column for containing "man" string
MsgBox % String2 := oTable.SearchColumn("Last name", "man").ToString()	;  convert search results to string.
;oFound := oTable.SearchColumn("Last name", "man")	; store search results as object
;MsgBox % oFound.1.3	; get [1. row, 3. column] in search results


; search "Occupation" and "Notes" columns for containing "Driver" or "artist" strings. "|" is query delimiter.
oFound2 := oTable.SearchColumn("Occupation|Notes", "Driver|artist")		; store search results as object




;===oTable Functions====================================================================
Table_ObjCreate(InputVariable, ColumnsDelimiter="`t", RowsDelimiter= "`n") {	; not finished
	
	static base := Table_Base("SearchColumn ToString", "Table_m")
	oTable := Object("base", base, "ColumnsDelimiter", ColumnsDelimiter, "RowsDelimiter", RowsDelimiter)
	oRows := Object()
	oColumnNames := Object()
	
	Loop, parse, InputVariable, %RowsDelimiter%
	{
		CurRow := A_LoopField
		if A_index = 1	; column names are specified as first row
		{
			Loop, parse, CurRow, %ColumnsDelimiter%
			oColumnNames.Insert(A_LoopField)
			continue
		}
		RowNum := A_index-1
		%RowNum% := Object()
		Loop, parse, CurRow, %ColumnsDelimiter%
		%RowNum%.Insert(A_LoopField)
		oRows.Insert(%RowNum%)
	}
	
	oTable.Rows := oRows, oTable.ColumnNames := oColumnNames
	return oTable
}

Table_Base(list, prefix) {
	base := Object()
	Loop Parse, list, %A_Space%
	base[A_LoopField] := prefix A_LoopField
	Return base
}

Table_mToString(oTable, ColumnsDelimiter="", RowsDelimiter= "") {
	if ColumnsDelimiter =
	ColumnsDelimiter := oTable.ColumnsDelimiter
	if RowsDelimiter =
	RowsDelimiter := oTable.RowsDelimiter
	
	For k,v in oTable.Rows
	{
		For k2,v2 in oTable.Rows[k]
		FoundRow .= v2 ColumnsDelimiter
		FoundRow := RTrim(FoundRow, ColumnsDelimiter)
		Found .= FoundRow RowsDelimiter
		FoundRow =
	}
	return RTrim(Found, RowsDelimiter)
}

Table_mSearchColumn(oTable, ColumnsToSearch, StringToSearch) {	; not finished
	; implemented "if field contains" for now. To add: exact match, regEx match, multiple filters.
	static base := Table_Base("ToString", "Table_mFound")
	
	oFound := Object("base", base)
	ColumnsToSearch := Table_ColumnNames2Num(oTable, ColumnsToSearch)
	StringReplace, StringToSearch, StringToSearch, `,, `,`,, all
	StringReplace, StringToSearch, StringToSearch, |, `,, all
	For k in oTable.Rows
	{
		Loop, parse, ColumnsToSearch, |
		{
			CurField := oTable.Rows[k][A_LoopField]
			if CurField contains %StringToSearch%
			{
				%k% := Object()		; example: 1 := Object()
				For	k2,v2 in oTable.Rows[k]
				%k%.Insert(v2)
				oFound.Insert(%k%)
			}
		}
	}
	return oFound
}

Table_mFoundToString(oFound, ColumnsDelimiter="", RowsDelimiter= "") {	; not finished
	if ColumnsDelimiter =
	ColumnsDelimiter := "`t"	; bad improvisation!
	if RowsDelimiter =
	RowsDelimiter := "`n"	; bad improvisation!
	
	For k,v in oFound
	{
		For k2,v2 in oFound[k]
		FoundRow .= v2 ColumnsDelimiter
		FoundRow := RTrim(FoundRow, ColumnsDelimiter)
		Found .= FoundRow RowsDelimiter
		FoundRow =
	}
	return RTrim(Found, RowsDelimiter)
}

Table_ColumnNames2Num(oTable, ColumnsToSearch) {
	StringReplace, ColumnsToSearch, ColumnsToSearch, `,, `,`,, all
	StringReplace, ColumnsToSearch, ColumnsToSearch, |, `,, all
	For k,v in oTable.ColumnNames
	{
		if v in %ColumnsToSearch%
		Found .= k "|"
	}
	return RTrim(Found, "|")
}
I want that ColumnsDelimiter and RowsDelimiter are optional in each ToString method. If not specified in method call, delimiters from constructor will be used.

Case 1: oTable.ToString()
no problems here - delimiters are optional. If not specified, oTable.ColumnsDelimiter and oTable.RowsDelimiter apply.
so I can call
MsgBox % oTable.ToString()	; delimiters from constructor used
; or
MsgBox % oTable.ToString("|","#")	; custom delimiters used

Case 2: oFound.ToString()
How can I access to oTable.ColumnsDelimiter and oTable.RowsDelimiter which are used in constructor when I'm inside oFound's ToString method?
Where to store those delimiters so I can access them? Maybe in storage function, so all objects will have access to delimiters? (like in radial menu codes) But than, they are not real properties... I also think it's wrong to create oFound.ColumnsDelimiter and oFound.RowsDelimiter keys, because oFonud elements should be just found rows IMO...
Table_Reg(variable, value="") { 	; Register (storage) 
	static
	if (value = "") {	; return variable's value
		yaqxswcdevfr := %variable%
		Return yaqxswcdevfr
	}
	Else			; set value to variable
	%variable% = %value%
}
/*	;Examples
Table_Reg("ColumnsDelimiter", ColumnsDelimiter)	; store
ColumnsDelimiter := Table_Reg("ColumnsDelimiter")	; get
*/
See "bad improvisation!" notes in first code block in this post.

* * *

Table and search objects need count property;
oTable.Rows.Count

why not just:
oTable.Rows.MaxIndex()


majkinetor
  • Moderators
  • 4512 posts
  • Last active: Jul 29 2016 12:40 AM
  • Joined: 24 May 2006
Why don't u just set it up when you create the object ?

in:
oFound := Object("base", base)

use
oFound := Object("base", base, "table", oTable)

Then you can access anything from search object as it keeps reference to its 'parent':

oFound.oTable.RowDelimiter.

Anyway, since oFound is another table, I suggest you first implementing Table.Clone as described above (this will automatically propagate delimiters and column names in fresh empty table, something that might be needed by users anyway) then create object using it.. You might for instance want to search results again .

so
Table_SearchColumn( oTable, ...) {
  oFound := oTable.Clone()
  oFound.table := oTable  ;could be set by Clone()
  ;populate oFound
  return oFound
}
oFound := oTable.ColumnSearch(...)
oFound.table --> oTable
oFound2 := oFound.ColumnSearch(....)
oFound2.table --> oFound   ;maybe some other name is better, like parent or parentTable....
oFound2.table.table -> oTable
oFound.toString() ; oFound is cloned so it has the same delimiter as its parent. Strictly speaking you don't need 'table' property then but it might be good to have it anyway. If oFound is not cloned then u need it, or at least you need its delimiter. Then again, if you clone Table for Found object, and user changes Table.RowDelimiter, delimiter in Found object will not follow, unless, again, you saved 'parent'. So its best to do so, and in toString() use oFound.table.RowDelimiter as separator


why not just:
Code:
oTable.Rows.MaxIndex()

Yup, you could use that, the minor side is you don't have control over it.
If implementation change not to use integer indices or something it will not produce expected results. For instance, in large tables you might deduce that you don't want to delete rows (as it requires to move all above rows which is performance problem) but to mark them as deleted. If that happens, MaxIndex() will no longer represent valid row count.
Posted Image

Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009
Good advices. I'm working on it.

Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009
Latest work - new construction, methods...
;===oTable testing area=================================================================
Variable =		; column names are specified as first row
(
First name%A_Tab%Last name%A_Tab%Occupation%A_Tab%Notes
Jack%A_Tab%Gates%A_Tab%Driver%A_Tab%
Mark%A_Tab%Weber%A_Tab%Student%A_Tab%His father is a driver.
Jim%A_Tab%Tucker%A_Tab%Driver%A_Tab%
Jill%A_Tab%Lochte%A_Tab%Artist%A_Tab%
Jessica%A_Tab%Hickman%A_Tab%Student%A_Tab%
Mary%A_Tab%Jones%A_Tab%Teacher%A_Tab%Her favorite song is "Driver".
Lenny%A_Tab%Stark%A_Tab%Driver%A_Tab%
Jack%A_Tab%Black%A_Tab%Actor%A_Tab%His wife is artist.
Tony%A_Tab%Jackman%A_Tab%Surfer%A_Tab%
Jonny%A_Tab%Poor%A_Tab%Beggar%A_Tab%
)


oTable := Table_ObjCreate(Variable)	; create table object

;MsgBox % oTable.3.2	; get value from [3. row, 2. column]
;oTable.1.1 := "Bobby"	; set [1. row, 1. column] to "Bobby"
oTable.AddRow("Joe", "Newman", "Kiteboarder", "Freestyle & Wave", "Do not store not existing column!")	; add row
;MsgBox % oTable.ToString()	; convert whole table object to string
;MsgBox % oTable.3.ToString()	; convert 3. row object to string
;MsgBox % oTable.11.ToString("#")	; convert 11. row object to string but use custom delimiter
;MsgBox % oTable.MaxIndex()	; get number of rows (in future maybe: oTable.Count)


;=== Simple search ===
; Search "Last name" column for containing "man" string
MsgBox % oTable.SearchColumn("Last name", "man").ToString() 	;  convert search results to string


;=== Complexs search ===
; step 1: search "Occupation" and "Notes" columns for containing "Driver" or "artist" strings. "|" is query delimiter.
; step 2: search that search result again: search "First name" column for containing "J" string
oFound := oTable.SearchColumn("Occupation|Notes", "Driver|artist")		; store search results as object
oFound2 := oFound.SearchColumn("First name", "J")		; search oFound (second search filter)

; or shorter:	oFound2 := oTable.SearchColumn("Occupation|Notes", "Driver|artist").SearchColumn("First name", "J")	; etc. --> multiple filters

MsgBox % oFound2.ToString()		; convert search results to string 
MsgBox % oFound2.2.ToString()	; convert 2. row from search results to string 
;MsgBox % oFound2.1.3	; get [1. row, 3. column] field from search results
;MsgBox % oFound2.MaxIndex()	; get number of found rows - from oFound2 (in future maybe: oFound2.Count)





;===oTable Functions====================================================================
Table_ObjCreate(InputVariable, ColumnsDelimiter="`t", RowsDelimiter= "`n") {	; not finished
	
	static TableBase := Table_Base("SearchColumn ToString NewFromScheme AddRow", "Table_mTable_")
	static RowBase := Table_Base("ToString", "Table_mRow_")
	
	oTable := Object("base", Tablebase, "ColumnsDelimiter", ColumnsDelimiter, "RowsDelimiter", RowsDelimiter)
	oColumnNames := Object()
	
	Loop, parse, InputVariable, %RowsDelimiter%
	{
		CurRow := A_LoopField
		if A_index = 1	; column names are specified as first row
		{
			Loop, parse, CurRow, %ColumnsDelimiter%
			{
				oColumnNames.Insert(A_LoopField)
				ColumnsCount++
			}
			oTable.ColumnNames := oColumnNames
			continue
		}
		RowNum := A_index-1
		%RowNum% := Object("base", RowBase, "table", oTable)
		
		StringSplit, field, CurRow, %ColumnsDelimiter%
		Loop, %ColumnsCount%
		%RowNum%.Insert(field%A_Index%)
		Loop, %ColumnsCount%
		field%A_Index% =
		
		oTable.Insert(%RowNum%)
	}
	return oTable
}

Table_Base(list, prefix) {
	base := Object()
	Loop Parse, list, %A_Space%
	base[A_LoopField] := prefix A_LoopField
	Return base
}

;====== oTable methods ======
Table_mTable_NewFromScheme(oTable) {
	oNewTable := Object("base", oTable.base, "ColumnsDelimiter", oTable.ColumnsDelimiter
		, "RowsDelimiter", oTable.RowsDelimiter, "ColumnNames", oTable.ColumnNames)
	return oNewTable
}

Table_mTable_ToString(oTable, ColumnsDelimiter="", RowsDelimiter= "") {
	if ColumnsDelimiter =
	ColumnsDelimiter := oTable.ColumnsDelimiter
	if RowsDelimiter =
	RowsDelimiter := oTable.RowsDelimiter

	For k,v in oTable
	{
		if k is not integer	;Rows are integers. Keys to skip: ColumnsDelimiter, RowsDelimiter, ColumnNames
		continue
		For k2,v2 in oTable[k]
		RowString .= v2 ColumnsDelimiter
		RowString := RTrim(RowString, ColumnsDelimiter)
		TableString .= RowString RowsDelimiter
		RowString =
	}
	return RTrim(TableString, RowsDelimiter)
}

Table_mTable_SearchColumn(oTable, ColumnsToSearch, StringToSearch) {	; not finished
	; implemented "if field contains" for now. To add: exact match, regEx match
	static RowBase := Table_Base("ToString", "Table_mRow_")
	
	oFound := oTable.NewFromScheme()	; create empty table from oTable template
	
	ColumnsToSearch := Table_ColumnNames2Num(oTable, ColumnsToSearch)
	StringReplace, StringToSearch, StringToSearch, `,, `,`,, all
	StringReplace, StringToSearch, StringToSearch, |, `,, all
	For k in oTable
	{
		if k is not integer	;Rows are integers. Keys to skip: ColumnsDelimiter, RowsDelimiter, ColumnNames
		continue
		Loop, parse, ColumnsToSearch, |
		{
			CurField := oTable[k][A_LoopField]
			if CurField contains %StringToSearch%
			{
				%k% := Object("base", RowBase, "table", oTable)
				For	k2,v2 in oTable[k]
				%k%.Insert(v2)
				oFound.Insert(%k%)
			}
		}
	}
	return oFound
}

Table_mTable_AddRow(oTable,Fields*) {
	static RowBase := Table_Base("ToString", "Table_mRow_")

	NewRowNum := oTable.MaxIndex() + 1
	%NewRowNum% := Object("base", RowBase, "table", oTable)
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		%NewRowNum%.Insert(v)
	}
	oTable.Insert(%NewRowNum%)
}

;====== oRow methods ======
Table_mRow_ToString(oRow, ColumnsDelimiter="") {
	if ColumnsDelimiter =
	ColumnsDelimiter := oRow.table.ColumnsDelimiter
	For k,v in oRow
	{
		if k is not integer	; field keys are integers. Keys to skip: table
		continue
		RowString .= v ColumnsDelimiter
	}
	return RTrim(RowString, ColumnsDelimiter)
}

;====== Shared, other ======
Table_ColumnNames2Num(oTable, ColumnsToSearch) {
	StringReplace, ColumnsToSearch, ColumnsToSearch, `,, `,`,, all
	StringReplace, ColumnsToSearch, ColumnsToSearch, |, `,, all
	For k,v in oTable.ColumnNames
	{
		if v in %ColumnsToSearch%
		Found .= k "|"
	}
	return RTrim(Found, "|")
}