ObjCount() or ObjLength() or ObjLen()

Discuss the future of the AutoHotkey language
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

ObjCount() or ObjLength() or ObjLen()

12 Jul 2014, 03:01

[Update by Lexikos: v2.0-a104 added dedicated Array and Map types; Array has Length and Map has Count.]

As per v2-thoughts
v2-thoughts wrote:A property like Object.Length may be warranted because of the awkwardness of MaxIndex() returning blank when there are no integer keys. There are workarounds like Round(x.MaxIndex()) in v1 and x.MaxIndex() or 0 in v2, but these are verbose and not very clear/expressive. It can't be added in v1 for compatibility reasons.
This is much needed now especially since numeric string keys are considered as strings, e.g.: ObjMaxIndex({"1":"One", 1:"One"}) returns 1. Currently ObjMaxIndex() is really unrealiable and a Length property will be very useful in many cases.
Last edited by lexikos on 19 Jun 2020, 18:53, edited 1 time in total.
Reason: update
User avatar
joedf
Posts: 8545
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: ObjCount() or ObjLength() or ObjLen()

12 Jul 2014, 03:27

I currently use the following workaround.
From: https://github.com/ahkscript/ASPDM/blob ... hk#L51-L58

Code: Select all

Util_ObjCount(Obj) {
	if (!IsObject(Obj))
		return 0
	z:=0
	for k in Obj
		z+=1 ;or z:=A_Index
	return z
}
I do agree that this could be of use, but I would hope for it to not affect performance. That is probably the case. ;) But, it might not be "accepted" if it is not proven to be a necessity? just a thought...
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: ObjCount() or ObjLength() or ObjLen()

12 Jul 2014, 03:48

joedf wrote:But, it might not be "accepted" if it is not proven to be a necessity? just a thought
I would consider it more of a necessity than ObjMaxIndex() or ObjMinIndex(). These two only work for integer keys and return either the highest or the lowest key - not the actual member(s) count. From a necessity standpoint, in most cases, these functions aren't that really useful except for what they are designed to do -- to get the highest or lowest integer key.
User avatar
joedf
Posts: 8545
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: ObjCount() or ObjLength() or ObjLen()

12 Jul 2014, 04:39

Makes sense. If both ObjMaxIndex() and ObjMinIndex() should exist, then the same should go for ObjCount().
lexikos
Posts: 8292
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: ObjCount() or ObjLength() or ObjLen()

12 Jul 2014, 19:12

I think you entirely missed the point of the quoted text. Object.Length would not return the actual member count either - it would be equivalent to Round(x.MaxIndex()) or (in v2) x.MaxIndex() or 0. The point is that it returns 0, not "", when no integer keys exist.
This is much needed now especially since numeric string keys are considered as strings, e.g.: ObjMaxIndex({"1":"One", 1:"One"}) returns 1.
How so? Now that it's easier to have "1" and 1 in the same object, is it suddenly going to become a common thing? No. Object.Count is no more necessary now than it was before. Objects were already used in an associative capacity, where MaxIndex was not applicable.
I would consider it more of a necessity than ObjMaxIndex() or ObjMinIndex().
MaxIndex was needed for array-like objects. Originally, it was the only way to iterate over an array. Count would not have been useful for iterating over sparse arrays or arrays with secondary values (such as a Length counter or other information about the array), and still would not have allowed enumeration of non-numeric keys.

