[Base] array.ahk

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

[Base] array.ahk

Post by Chunjee » 05 Aug 2020, 17:20

array.ahk

Conversion of JavaScript's Array methods to AutoHotKey

Image Image Image Image

AutoHotKey lacks built-in iteration helper methods (as of 1.1.33) to perform many of the common array behaviors found in other languages. This package ports most of JavaScript's Array object methods to AutoHotKey's Array object.

Ported Methods
  • concat
  • every
  • fill
  • filter
  • find
  • findIndex
  • forEach
  • includes
  • indexOf
  • join
  • keys
  • lastIndexOf
  • map
  • reduce
  • reduceRight
  • reverse
  • shift
  • slice
  • some
  • sort
  • splice
  • toString
  • unshift
  • values
Installation

In a terminal or command line navigated to your project folder:

Code: Select all

npm install array.ahk
In your code only export.ahk needs to be included:

Code: Select all

#Include %A_ScriptDir%\node_modules
#Include array.ahk\export.ahk

msgbox, % [1,2,3].join()
; => "1,2,3"
You may also review or copy the package from ./export.ahk on GitHub; #Include it however you would normally when manually downloading a library.


API

Usage: Array.<fn>([params*])

Code: Select all

; Map to doubled value
arrayInt := [1, 5, 10]
arrayInt.map(func("double_int"))
; => [2, 10, 20]

double_int(int) {
	return int * 2
}


; Map to object property
arrayObj.map(func("get_name")) 
; => ["bob", "tom"]

get_name(obj) {
	return obj.name
}


; Method chaining
arrayObj := [{"name": "bob", "age": 22}, {"name": "tom", "age": 51}]
msgbox, % arrayObj.map(func("get_prop").bind("age"))
	.map(func("double_int"))
	.join(",")
; => "44,102"

get_prop(prop, obj) {
	return obj[prop]
}
Sorting

JavaScript does not expose start/end or left/right parameters and neither does this sort.

Array.sort([params*])

Code: Select all

arrayInt := [11,9,5,10,1,6,3,4,7,8,2]
arrayInt.sort()
; => [1,2,3,4,5,6,7,8,9,10,11]
Last edited by Chunjee on 29 Jul 2021, 18:45, edited 6 times in total.

User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 05 Aug 2020, 17:21

v0.1.8

Code: Select all

; Apply "native" support, redefines Array() to add custom _Array base object
Array(prms*) {
	; Since prms is already an array of the parameters, just give it a
	; new base object and return it. Using this method, _Array.__New()
	; is not called and any instance variables are not initialized.
	prms.base := _Array
	return prms
}


