How to create custom enumerator objects

Helpful script writing tricks and HowTo's
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

How to create custom enumerator objects

22 Apr 2015, 10:12

What is an Enumerator Object


An enumerator object is the object that defines the parameters of a for-loop. The default enumerator object gives the contents of an array sorted alphanumerically by the keys. Enumerator objects can be much more flexible than this, generating any values you could want.

How do you make an Enumerator Object


An enumerator object is a class that has a method Class.Next. This method gets called every iteration of the for loop. There are up to two parameters for the Class.Next method, not counting the built in "this" parameter. The parameters are most commonly ByRef k and ByRef v. The names of the variables doesn't matter so much, but they do have to be ByRef.

The Class.Next method sets the values of the two ByRef parameters, and they become the two variables k and v from for k, v in MyArray. The method returns a True/False value that indicates if the loop should keep looping or if it should break.

For example, this custom enumerator would enumerate through the values 1, 2, 2, 4, 3, 6 and so on.

Code: Select all

class CustomEnum
{
	static i := 0
	Next(ByRef k, ByRef v)
	{
		this.i++
		k := this.i
		v := this.i*2
		return True
	}
}
How to implement your custom Enumerator Object


An enumerator object is gotten when a for loop calls the _NewEnum method on the object passed into it. For example, the code for k, v in Members would call Members._NewEnum() to get the enumerator object.

To implement your own enumerator object, you will have to override the _NewEnum method on the object so it returns your custom enumerator. This can be done in a few ways, but the simplest is to just set it to a function reference. For example:

Code: Select all

MyArray := ["Nobody", "expects", "the"]
MyArray._NewEnum := Func("MyNewEnum")

MyNewEnum(this)
{
	return new CustomEnum()
}
Another way would be to create a custom class that already has _NewEnum overridden.

Code: Select all

MyObject := new ObjectWithCustomEnum()

class ObjectWithCustomEnum
{
	_NewEnum()
	{
		return new CustomEnum()
	}
}

For cleaner code, the enumerator object can be defined as a nested class of the original object.

Code: Select all

class ObjectWithCustomEnum
{
	_NewEnum()
	{
		return new this.CustomEnum()
	}
	
	class CustomEnum
	{
		static i := 0
		Next(ByRef k, ByRef v)
		{
			this.i++
			k := this.i
			v := this.i*2
			return True
		}
	}
}
Or if you want, it can even be the same object!

Code: Select all

class ObjectWithCustomEnum
{
	_NewEnum()
	{
		; Reset i since we aren't making a new object
		; that starts off with a fresh i as 0
		this.i := 0
		return this
	}
	
	Next(ByRef k, ByRef v)
	{
		this.i++
		k := this.i
		v := this.i*2
		return True
	}
}
Trying it out


Putting the parts together, you can get a working custom enumerator setup going. Here's an example that enumerates the Fibonacci sequence.

Code: Select all

for a, b in new Fibonacci()
	MsgBox, % a ", " b ", " b/a ; b/a is an approximation of the golden ratio

class Fibonacci
{
	_NewEnum()
	{
		return new this.MyEnum()
	}
	
	class MyEnum
	{
		static Fib := [0, 1]
		Next(ByRef a, ByRef b)
		{
			this.Fib.Push(this.Fib[1] + this.Fib[2])
			a := this.Fib.RemoveAt(1)
			b := this.Fib[1]
			return True
		}
	}
}
Notes, tips and tricks


Since classes are really just objects, your custom enumerator class can be created using object syntax, for example {Next: Func("CustomNext"), i: 0, Jiggly: "puff"}. Going along with how methods are implemented in AutoHotkey, CustomNext would need to have the "this" parameter explicitly defined. CustomNext(this, ByRef a, ByRef b).

The second ByRef parameter of Next can be made optional, in the case that you want to only use the first value in the for loop (i.e. for k in Thing). This would create a Next method that looked something like this: Next(ByRef a, ByRef b="")

If you're inclined to, you can actually fit this entire system into a single function. This is a function that can be used to let you use a for loop on COM collections.
Spoiler
Last edited by geek on 24 Apr 2015, 21:14, edited 1 time in total.
User avatar
boiler
Posts: 16771
Joined: 21 Dec 2014, 02:44

Re: How to create custom enumerator objects

22 Apr 2015, 20:22

Thanks for this, GeekDude. I'd really like to see more tutorials on topics like this that teach various aspects of OOP using AHK.
toralf
Posts: 868
Joined: 27 Apr 2014, 21:08
Location: Germany

Re: How to create custom enumerator objects

23 Apr 2015, 04:17

Awesome
Thanks a lot for the great explanation
ciao
toralf
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
Contact:

Re: How to create custom enumerator objects

23 Apr 2015, 11:34

Nice, btw, I think you should also mention something about .Next()'s return value. By returning false("", 0) the for-loop/while-loop(while enum.Next(k, v)) is terminated. So depending on what the enumerator does, the return value should be taken into account.
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: How to create custom enumerator objects

23 Apr 2015, 11:36

I did mention it, I just didn't demonstrate it. Think I should come up with a demonstration?
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
Contact:

Re: How to create custom enumerator objects

23 Apr 2015, 11:44

GeekDude wrote:I did mention it, I just didn't demonstrate it.
Oops, I must have missed it.
GeekDude wrote:Think I should come up with a demonstration?
If it's OK with you it would definitely be handy. Something like enumerating the first n numbers of a certain sequence should suffice.
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

Re: How to create custom enumerator objects

24 Apr 2015, 19:01

GeekDude wrote:For cleaner code, the enumerator object can be defined as a subclass of the original object.
To avoid confusion, you should use the term nested class, not subclass. class CustomEnum extends ObjectWithCustomEnum would declare a subclass.
User avatar
joedf
Posts: 8940
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

Re: How to create custom enumerator objects

25 Apr 2015, 12:35

Interesting way to create a Fibonacci function ;)
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500, NVIDIA GTX 1060 6GB, 2x16GB Kingston FURY Beast - DDR4 3200 MHz | [About Me] | [About the AHK Foundation] | [Courses on AutoHotkey]
[ASPDM - StdLib Distribution] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library]
iPhilip
Posts: 802
Joined: 02 Oct 2013, 12:21

Re: How to create custom enumerator objects

14 Feb 2021, 16:02

Coco wrote:
23 Apr 2015, 11:44
Something like enumerating the first n numbers of a certain sequence should suffice.
Perhaps, something like this?

Code: Select all

List := ""
for a, b in new Fibonacci(9)
   List .= A_Index ". " a ", " b ", " b/a "`n"  ; b/a is an approximation of the golden ratio
MsgBox % List

class Fibonacci
{
   __New(n)
   {
      this.n := n
   }
   
   _NewEnum()
   {
      this.i := 0
      this.Fib := [0, 1]
      return this
   }
   
   Next(ByRef a, ByRef b)
   {
      if (++this.i > this.n)
         return False
      this.Fib.Push(this.Fib[1] + this.Fib[2])
      a := this.Fib.RemoveAt(1)
      b := this.Fib[1]
      return True
   }
}
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

Return to “Tutorials (v1)”

Who is online

Users browsing this forum: No registered users and 38 guests