MinIndex was mostly for symmetry, but also to differentiate between 0-based and 1-based arrays (at the time neither one existed, so I didn't know which would be used).

In general, I've far less frequently needed to know the number of string keys in an associative array than the number of elements in a "linear" array. I simply don't think Object.Count is needed often.
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: ObjCount() or ObjLength() or ObjLen()

13 Jul 2014, 00:22

lexikos wrote:How so? Now that it's easier to have "1" and 1 in the same object, is it suddenly going to become a common thing? No.
Well, you have a point. However, the above behavior still exists and there will be scenario(s) in which an unaware user tries to alter(dynamically) the value at numeric key and that user passed a variable containing the numeric key but Type(VarContainingNumKey) is String, hence, that user ended up adding a new field instead of altering the targeted existing one. e.g/ obj[VarContainingNumKey] := value ; VarContainingNumKey might have been retrieved via command(s) that return/output string (RegExMatch, SubStr, FileRead, etc). MaxIndex will not help in these types of cases, and I reckon that this will happen at a substantial amount than expected due to majority of AHK users being spoilt by AHK's behavior of not being strict when it comes to Integers and Strings.
lexikos wrote:I simply don't think Object.Count is needed often.
There have been multiple questions on how to get array length/count but seldom for getting highest/lowest integer key.
lexikos
Posts: 8292
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: ObjCount() or ObjLength() or ObjLen()

13 Jul 2014, 07:11

Coco wrote:There have been multiple questions on how to get array length/count but seldom for getting highest/lowest integer key.
I expect that many or most of those questions can be answered with "use MaxIndex()". It's not a question of whether it's often needed to get the length/count of an array, but whether it's often needed to get the number of keys in an associative array. My point was that the former is commonly needed, and that MaxIndex() fulfils that need, whereas the latter is not commonly needed.

You also don't know about all the users who never needed to ask the question because the answer was already given to them. Since there's no built-in method to get the total number of keys, it stands to reason that anyone who wants to do it will post about it. By contrast, users who read even basic parts of the object documentation should see the MaxIndex examples and so should not need to ask about it. Some don't read it, or don't see the connection between "max index" and "length of array", so need to ask.
MaxIndex will not help in these types of cases,
You're implying that Count will help enough that it would be a reason to implement it. I think that's ludicrous.

I'm not against implementing Count; I just don't value it highly.
User avatar
tank
Posts: 3020
Joined: 28 Sep 2013, 22:15
Google: ttnnkkrr
GitHub: ttnnkkrr
Location: CarrolltonTX
Contact:

Re: ObjCount() or ObjLength() or ObjLen()

13 Jul 2014, 08:59

I never posted about it because I can just count them as joedf did. It is in fact useful to know the full count of items in an associative array. As is flping keys and values as is key and value searching.. maxindex is all but useless. The point I share with joedf is there are tons of array proprties and functions that are necesay even though you disagree. If it would affect performance to build int ahk then I would rather use user define fuctions within my script
We are troubled on every side‚ yet not distressed; we are perplexed‚
but not in despair; Persecuted‚ but not forsaken; cast down‚ but not destroyed;
https://www.facebook.com/ahkscript.org
If you have forum suggestions please submit a
Check Out WebWriter
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: ObjCount() or ObjLength() or ObjLen()

13 Jul 2014, 12:29

I'm totally fine with using UDF's. Perhaps in the future of the language this might get implemented. .Count/.Length kind of exists in most languages that uses arrays/objects, but then again, AutoHotkey shines in its quirkiness. Moving forward, just being curious, @lexikos, if a change of heart happens and this gets implemented, will it really affect performance or the other way around?
just me
Posts: 8522
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: ObjCount() or ObjLength() or ObjLen()

13 Jul 2014, 15:01

Well, that's my kind of workaround (UDF). I don't know whether it has some unwanted side-effects, but lexikos should know:

Code: Select all

#NoEnv
SetBatchLines, -1
Dummy := []
Loop, 1999
   If (A_Index & 1)
      Dummy[A_Index] := A_Index
Before := Dummy.GetCapacity()
S := A_TickCount
Loop, 10000
   L1 := Util_ObjCount(Dummy)
T1 := A_TickCount - S
S := A_TickCount
Loop, 10000
   L2 := ObjCount(Dummy)
T2 := A_TickCount - S
After := Dummy.GetCapacity()
MsgBox, %Before% - %L1% (%T1% ms) - %L2% (%T2% ms) - %After%
ExitApp

ObjCount(Obj) {
   ; If SetCapacity(0) doesn't effect performance, Obj.Clone() could be replaced by Obj
   Return Obj.Clone().SetCapacity(0)
}
Util_ObjCount(Obj) { ; by joedf
    if (!IsObject(Obj))
        return 0
    z:=0
    for k in Obj
        z+=1 ;or z:=A_Index
    return z
}
lexikos
Posts: 8292
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: ObjCount() or ObjLength() or ObjLen()

13 Jul 2014, 21:38

tank wrote:maxindex is all but useless.
So we don't need Array.Length? That's the main purpose that MaxIndex already serves. It just has an unintuitive name and behaviour when there are no integer keys.
The point I share with joedf is there are tons of array proprties and functions that are necesay even though you disagree.
Clearly we can do (and have done) without them, therefore they are not "necesay". Of course there are many functions that would be useful. Adding them is not what v2 is about, nor was my goal ever to have built-in methods for every potentially useful function.
Coco wrote:@lexikos, if a change of heart happens and this gets implemented, will it really affect performance or the other way around?
A change of heart is unlikely. That should work out well enough for you, since as I already said, I'm not against implementing it. No, it won't affect performance.


I was thinking of implementing both Length and Count. I'm not sure whether they would be properties (x.Length) or methods (x.Length()), or whether to use "Len" (as with RegExMatch objects; matches StrLen) or "Length" (more familiar for users of other languages). In any case, assigning x.Length := ... would probably override the built-in property (though ObjLength() may be available).

There's enough overlap between Length and MaxIndex that I may also consider removing MinIndex/MaxIndex. The functionality they provide could be achieved other ways:

Code: Select all

x := new Object
x.Length := 0  ; For demonstration purposes.
MsgBox % x.MinIndex() ".." x.MaxIndex()
Loop 2
	x[A_Index] := 1
	, x.Length += 1  ; For demonstration purposes.
MsgBox % x.MinIndex() ".." x.MaxIndex()

class Object {
	MinIndex() {
		for k in this
			if type(k) == "Integer"
				return k  ; Relies on Integer keys being enumerated in order.
			else
				return ""  ; Relies on Integer keys always being first.
	}
	MaxIndex() {
		return (this.Length || (this.HasKey(0) ? 0 : ""))
	}
}
The only real benefit of removing them is reducing API/documentation clutter.

If not removed, I was considering whether MinIndex/MaxIndex should remain as methods() or become properties.
User avatar
joedf
Posts: 8545
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: ObjCount() or ObjLength() or ObjLen()

13 Jul 2014, 22:45

@just me Very Interesting! I wonder if there are any side effects...
User avatar
joedf
Posts: 8545
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: ObjCount() or ObjLength() or ObjLen()

15 Jul 2014, 23:42

@justme I have found something...
replace the line
Dummy[A_Index] := A_Index with
Dummy[A_Index "wiuroiwe"] := A_Index "fkdgljfdkjgkld" or even Dummy[A_Index "a"] := A_Index "a"
and the counting performance drops drastically
for ObjCount()
but, Util_ObjCount() is unaffected... :? :geek: :?:
lexikos
Posts: 8292
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: ObjCount() or ObjLength() or ObjLen()

16 Jul 2014, 00:58

; If SetCapacity(0) doesn't effect performance, Obj.Clone() could be replaced by Obj
Return Obj.Clone().SetCapacity(0)
SetCapacity(0) causes a single memory reallocation and copy, whereas Clone also copies every string key and value from the object into separate, newly allocated blocks of memory. The more data in the object, the more drastically the function using Clone will slow down. On the other hand, the time required for SetCapacity on its own depends on the number of key-value pairs, but is probably much less.

Using SetCapacity directly on Obj would potentially cause an extra memory reallocation and copy the next time a key-value pair is added.

A built-in function would be implemented more like this (but with type checking):
DO NOT USE this code (it was posted for purely academic purposes):

Code: Select all

ObjCount(obj) {
    return NumGet(&obj+4*A_PtrSize)  ; obj->mFieldCount -- OK for v1.1.15.01 and v2.0-a48
}

MsgBox % ObjCount({a:1, 1:0, "1":0})
Edit: As noted above, this uses the correct offset for v1.1.15.01 and v2.0-a048. It relies on undocumented details and is therefore not officially guaranteed to work, ever. Unofficially it will work in these versions. You may verify that it is valid for other versions by checking the struct layout in the AutoHotkey source code. If you want to use it, be prepared for your script to crash or otherwise behave incorrectly. (It would be safer with type checking, but I do not recommend using it at all, ever.)
Last edited by lexikos on 27 Apr 2017, 06:08, edited 1 time in total.
Reason: Added warnings labels.
User avatar
joedf
Posts: 8545
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: ObjCount() or ObjLength() or ObjLen()

16 Jul 2014, 01:09

Nice lexikos
getting it directly from the struct, cool!
Thanks for sharing, I didnt think of it. :)
User avatar
joedf
Posts: 8545
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: ObjCount() or ObjLength() or ObjLen()