; Define the base class.
class _Array {

	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
	concat(arrays*) {
		
		results := []

		; First add the values from the instance being called on
		for index, value in this
			results.push(value)

		; Second, add arrays given in parameter
		for index, array in arrays {
			if (IsObject(array)) {
				for index, element in array {
					results.push(element)
				}
			} else {
				results.push(array)
			}
		}
		return results
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every
	every(callback) {

		for index, element in this
			if !callback.Call(element, index, this)
				return false

		return true
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill
	fill(value, start:=0, end:=0) {

		len := this.Count()

		; START: Adjust 1 based index, check signage, set defaults
		if (start > 0)
			begin := start - 1    ; Include starting index going forward
		else if (start < 0)
			begin := len + start  ; Count backwards from end
		else
			begin := start


		; END: Check signage and set defaults
		if (end > 0)
			last := end
		else if (end < 0)
			last := len + end   ; Count backwards from end
		else
			last := len


		loop, % (last - begin)
			this[begin + A_Index] := value

		return this
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
	filter(callback) {
		
		results := []

		for index, element in this
			if (callback.Call(element, index, this))
				results.push(element)

		return results
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
	; Modified to return "" instead of 'undefined'
	find(callback) {
		
		for index, element in this
			if (callback.Call(element, index, this))
				return element

		return ""
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
	findIndex(callback) {

		for index, value in this
			if (callback.Call(value, index, this))
				return index

		return -1
	}



	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
	forEach(callback) {

		for index, element in this
			callback.Call(element, index, this)

		return ""
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
	includes(searchElement, fromIndex:=0) {

		return this.indexOf(searchElement, fromIndex) > 0 ? true : false
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
	indexOf(searchElement, fromIndex:=0) {
		
		len := this.Count()

		if (fromIndex > 0)
			start := fromIndex - 1    ; Include starting index going forward
		else if (fromIndex < 0)
			start := len + fromIndex  ; Count backwards from end
		else
			start := fromIndex


		loop, % len - start
			if (this[start + A_Index] = searchElement)
				return start + A_Index

		return -1
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join
	join(delim:=",") {
		
		result := ""
		
		for index, element in this
			result .= element (index < this.Count() ? delim : "")

		return result
	}


	keys() {
		
		result := []

		for key, value in this {
			result.push(key)
		}
		return result
	}

	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf
	; "if the provided index is negative, the array is still searched from front to back"
	;   - Are we not able to return the first found starting from the back?
	lastIndexOf(searchElement, fromIndex:=0) {
		
		len := this.Count()
		foundIdx := -1

		if (fromIndex > len)
			return foundIdx

		if (fromIndex > 0)
			start := fromIndex - 1    ; Include starting index going forward
		else if (fromIndex < 0)
			start := len + fromIndex  ; Count backwards from end
		else
			start := fromIndex

		loop, % len - start
			if (this[start + A_Index] = searchElement)
				foundIdx := start + A_Index

		return foundIdx
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
	map(callback) {

		results := []

		for index, element in this
			results.push(callback.Call(element, index, this))

		return results
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
	; -->[A,B,C,D,E,F]
	reduce(callback, initialValue:="__NULL__") {

		len := this.Count()

		; initialValue not defined
		if (initialValue == "__NULL__") {

			if (len < 1) {
				; Empty array with no intial value
				return
			}
			else if (len == 1) {
				; Single item array with no initial value
				return this[1]
			}

			; Starting value is last element
			initialValue := this[1]

			; Loop n-1 times (start at 2nd element)
			iterations := len - 1 

			; Set index A_Index+1 each iteration
			idxOffset := 1

		} else {
		; initialValue defined

			if (len == 0) {
				; Empty array with initial value
				return initialValue
			}

			; Loop n times (starting at 1st element)
			iterations := len

			; Set index A_Index each iteration
			idxOffset := 0
		}

		; if no initialValue is passed, use first index as starting value and reduce
		; array starting at index n+1. Otherwise, use initialValue as staring value
		; and start at arrays first index.
		Loop, % iterations
		{
			adjIndex := A_Index + idxOffset
			initialValue := callback.Call(initialValue, this[adjIndex], adjIndex, this)
		}
		
		return initialValue
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight
	; [A,B,C,D,E,F]<--
	reduceRight(callback, initialValue:="__NULL__") {

		len := this.Count()

		; initialValue not defined
		if (initialValue == "__NULL__") {

			if (len < 1) {
				; Empty array with no intial value
				return
			}
			else if (len == 1) {
				; Single item array with no initial value
				return this[1]
			}

			; Starting value is last element
			initialValue := this[len]

			; Loop n-1 times (starting at n-1 element)
			iterations := len - 1 
			
			; Set index A_Index-1 each iteration
			idxOffset := 0

		} else {
		; initialValue defined

			if (len == 0)
				; Empty array with initial value
				return initialValue

			; Loop n times (start at n element)
			iterations := len

			; Set index A_Index each iteration
			idxOffset := 1
		}

		; If no initialValue is passed, use last index as starting value and reduce
		; array starting at index n-1. Otherwise, use initialValue as starting value
		; and start at arrays last index.
		Loop, % iterations
		{
			adjIndex := len - (A_Index - idxOffset)
			initialValue := callback.Call(initialValue, this[adjIndex], adjIndex, this)
		}

		return initialValue
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse
	reverse() {

		len := this.Count()

		Loop, % (len // 2)
		{
			idxFront := A_Index
			idxBack := len - (A_Index - 1)

			tmp := this[idxFront]
			this[idxFront] := this[idxBack]
			this[idxBack] := tmp
		}

		return this
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
	shift() {

		return this.RemoveAt(1)
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
	slice(start:=0, end:=0) {

		len := this.Count()

		; START: Adjust 1 based index, check signage, set defaults
		if (start > 0)
			begin := start - 1    ; Include starting index going forward
		else if (start < 0)
			begin := len + start  ; Count backwards from end
		else
			begin := start


		; END: Check signage and set defaults
		; MDN States: "to end (end not included)" so subtract one from end
		if (end > 0)
			last := end - 1
		else if (end < 0)
			last := len + end ; Count backwards from end
		else
			last := len


		results := []

		loop, % last - begin
			results.push(this[begin + A_Index])
		if (results.Count() == 0) {
			return ""
		}
		return results
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
	some(callback) {

		for index, value in this
			if callback.Call(value, index, this)
				return true

		return false
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
	sort(compare_fn:=0) {

		; Quicksort
		this._Call(this, compare_fn)

		return this
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
	splice(start, deleteCount:=-1, args*) {

		len := this.Count()
		exiting := []

		; Determine starting index
		if (start > len)
			start := len

		if (start < 0)
			start := len + start

		; deleteCount unspecified or out of bounds, set count to start through end
		if ((deleteCount < 0) || (len <= (start + deleteCount))) {
			deleteCount := len - start + 1
		}

		; Remove elements
		Loop, % deleteCount
		{
			exiting.push(this[start])
			this.RemoveAt(start)
		}

		; Inject elements
		Loop, % args.Count()
		{
			curIndex := start + (A_Index - 1)

			this.InsertAt(curIndex, args[1])
			args.removeAt(1)
		}

		return exiting
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString
	toString() {
		str := ""

		for i,v in this
			str .= v (i < this.Count() ? "," : "")
		
		return str
	}


	; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
	unshift(args*) {

		for index, value in args
			this.InsertAt(A_Index, value)

		return this.Count()
	}


	values() {
		
		result := []

		for key, value in this {
			result.push(value)
		}
		return result
	}

	; Simple swap
	swap(index1, index2) {
		tmp := this[index1]
		this[index1] := this[index2]
		this[index2] := tmp
	}




	; internal sorting
	_compare_alphanum(a, b) {
		return a > b ? 1 : a < b ? -1 : 0
	}

	_sort(array, compare_fn, left, right) {
		if (array.Count() > 1) {
			centerIdx := this._partition(array, compare_fn, left, right)
			if (left < centerIdx - 1) {
				this._sort(array, compare_fn, left, centerIdx - 1)
			}
			if (centerIdx < right) {
				this._sort(array, compare_fn, centerIdx, right)
			}
		}
	}

	_partition(array, compare_fn, left, right) {
		pivot := array[floor(left + (right - left) / 2)]
		i := left
		j := right

		while (i <= j) {
			while (compare_fn.Call(array[i], pivot) = -1) { ;array[i] < pivot
				i++
			}
			while (compare_fn.Call(array[j], pivot) = 1) { ;array[j] > pivot
				j--
			}
			if (i <= j) {
				this._swap(array, i, j)
				i++
				j--
			}
		}

		return i
	}

	_swap(array, idx1, idx2) {
		tmp := array[idx1]
		array[idx1] := array[idx2]
		array[idx2] := tmp
	}


	; Left/Right remain from a standard implementation, but the adaption is 
	; ported from JS which doesn't expose these parameters.
	;
	; To expose left/right: Call(array, compare_fn:=0, left:=0, right:=0), but
	; this would require passing a falsey value to compare_fn when only 
	; positioning needs altering: Call(myArr, <false/0/"">, 2, myArr.Count())
	_Call(array, compare_fn:=0) {
		; Default comparator
		if !(compare_fn) {
			compare_fn := objBindMethod(this, "_compare_alphanum")
		}

		; Default start/end index
		left := left ? left : 1
		right := right ? right : array.Count()

		; Perform in-place sort
		this._sort(array, compare_fn, left, right)

		; Return object ref for method chaining
		return array
	}

	; Redirect to newer calling standard
	__Call(method, args*) {
		if (method = "")
			return this._Call(args*)
		if (IsObject(method))
		   return this._Call(method, args*)
	}
}
Last edited by Chunjee on 15 Aug 2020, 06:56, edited 3 times in total.

User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 05 Aug 2020, 17:28

array.ahk is a fork of https://github.com/ryunp/ahk-array


This package is designed to be immediately includable and more closely match the normal returns for each method. This brings many wonderful features to the ahk base Array object. Sadly some return objects from ahk built-ins like StrSplit(), etc do not receive the benefit from this base object modification. Please let me know if there is a simple way to accomplish that.


The {}/Object() is not modified. You can do so with:

Code: Select all

Object(prms*) {
	prms.base := _Array
	return prms
}
but this has not been robustly tested or a goal of this package.
Last edited by Chunjee on 10 Aug 2020, 17:08, edited 4 times in total.

User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 05 Aug 2020, 17:38

please, please, please send any bug reports in via github. :thumbup:

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: [Base] array.ahk

Post by kczx3 » 06 Aug 2020, 20:47

You might consider also mimicking the variations in the number of parameters that the callbacks can take as in JavaScript. So add some param count checking and flex what is passed based on the minParams of the callback.


User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 10 Aug 2020, 17:13

I did some bugfixes and a first pass at full documentation: https://chunjee.github.io/array.ahk

Excited to use some of these methods in whatever my next project is.

User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 12 Aug 2020, 21:07

v0.1.6 has been published.

.concat is supposed to accept arrays and values as arguments. It was only working with arrays. Now it is more complete.

Code: Select all

[].concat(["Bill", "Ted"], "Socrates", ["Lincoln", "Joan of Arc"])
; => ["Bill", "Ted", "Socrates", "Lincoln", "Joan of Arc"]

User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 14 Aug 2020, 01:52

v0.1.7 has been published.

.keys was added.
Returns a new array that contains all the keys in the called array.

Code: Select all

["Bill", "Ted", "Socrates"].keys()
; => [1, 2, 3]



v0.1.8 has been published.

.values was added.
Returns a new array that contains the values for each element in the calling array.

Code: Select all

["Bill", "Ted", "Socrates"].values()
; => ["Bill", "Ted", "Socrates"]

array := []
array.insert("x", "a")
array.insert("y", "b")
array.insert("z", "c")
array.values()
; => ["a","b","c"]

songdg
Posts: 565
Joined: 04 Oct 2017, 20:04

Re: [Base] array.ahk

Post by songdg » 14 Aug 2020, 04:27

Does it can load/save an array from file?

User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 14 Aug 2020, 10:01

one of the JSON libraries is better suited for that. https://github.com/Chunjee/json.ahk/network/members
I prefer mine simply because the method names follow what I am used to (again mirroring javascript)


But it can be done to a small extent with array.ahk; assuming your object is not multidimensional:

Code: Select all

; create object
array := [1,2,3]
; convert object to string with unique separator
stringifiedObject := array.join("###")
; remove any existing file
FileDelete, test.ahkobject
; write string to file
FileAppend, %stringifiedObject%, test.ahkobject

sleep, 1000

; read the file back to a new variable
FileRead, newStringObject, test.ahkobject
; map to a new object 
; (reminder StrSplit returns an array without extra methods
newarray := StrSplit(newStringObject, "###")
; => [1,2,3]
This will not work well for multidimensional arrays or associative arrays.
Last edited by Chunjee on 29 Mar 2022, 11:49, edited 1 time in total.

Netmano
Posts: 58
Joined: 17 Jun 2020, 16:24

Re: [Base] array.ahk

Post by Netmano » 14 Aug 2020, 13:04

Sweet lord, this looks really intresting.

Thanks for putting it together!

User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 29 Mar 2022, 14:25

I believe there is a bug in the .sort method when using an unconventional compareFunction

JavaScript:

Code: Select all

var array = [40, 100, 1, 5, 25, 10];
array.sort(func("myFunction"));
// => [1, 5, 10, 25, 40, 100]

myFunction(a, b)
{
	if (a == b) {
		return 0
	}
	if (a > b) {
		return 10
	}
	return -2000
}
array.ahk:

Code: Select all

array := [40, 100, 1, 5, 25, 10]
array.sort(func("myFunction"));
; => [25, 5, 10, 100, 40, 1]

myFunction(a, b)
{
	if (a == b) {
		return 0
	}
	if (a > b) {
		return 10
	}
	return -2000
}


This bug is NOT present when returning -1 and 1 therefore I have to conclude the error is somewhere in the internal ._sort or ._partition method.

Code: Select all

array := [40, 100, 1, 5, 25, 10]
array.sort(myFunction);
; => [1, 5, 10, 25, 40, 100]

myFunction(a, b)
{
	if (a == b) {
		return 0
	}
	if (a > b) {
		return 1
	}
	return -1
}
Last edited by Chunjee on 30 Mar 2022, 01:32, edited 1 time in total.

User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 29 Mar 2022, 14:59

The above bug has been fixed in v0.1.10 which is now on github and later today on npm.

The .sort docs and tests have been updated to reflect this improvement.

User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 30 Mar 2022, 01:34

Discovered an alarming bug when attempting to sort the following: [40, 100, 1, "", "", 5, 25, 10, ""].sort()

v0.1.11 now correctly sorts to: [1, 5, 10, 25, 40, 100, "", "", ""]

If your script uses #NoEnv (recommended); this bug would have had little impact other than being in the wrong order ["", "", "", 1, 5, 10, 25, 40, 100]

User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 21 Feb 2024, 11:49

I thought it would be fun to translate the documentation page. This was a lot more work than originally anticipated; but it's done now!

Additional languages now available:
:jp: 日本語
:de: Deutsch
:es: Español
:fr: Français
:kr: 한국어
:portugal: Português
:ru: Русский
:cn: 中文


Please make any pull requests to /docs/translations.csv if you spot a mistake or area for improvement

If you are interested in an additional translation please add it as another column in the above mentioned csv file
Last edited by Chunjee on 29 Feb 2024, 19:04, edited 1 time in total.

User avatar
Chunjee
Posts: 1421
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Base] array.ahk

Post by Chunjee » 21 Feb 2024, 14:07

Very fast additional language to add to the list:
:it: Italiano

Post Reply

Return to “Scripts and Functions (v1)”