Page 1 of 2
Associative Array inside a Regular Array
Posted: 23 Aug 2015, 15:20
by gilliduck
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
}
}
Re: Associative Array inside a Regular Array
Posted: 23 Aug 2015, 15:55
by TLM
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
}
Re: Associative Array inside a Regular Array
Posted: 23 Aug 2015, 16:02
by gilliduck
That doesn't seem to do anything. It runs, then exits without ever generating a MsgBox.
Re: Associative Array inside a Regular Array
Posted: 23 Aug 2015, 16:06
by TLM
hrm that's odd I get msgbox's for each item
Re: Associative Array inside a Regular Array
Posted: 23 Aug 2015, 16:29
by TLM
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
Re: Associative Array inside a Regular Array
Posted: 23 Aug 2015, 16:52
by gilliduck
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!
Re: Associative Array inside a Regular Array
Posted: 24 Aug 2015, 02:02
by lexikos
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.
Re: Associative Array inside a Regular Array
Posted: 24 Aug 2015, 05:42
by gilliduck
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.
Re: Associative Array inside a Regular Array
Posted: 27 Dec 2020, 07:29
by paulpma
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.
Re: Associative Array inside a Regular Array
Posted: 27 Dec 2020, 14:23
by teadrinker
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 . "')")
}
Re: Associative Array inside a Regular Array
Posted: 27 Dec 2020, 22:17
by paulpma
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
Shouldn't it be something like
(2) represents index, (1) in first example? Not sure.
Thank you.
Update: push only handles integer keys.
Re: Associative Array inside a Regular Array
Posted: 27 Dec 2020, 23:46
by paulpma
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.
Re: Associative Array inside a Regular Array
Posted: 28 Dec 2020, 00:18
by teadrinker
Using yours method, you get:
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:
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:
Re: Associative Array inside a Regular Array
Posted: 29 Dec 2020, 02:29
by paulpma
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!
Re: Associative Array inside a Regular Array
Posted: 29 Dec 2020, 08:42
by teadrinker
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, " ")
Re: Associative Array inside a Regular Array
Posted: 29 Dec 2020, 10:30
by BoBo
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?
Re: Associative Array inside a Regular Array
Posted: 29 Dec 2020, 10:41
by paulpma
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
Re: Associative Array inside a Regular Array
Posted: 29 Dec 2020, 10:44
by paulpma
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.
Re: Associative Array inside a Regular Array
Posted: 29 Dec 2020, 14:45
by paulpma
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..
Re: Associative Array inside a Regular Array
Posted: 29 Dec 2020, 20:02
by paulpma
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,"~")