16 Jul 2014, 02:19

Update: Just making sure here... you actually mean this obj->mFieldCountMax, right? otherwise im confused :P
just me
Posts: 8522
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: ObjCount() or ObjLength() or ObjLen()

16 Jul 2014, 11:28

I suppose that mFieldCountMax holds the current capacity of the object as returned by Obj.GetCapacity() and is stored at &obj+5*A_PtrSize.
User avatar
joedf
Posts: 8545
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: ObjCount() or ObjLength() or ObjLen()

16 Jul 2014, 12:37

For reference

Code: Select all

	IObject *mBase;
	FieldType *mFields;
	IndexType mFieldCount, mFieldCountMax; // Current/max number of fields.

	// Holds the index of first key of a given type within mFields.  Must be in the order: int, object, string.
	// Compared to storing the key-type with each key-value pair, this approach saves 4 bytes per key (excluding
	// the 8 bytes taken by the two fields below) and speeds up lookups since only the section within mFields
	// with the appropriate type of key needs to be searched (and no need to check the type of each key).
	// mKeyOffsetObject should be set to mKeyOffsetInt + the number of int keys.
	// mKeyOffsetString should be set to mKeyOffsetObject + the number of object keys.
	// mKeyOffsetObject-1, mKeyOffsetString-1 and mFieldCount-1 indicate the last index of each prior type.
	static const IndexType mKeyOffsetInt = 0;
	IndexType mKeyOffsetObject, mKeyOffsetString;
