Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate

[AHK_L] For Loop in order of key-value pair creation

  • Please log in to reply
4 replies to this topic
  • Members
  • 158 posts
  • Last active: Dec 28 2012 04:50 PM
  • Joined: 03 Oct 2007
Is it possible to loop through an array in the order each key-value pair was created for non-integer keys? For example, for
Array := Object("b", "1st", "a", "2nd")
For Key, Value in Array
	MsgBox %Key%, %Value%
I would like it to show "1st" first and "2nd" second. Currently, I believe it is a matter of alphabetical order of keys which key-value pair is shown first.


  • Guests
  • Last active:
  • Joined: --

I believe it is a matter of alphabetical order of keys which key-value pair is shown first.

Yes - this is how the AHK object keys are stored. I don't believe there is a way to retrieve creation time (same with AHK variables).

  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Try this:
Array := FiFoArray("c","1rst","b","2nd","a","3rd")

For Idx, Key in Array[]
   MsgBox % Key "," array[Key]

	for k,v in p
		If Mod(A_Index,2)
	Return array
	if k=
		Return o["",""]
	else If v=~empty~
		Return o["",k]
	Return o["",k]:=v , o["",""].Insert(k)

  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
This has come up before, but I was unable to find the post. Basically, the simplest solution is to store the keys in a separate array as they are created.

HotKeyIt's somewhat cryptic code goes one step too far: it also stores the key-value pairs in an internal array, rather than allowing them to be stored normally. This is not only unnecessary, but also inefficient since it adds overhead for every time the array is accessed.

The following adds each key to an array as it is added to the object. Since the __Set meta-function is only called if the key didn't already exist, this incurs only a one-time cost for each unique key and some overhead when the object is enumerated. For for-loops to work, the script overrides _NewEnum to return a custom enumerator; basically, an object which calls a particular function once for each iteration of the for-loop. This function retrieves the next key from the _keys array and the value it is associated with within the object.
testObjects := Object(1, Object(), 2, OrderedArray())
for i,obj in testObjects
    obj.one := 1
    obj.two := 2
    obj.three := 3
    obj.four := 4
    for k,v in obj
        t .= k "=" v "`n"
    t .= "`n"
MsgBox % t

    ; Define prototype object for ordered arrays:
    static base := Object("__Set", "oaSet", "_NewEnum", "oaNewEnum")
    ; Create and return new ordered array object:
    return Object("_keys", Object(), "base", base)

oaSet(obj, k, v)
    ; If this function is called, the key must not already exist.
    ; Just add this new key to the array:
    ; Since we don't return a value, the default behaviour takes effect.
    ; That is, a new key-value pair is created and stored in the object.

    ; Define prototype object for custom enumerator:
    static base := Object("Next", "oaEnumNext")
    ; Return an enumerator wrapping our _keys array's enumerator:
    return Object("obj", obj, "enum", obj._keys._NewEnum(), "base", base)

oaEnumNext(e, ByRef k, ByRef v="")
    ; If Enum.Next() returns a "true" value, it has stored a key and
    ; value in the provided variables. In this case, "i" receives the
    ; current index in the _keys array and "k" receives the value at
    ; that index, which is a key in the original object:
    if r := e.enum.Next(i,k)
        ; We want it to appear as though the user is simply enumerating
        ; the key-value pairs of the original object, so store the value
        ; associated with this key in the second output variable:
        v := e.obj[k]
    return r
Since the key-value pairs are stored normally within the object, all of the built-in methods work. However, since .Insert() bypasses the __set meta-function, any keys inserted with that method would not be enumerated. Similarly, removing a key with .Remove() would not remove it from the object's _keys array. To fix these issues, the script could override these two methods as necessary. I leave that as an exercise to the reader. :)

  • Guests
  • Last active:
  • Joined: --
I'd go with Lexikos' example, but another option would be to us a Scripting.Dictionary object. You could use it directly, or wrap it in an AHK object.