Associative Array inside a Regular Array Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
gilliduck
Posts: 265
Joined: 06 Oct 2014, 15:01

Associative Array inside a Regular Array

23 Aug 2015, 15:20

I'd like the below code to end up making an array that each index contains an associative array containing the values of Name, Price, Option, Quantity & Stock. Basically Array := [{Name:Something,Price:$129.99,Option:Brown,Stock:In Stock},{Name:SomethingElse,Price:$119.99,Option:,Stock:Limited Quantity}. I'm just not sure how to create it. Someone mind tossing me a small bone?

FYI if you run the script, prepare for the UrlDownloadToFile to take 30 seconds or more.

Code: Select all

#SingleInstance, Force

URL := "https://www.overstock.com/registries/wishlists/3119049/Ebay/?view&page=100"
FileName := "overstock.txt"
; Destination := "C:\Users\dgilliland\Desktop\Test\overstock.txt"
Dest_Folder := "C:\Users\Public\Desktop\"
FileArray := []


UrlDownloadToFile, %URL%, % Dest_Folder . FileName
If ErrorLevel <> 0
	MsgBox There was a problem
Loop, Files, %Dest_Folder%, F
	If (A_LoopFileName = FileName)
		Break
Loop, Read, % Dest_Folder . FileName
	FileArray.Push(A_LoopReadLine)
Loop % FileArray.MaxIndex()
	{
	If (InStr(FileArray[A_Index], "wl-item-img"))
		{
		Name := SubStr(SubStr(FileArray[A_Index+1], InStr(FileArray[A_Index + 1], "alt=")+5),1,-6)
		Price := SubStr(SubStr(FileArray[A_Index + 6], InStr(FileArray[A_Index + 6], "$")),1,18)
		Option := (InStr(FileArray[A_Index + 8], "<strong>Option:")) ? "Option: " . SubStr(FileArray[A_Index + 8],41) : ""
		Stock_Increment := (Option = "") ? 0 : 1
		Stock := (Stock_Increment = 0) ? InStr((FileArray[A_Index + 14]), "In Stock") : InStr((FileArray[A_Index + 17]), "In Stock")
		Stock := (Stock <> 0) ? "In Stock" : "Limited Quantity"
		MsgBox % Name "`n" Price "`n" Option "`n" Quantity "`n" Stock
		}
	}
User avatar
TLM
Posts: 1608
Joined: 01 Oct 2013, 07:52
Contact:

Re: Associative Array inside a Regular Array

23 Aug 2015, 15:55

gilliduck I noticed you left irc before I got to show you this..
you don't have to save to file and use a bunch of substr() things, you can use dom to get the info you're after
http://p.ahkscript.org/?p=0d45fd

Code: Select all

url = https://www.overstock.com/registries/wishlists/3119049/Ebay/?view&page=100

document := URLToObject( url )

itmObj := document.getElementById( "wlItemsWrapper" ).children

Loop % itmObj.length
{
    itm := itmObj[ a_index-1 ]
    itemname	:= itm.getElementsByTagName( "img" )[ 0 ].alt
    imglink		:= itm.getElementsByTagName( "img" )[ 0 ].src
    itemprice	:= itm.getElementsByTagName( "div" )[ 5 ].innerText
    dateadded	:= itm.getElementsByTagName( "div" )[ 0 ].parentNode.getAttribute( "data-added-on" )

    msgbox % "Name: " itemname "`nPrice: " itemprice "`nDateAdded: " dateadded  "`nIMGLink: " imglink
}

return

URLToObject( url )
{
    htReqObj := ComObjCreate( "WinHttp.WinHttpRequest.5.1" ), htFileObj := ComObjCreate( "HTMLfile" )
    htReqObj.Open( "GET", url ), htReqObj.Send(), htFileObj.Write( htReqObj.ResponseText )

    return htFileObj
}
gilliduck
Posts: 265
Joined: 06 Oct 2014, 15:01

Re: Associative Array inside a Regular Array

23 Aug 2015, 16:02