just me
Posts: 8522
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: ObjCount() or ObjLength() or ObjLen()

16 Jul 2014, 12:55

For reference:

Code: Select all

ResultType Object::_GetCapacity(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
{
	if (aParamCount == 1)
	{
		SymbolType key_type;
		KeyType key;
		IndexType insert_pos;
		FieldType *field;

		if ( (field = FindField(*aParam[0], aResultToken.buf, /*out*/ key_type, /*out*/ key, /*out*/ insert_pos))
			&& field->symbol == SYM_OPERAND )
		{
			aResultToken.symbol = SYM_INTEGER;
			aResultToken.value_int64 = field->size ? _TSIZE(field->size - 1) : 0; // -1 to exclude null-terminator.
		}
		// else wrong type of field; leave aResultToken at default, empty string.
	}
	else if (aParamCount == 0)
	{
		aResultToken.symbol = SYM_INTEGER;
		aResultToken.value_int64 = mFieldCountMax;
	}
	return OK;
}
How would you implement _GetCount()?
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

Re: ObjCount() or ObjLength() or ObjLen()

16 Jul 2014, 13:22

If I understand right, that is quite simple:

Code: Select all

ResultType Object::_Count(ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
{
	if (aParamCount)
		return OK;
	aResultToken.symbol = SYM_INTEGER;
	aResultToken.value_int64 = (__int64)mFieldCount;
	return OK;
}

Return to “AutoHotkey v2 Development”

Who is online

Users browsing this forum: No registered users and 4 guests