That doesn't seem to do anything. It runs, then exits without ever generating a MsgBox.
User avatar
TLM
Posts: 1608
Joined: 01 Oct 2013, 07:52
Contact:

Re: Associative Array inside a Regular Array

23 Aug 2015, 16:06

hrm that's odd I get msgbox's for each item
Image
User avatar
TLM
Posts: 1608
Joined: 01 Oct 2013, 07:52
Contact:

Re: Associative Array inside a Regular Array

23 Aug 2015, 16:29

that aside for a sec,
here's an example of inserting associative arrays into simple arrays

Code: Select all

arr := []
arr[ 1 ] := {a:"dog",b:"cat",c:"bird"}
arr[ 2 ] := {a:"fish",b:"snake",c:"frog"}

for i, obj in arr
    for key, animal in obj
        msgbox % "Array: " i "`nKey: " key "`nAnimal: " animal
gilliduck
Posts: 265
Joined: 06 Oct 2014, 15:01

Re: Associative Array inside a Regular Array

23 Aug 2015, 16:52

Yeah, I've got no explanation. I can see it run, but then it eventually exits without ever generating anything. I'll play with it more. Thanks for the Array info!
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: Associative Array inside a Regular Array

24 Aug 2015, 02:02

Basically Array := [{Name:Something,Price:$129.99,Option:Brown,Stock:In Stock},{Name:SomethingElse,Price:$119.99,Option:,Stock:Limited Quantity}
Add quote marks around the strings, append the missing ']', and you'll have it...

...but I guess your actual usage will involve adding items one at a time, like TLM demonstrated.
gilliduck
Posts: 265
Joined: 06 Oct 2014, 15:01

Re: Associative Array inside a Regular Array

24 Aug 2015, 05:42

Once I had the basic syntax down it made perfect sense. Inside a loop dict[A_Index] := {Item:Name, Price:Price, Option:Option, Stock:Stock} with all values being variables, so no need for quotes to define strings. It's so simple yet I just couldn't see it in the moment.
paulpma
Posts: 65
Joined: 08 Sep 2018, 22:05

Re: Associative Array inside a Regular Array

27 Dec 2020, 07:29

Hello, I am trying to do something similar, read pipeline seperated. file and place the table into an array of associated arrays. Table looks like this:

Row1: Head for columns: Name| DOB| Address| City| Zip
Row2: Jake|01/01/2020|123 Main| NY City| 11001
Row3: Mike|03/04/2001|30 south| MIAMI |2404
Row4: next person etc.

I got so far:

Code: Select all

FileRead, fileDB, %A_ScriptDir%\lib\_phDB.csv
if ErrorLevel
	Tooltip, File Not Loaded
	
	
obHead := []
obPersonS := []
obj:={}
Loop, parse, fileDB, `r`n
{
	if (A_Index = 1) {
			loop, parse, A_LoopField, "|" ;headers to array
			obHead.Push(A_LoopField)
	}
	else if (A_LoopField)
		obPersonS.Push(A_LoopField)

}

	
I have trouble with following :

Code: Select all

for i, obj in obPersonS {
	
	loop, parse, obj, "|" 
	{
		obPersonS[(i)].([obHead[(A_Index)]]) := A_LoopField

	}
}
The trouble is to create an array of associate arrays. I have tried other methods and failed. I am stuck.. Any ideas? Thank you.
teadrinker
Posts: 4295
Joined: 29 Mar 2015, 09:41
Contact:

Re: Associative Array inside a Regular Array

27 Dec 2020, 14:23

Perhaps you need this:

Code: Select all

fileDB =
(
Name| DOB| Address| City| Zip
Jake|01/01/2020|123 Main| NY City| 11001
Mike|03/04/2001|30 south| MIAMI |2404
)
obPersonS := []
Loop, parse, fileDB, `n, `r
{
   if (A_Index = 1)
      Headers := StrSplit(A_LoopField, "|", " ")
   else if A_LoopField {
      obj := []
      for k, v in StrSplit(A_LoopField, "|", " ") {
         o := {}, o[Headers[k]] := v
         obj.Push(o)
      }
      obPersonS.Push(obj)
   }
}
To see what you have:

Code: Select all

MsgBox, % AhkToJSON(obPersonS, "    ")

AhkToJSON(obj, indent := "") {
   static Doc, JS
   if !Doc {
      Doc := ComObjCreate("htmlfile")
      Doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := Doc.parentWindow
      ( Doc.documentMode < 9 && JS.execScript() )
   }
   if indent|1 {
      if IsObject( obj ) {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : """" . k . """:" ) . %A_ThisFunc%(v, true)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "\s"))
         Return obj
      
      for k, v in [["\", "\\"], [A_Tab, "\t"], ["""", "\"""], ["/", "\/"], ["`n", "\n"], ["`r", "\r"], [Chr(12), "\f"], [Chr(8), "\b"]]
         obj := StrReplace( obj, v[1], v[2] )

      Return """" obj """"
   }
   sObj := %A_ThisFunc%(obj, true)
   Return JS.eval("JSON.stringify(" . sObj . ",'','" . indent . "')")
}
paulpma
Posts: 65
Joined: 08 Sep 2018, 22:05

Re: Associative Array inside a Regular Array

27 Dec 2020, 22:17

Thank you, teadrinker
I have came up more code on on my own, but was still having issues with the code and you have helped me to see what was wrong. Creating/Clearing obj in loop was the key. I wouldn't be able to solve it without you. Thank you so much.

I came up with the code to read the array of associated arrays, but have trouble understanding why i need 3 loops to read key and value. Some Vars may have changed, but its the same data array and same principle.

Code: Select all

for Index, oIndiv in objIndvS {
	for i, Val in oIndiv {
		for k,v in Val	{
			Msgbox %k% = %v%
		}
	}
}
To my understating, I should be able to read with 2 loops.. Any suggestions?
And objects are accessible via

Code: Select all

MsgBox, % objIndvS.2.1.Name "x"
Shouldn't it be something like

Code: Select all

MsgBox, % objIndvS.2.Name "x"
(2) represents index, (1) in first example? Not sure.
Thank you.

Update: push only handles integer keys.
paulpma
Posts: 65
Joined: 08 Sep 2018, 22:05

Re: Associative Array inside a Regular Array

27 Dec 2020, 23:46

Updated Code:

Code: Select all

Headers := []
obj := []
Loop, parse, fileDB, `n, `r
{
	if (A_Index = 1)
		Headers := StrSplit(A_LoopField, "|", " ")
	else if A_LoopField {
		o := {}
		for k, v in StrSplit(A_LoopField, "|", " ") {
			
			cHead := Headers[k]
			o[cHead] := v
			;MsgBox, % "key: " cHead " val = " v
			;obj.Push(o)
		}
		;MsgBox, % Obj2Str2(o)
		obj[A_Index] := o
		;obj.Push(o)
	}
}

;MsgBox, % AhkToJSON(obj,"~")
Bottom line: push only handles integer keys.
Thank you for directing me in right way.
teadrinker
Posts: 4295
Joined: 29 Mar 2015, 09:41
Contact:

Re: Associative Array inside a Regular Array

28 Dec 2020, 00:18

Using yours method, you get:
 
 Image
 
More proper way is:

Code: Select all

fileDB =
(
Name| DOB| Address| City| Zip
Jake|01/01/2020|123 Main| NY City| 11001
Mike|03/04/2001|30 south| MIAMI |2404
)
Headers := []
obj := []
Loop, parse, fileDB, `n, `r
{
	if (A_Index = 1)
		Headers := StrSplit(A_LoopField, "|", " ")
	else if A_LoopField {
		o := {}
		for k, v in StrSplit(A_LoopField, "|", " ") {
			cHead := Headers[k]
			o[cHead] := v
		}
		obj[A_Index - 1] := o
	}
}

MsgBox, % AhkToJSON(obj,"~")
Now you have:
 
 Image
 
But, as you can see, the headers order is not preserved.
There is a trick:

Code: Select all

fileDB =
(
Name| DOB| Address| City| Zip
Jake|01/01/2020|123 Main| NY City| 11001
Mike|03/04/2001|30 south| MIAMI |2404
)
obPersonS := []
Loop, parse, fileDB, `n, `r
{
   if (A_Index = 1)
      Headers := StrSplit(A_LoopField, "|", " ")
   else if A_LoopField {
      obj := new CaseSenseList
      for k, v in StrSplit(A_LoopField, "|", " ")
         obj[Headers[k]] := v
      obPersonS.Push(obj)
   }
}

MsgBox, % AhkToJSON(obPersonS, "~")

class CaseSenseList
{
   __New() {
      ObjRawSet(this, "_list_", [])
   }
   
   __Delete() {
      this.SetCapacity("_list_", 0)
      ObjRawSet(this, "_list_", "")
   }
   
   __Set(key, value) {
      for k, v in this._list_ {
         if !(v[1] == key)
            continue
         v[2] := value
         keyExist := true
      } until keyExist
      if !keyExist
         this._list_.Push([key, value])
      Return value
   }
   
   __Get(key) {
      if (key == "_list_")
         Return
      for k, v in this._list_ {
         if (v[1] == key)
            Return v[2]
      }
   }
   
   _NewEnum() {
      Return new this._CustomEnum_(this._list_)
   }
   
   class _CustomEnum_
   {
      __New(list) {
         this.i := 0
         this.list := list
      }
      
      Next(ByRef k, ByRef v := "") {
         if ++this.i <= this.list.Length() {
            k := this.list[this.i, 1]
            v := this.list[this.i, 2]
            Return true
         }
      }
   }
   
   Count() {
      Return this._list_.Length()
   }
   
   Delete(key) {
      for k, v in this._list_
         continue
      until v[1] == key && keyExist := true
      Return keyExist ? this._list_.RemoveAt(k)[2] : ""
   }
   
   HasKey(key) {
      for k, v in this._list_
         if (v[1] == key)
            Return true
      Return false
   }
}

AhkToJSON(obj, indent := "") {
   static Doc, JS
   if !Doc {
      Doc := ComObjCreate("htmlfile")
      Doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := Doc.parentWindow
      ( Doc.documentMode < 9 && JS.execScript() )
   }
   if indent|1 {
      if IsObject( obj ) {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : """" . k . """:" ) . %A_ThisFunc%(v, true)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "\s"))
         Return obj
      
      for k, v in [["\", "\\"], [A_Tab, "\t"], ["""", "\"""], ["/", "\/"], ["`n", "\n"], ["`r", "\r"], [Chr(12), "\f"], [Chr(8), "\b"]]
         obj := StrReplace( obj, v[1], v[2] )

      Return """" obj """"
   }
   sObj := %A_ThisFunc%(obj, true)
   Return JS.eval("JSON.stringify(" . sObj . ",'','" . indent . "')")
}
Now you have:
 
 Image
paulpma
Posts: 65
Joined: 08 Sep 2018, 22:05

Re: Associative Array inside a Regular Array

29 Dec 2020, 02:29

Dam... thank you so much... I have spent 1+ hour yesterday to solve header issue, came up with different solution,. Took me few hours to digest your code.. Learning and making something takes a bit of time and patience...

Question. In your example the index for each object is not shown and you stated its proper way? Can you please explain why? Or where can I read about it? Thank you for sharing great code and ideas!
teadrinker
Posts: 4295
Joined: 29 Mar 2015, 09:41
Contact:

Re: Associative Array inside a Regular Array

29 Dec 2020, 08:42

paulpma wrote: In your example the index for each object is not shown and you stated its proper way?
"Proper" array strarts with the key 1, and all its key numbers must be in a row without gaps. In this case AhkToJSON() shows it without indexes:

Code: Select all

Arr := ["a", "b", "c"]
MsgBox, % AhkToJSON(Arr, "  ")
But if you delete any key (except the last), keys will stop going in a row, and indexes will appear:

Code: Select all

Arr := ["a", "b", "c"]
Arr.Delete(1)
MsgBox, % AhkToJSON(Arr, "  ")
BoBo
Posts: 6564
Joined: 13 May 2014, 17:15

Re: Associative Array inside a Regular Array

29 Dec 2020, 10:30

paulpma wrote:
27 Dec 2020, 22:17
[…]

Update: push only handles integer keys.
Any evidence (AHK Help-reference) for that?

arr := []
arr.Push("The Expanse")
MsgBox % arr.1

So that isn’t working?
paulpma
Posts: 65
Joined: 08 Sep 2018, 22:05

Re: Associative Array inside a Regular Array

29 Dec 2020, 10:41

I have solved sorting of headers with SortArray() function. However, I see that class CaseSenseList is a better option, because it preserves order, but SortArray() sorts headers to match object and the whole order is changed from original.

Code: Select all

SortArray(Array, Order="A") {
    ;Order A: Ascending, D: Descending, R: Reverse
	MaxIndex := ObjMaxIndex(Array)
	If (Order = "R") {
		count := 0
		Loop, % MaxIndex
			ObjInsert(Array, ObjRemove(Array, MaxIndex - count++))
		Return
	}
	Partitions := "|" ObjMinIndex(Array) "," MaxIndex
	Loop {
		comma := InStr(this_partition := SubStr(Partitions, InStr(Partitions, "|", False, 0)+1), ",")
		spos := pivot := SubStr(this_partition, 1, comma-1) , epos := SubStr(this_partition, comma+1)    
		if (Order = "A") {    
			Loop, % epos - spos {
				if (Array[pivot] > Array[A_Index+spos])
					ObjInsert(Array, pivot++, ObjRemove(Array, A_Index+spos))    
			}
		} else {
			Loop, % epos - spos {
				if (Array[pivot] < Array[A_Index+spos])
					ObjInsert(Array, pivot++, ObjRemove(Array, A_Index+spos))    
			}
		}
		Partitions := SubStr(Partitions, 1, InStr(Partitions, "|", False, 0)-1)
		if (pivot - spos) > 1    ;if more than one elements
			Partitions .= "|" spos "," pivot-1        ;the left partition
		if (epos - pivot) > 1    ;if more than one elements
			Partitions .= "|" pivot+1 "," epos        ;the right partition
	} Until !Partitions
}
I think I will go with class CaseSenseList because Its better option. Not so eager to rewrite, but will do, the code for editing and placing object back to file, spent many hours to figure things out.

Code: Select all

;to delete obj
for Index, oIndiv in obj {
	;MsgBox, % Obj2Str(obj[Index])
	;MsgBox, % obj[Index].Zip
	;Testing
	if InStr(obj[Index].Zip,"11001"){
		;MsgBox, found
		obj.Delete(Index)
		obj[index] := obNew ;created separately with diff code.
		;MsgBox, % Obj2Str(obj[Index])
		break
	}
}

MsgBox, % AhkToJSON(obj,"~")

obj4file:=[]
SortArray(Headers)

for ke,va in Headers
	hline .= va "|"
obj[1] := hline
MsgBox, % hline
hline := ""


for Index, Val in obj {
	line := ""
	;Msgbox, % Index
	if (Index = 1){
		File .= hline
	}
	else if (Index > 1){
		for k,v in obj[Index] 
			line.= v "|"
	}
	File .= line "`r`n"
	;Msgbox, % "LINE: " obj4file[Index]
}

	FileDelete, %A_ScriptDir%\lib\_DB.csv
	FileAppend, %File%, %A_ScriptDir%\lib\_DB.csv
paulpma
Posts: 65
Joined: 08 Sep 2018, 22:05

Re: Associative Array inside a Regular Array

29 Dec 2020, 10:44

BoBo wrote:
29 Dec 2020, 10:30
paulpma wrote:
27 Dec 2020, 22:17
[…]

Update: push only handles integer keys.
Any evidence (AHK Help-reference) for that?

arr := []
arr.Push("The Expanse")
MsgBox % arr.1

So that isn’t working?
@BoBo The example provided should work, because integer key was used. I have seen reference for it, now that I am looking for it, I cant find it. Ill post once I come across it.
paulpma
Posts: 65
Joined: 08 Sep 2018, 22:05

Re: Associative Array inside a Regular Array

29 Dec 2020, 14:45

That wasn't so bad. Happy with results, hopefully this will help someone as well.
Read Data to Array of Associative Arrays:

Code: Select all

fileDB =
(
Name| DOB| Address| City| Zip
Jake|01/01/2020|123 Main| NY City| 11001
Mike|03/04/2001|30 south| MIAMI |2404
)
Headers := []
objS := []
Loop, parse, fileDB, `n, `r
{
	if (A_Index = 1)
		Headers := StrSplit(HeadStr:=A_LoopField, "|", " ")
	else if A_LoopField {
		obj := new CaseSenseList
		for k, v in StrSplit(A_LoopField, "|", " ") {
			cHead := Headers[k]
			obj[cHead] := v
			;MsgBox, % "key: " cHead " val = " v
			;obj.Push(o) ;doesn't work
		}
		;MsgBox, % Obj2Str2(o)
		;obj[A_Index-1] := o
		objS.Push(obj)
	}
}
To edit/delete: (please note some vars not consistent with data provided phoneNumbr

Code: Select all

;to delete/edit obj
for Index, oIndiv in objS {
	;MsgBox, % Obj2Str(obj[Index])
	;MsgBox, % obj[Index].phoneNbr
	;Testing
	if InStr(objS[Index].phoneNbr,objNew["phoneNbr"]){ ;search based on phone#
		;MsgBox, found
		objS.Delete(Index)
		objS[index] := objNew ;replace with new object created elsewhere.
		
		;MsgBox, % Obj2Str(objS[Index])
		break
	}
}
Writing to a file:

Code: Select all

;Start File
File:=""
File .= HeadStr "`r`n"

for Index, Val in objS {
	line := ""
	;Msgbox, % Index
	for k,v in objS[Index] 
		line.= v "|"

	File .= line "`r`n"
	;Msgbox, % "LINE: " obj4file[Index]
}
MsgBox, % "Bfr. Wr: " AhkToJSON(File,"~")

FileDelete, %A_ScriptDir%\lib\_DB.csv
FileAppend, %File%, %A_ScriptDir%\lib\_DB.csv
Hopefully, this will help someone. Thank you all for helping. Now have to find how to Sort ObjS based on first column..
paulpma
Posts: 65
Joined: 08 Sep 2018, 22:05

Re: Associative Array inside a Regular Array

29 Dec 2020, 20:02

Any thoughts why AhkToJSON displays numbers incorrectly from Associative Array when they begin with zero? Eg. ZIP code 02030 will give a random 512.. Note last two begin with zeros, but it will display zip as 1284 and 572, however, the value is still written correctly to file.

Code: Select all

fileDB =
(
Name| DOB| Address| City| Zip
Jake|01/01/2020|123 Main| NY City| 11001
Mike|03/04/2001|30 south| MIAMI |02404
Aleex|03/04/2001|30 rich| MIAMI |01074
)
Headers := []
objS := []
Loop, parse, fileDB, `n, `r
{
	if (A_Index = 1)
		Headers := StrSplit(HeadStr:=A_LoopField, "|", " ")
	else if A_LoopField {
		obj := new CaseSenseList
		for k, v in StrSplit(A_LoopField, "|", " ") {
			cHead := Headers[k]
			obj[cHead] := v
			;MsgBox, % "key: " cHead " val = " v
			;obj.Push(o) ;doesn't work
		}
		;MsgBox, % Obj2Str2(o)
		;obj[A_Index-1] := o
		objS.Push(obj)
	}
}

MsgBox, % AhkToJSON(objS,"~")


Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot] and 182 guests