'For k, v In Array' loop should iterate through all items

Propose new features and changes
-_+
Posts: 70
Joined: 06 Dec 2014, 12:43

'For k, v In Array' loop should iterate through all items

20 Jan 2016, 12:29

I want For loop work the same way it works in Python.
In AHK if you run a For loop on some array and during that loop delete an item - all the next items' indexes in that array get decremented.
If during the For loop's work you delete an item - your loop from now on will not iterate through all the next items after the deleted one.

Code: Select all

Arr := ["a", "b", "c", "d", "e"]
For k, v In Arr
{
	If (A_Index < 4)	; Delete first 3 items.
		Arr.Remove(A_Index)
	Else	; This will never get executed, although the array still contains some items, that were not iterated through by the loop.
		MsgBox, %v%	; This should have worked for the 4th and the 5th iterations of the loop, but because the items in the array got shifted - the loop stopped after just 3 iterations.
}
This is very uncomfortable and leads to problems, like when a programmer wants to delete some items during the For loop (suppose, the decision which ones to delete is based on some checks executed in that loop) - he has to run 2 For loops instead: first time he should run the checks and gather indexes of the items to be deleted, then sort them in the descending order and then run another loop that removes each gathered index.

Example task:
You have an array Arr := [1, 0, 6, 2, 0, 2, 9, 1, 1, 1, 54] in which you need to delete all the items, who's value is bigger than 1.

This will fail now (but similar thing would work totally fine in Python):

Code: Select all

Arr := [1, 0, 6, 2, 0, 2, 9, 1, 1, 1, 54]
For k, v In Arr
	(v > 1 ? Arr.Remove(k) : "")
Instead, you have to do something like this:

Code: Select all

Arr := [1, 0, 6, 2, 0, 2, 9, 1, 1, 1, 54], RemoveTheseIndexes := ""
For k, v In Arr
	(v > 1 ? RemoveTheseIndexes .= "|" k : "")
RemoveTheseIndexes := LTrim(RemoveTheseIndexes, "|")
Sort, RemoveTheseIndexes, N R D|
Loop, Parse, RemoveTheseIndexes, |
	Arr.Remove(A_LoopField)
Removetheseindexes := ""
User avatar
sinkfaze
Posts: 616
Joined: 01 Oct 2013, 08:01

Re: 'For k, v In Array' loop should iterate through all items

20 Jan 2016, 22:53

Or you could do this:

Code: Select all

Arr := [1, 0, 6, 2, 0, 2, 9, 1, 1, 1, 54]
Loop
{
	pass :=	1
	For k, v In Arr
		if	(v > 1)
		{
			Arr.Remove(k), pass :=	0
			break
		}
}	Until	pass
;For k, v in Arr
;	MsgBox, Key:`t%k%`nValue:`t%v%
return
Coco-guest

Re: 'For k, v In Array' loop should iterate through all items

21 Jan 2016, 01:21

You can clone the object:

Code: Select all

Arr := [1, 0, 6, 2, 0, 2, 9, 1, 1, 1, 54]
for k, v in Arr.Clone()
	(v > 1 ? Arr.Remove(k) : "")
User avatar
haichen
Posts: 631
Joined: 09 Feb 2014, 08:24

Re: 'For k, v In Array' loop should iterate through all items

21 Jan 2016, 03:49

@coco : the Arr.Remove() changes the index. So it doesn't solve the problem.

Code: Select all

Arr := [1, 0, 6, 2, 0, 2, 9, 1, 1, 1, 54]
for k, v in Arr.Clone()
{
  x:=arr.pop()
  (x >1 ? "" : arr.InsertAt(1,x))
}

for k, v in Arr
  s .= v " "
msgbox, % s
Is there a shorter way?
I thought this will do it but it doesn't

Code: Select all

for k, v in Arr.Clone()
  (arr.pop() >1 ? "" : arr.InsertAt(1,arr[arr.maxindex()]))
Edit :
Found it

Code: Select all

for k, v in Arr.Clone()
  ( arr[arr.maxindex()]>1 ? arr.pop() : arr.InsertAt(1,arr.pop()))
-_+
Posts: 70
Joined: 06 Dec 2014, 12:43

Re: 'For k, v In Array' loop should iterate through all items

21 Jan 2016, 10:06

sinkfaze wrote:Or you could do this:

Code: Select all

I could, but this is even less optimal than my suggested solution, since in your case the loops end up iterating through the same items in the array more than twice (or at least twice, if you remove your Break).
I could also find a few more solutions to the problem, but they all will be as non-optimal.

The optimal (and also the shortest one) way would only be possible if For loop would iterate through all items even when some items got deleted during the loop.
This is what I suggest. The optimality.
Cloning the object is also not optimal, thought it also would solve the example problem.
User avatar
haichen
Posts: 631
Joined: 09 Feb 2014, 08:24

Re: 'For k, v In Array' loop should iterate through all items

22 Jan 2016, 03:10

The solution (trick) is: going reverse through the array. From the Last index to the first. That what my working example do. Every Item is iterated only once. And if you know how much items are in the Array you can use a simple loop to go throug the array; without using cloning.

Code: Select all

Arr := [1, 0, 6, 2, 0, 2, 9, 1, 1, 1, 54]
loop, 11
  ( arr[arr.maxindex()]>1 ? arr.pop() : arr.InsertAt(1,arr.pop()))
  
for k, v in Arr
  s .= v " "
msgbox, % s
User avatar
haichen
Posts: 631
Joined: 09 Feb 2014, 08:24

Re: 'For k, v In Array' loop should iterate through all items

22 Jan 2016, 03:22

Code: Select all

Arr := [1, 0, 6, 2, 0, 2, 9, 1, 1, 1, 54]
MaxItems := Arr.GetCapacity()
loop, % MaxItems
  ( arr[arr.maxindex()]>1 ? arr.pop() : arr.InsertAt(1,arr.pop()))
-_+
Posts: 70
Joined: 06 Dec 2014, 12:43

Re: 'For k, v In Array' loop should iterate through all items

22 Jan 2016, 16:04

haichen
that will work good only in some cases, but not in all cases.
It will totally get broken when parsing associative arrays: just try to MsgBox, % Arr.GetCapacity on Arr :={"aaa": "bbb", "ccc": "ddd", 111: 222} and other associative arrays.
You'll get a wrong MaxItems.
But okay, you may reply that one may use Arr.SetCapacity(0) prior to that and then it will return the correct MaxItems, but your arr[arr.maxindex()] will still fail.

None of the suggested solutions are optimal!
The only optimal solution is the one I suggested in the initial post!
just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: 'For k, v In Array' loop should iterate through all items

22 Jan 2016, 16:40

Example task:

Code: Select all

#NoEnv
Arr := [1, 0, 6, 2, 0, 2, 9, 1, 1, 1, 54]
Arr := Tidy(Arr)
Elements := Arr.Length()
For K, V in Arr
   MsgBox, 0, Element %A_Index% of %Elements%, %v%
ExitApp

Tidy(Arr) {
   Tidied := []
   For K, V In Arr
	  (V <= 1 ? Tidied.Push(V) : "")
   Return Tidied
}
lexikos
Posts: 9559
Joined: 30 Sep 2013, 04:07
Contact:

Re: 'For k, v In Array' loop should iterate through all items

22 Jan 2016, 18:32

The only optimal solution is the one I suggested in the initial post!
What makes you think that whatever goes on under the hood to allow your original code to work will be better than every other solution posted here?

The current implementation returns key-value pairs sequentially, by incrementing an array index. It is simple and efficient. Any other implementation is likely to be more complicated and less efficient.

Are you sure that Python actually allows this? Some quick searching seems to indicate otherwise.
It is not safe to modify the sequence being iterated over in the loop (this can only happen for mutable sequence types, such as lists). If you need to modify the list you are iterating over (for example, to duplicate selected items) you must iterate over a copy.
Source: python - Why is it not safe to modify sequence being iterated on? - Stack Overflow
Many .NET collections do not allow it either, and will throw an exception if you attempt to modify them during enumeration.
-_+
Posts: 70
Joined: 06 Dec 2014, 12:43

Re: 'For k, v In Array' loop should iterate through all items

23 Jan 2016, 08:35

lexikos wrote:What makes you think that whatever goes on under the hood to allow your original code to work will be better than every other solution posted here?
Nothing. I meant the optimality of how the code would look like if the proposed change would get into AHK.
I don't know anything about the things 'under the hood' and whether my suggestion is even doable or not, or how much it would slow things down or not.
lexikos wrote:The current implementation returns key-value pairs sequentially, by incrementing an array index. It is simple and efficient. Any other implementation is likely to be more complicated and less efficient.
Since in most tasks it doesn't really matter what direction the array will get iterated through and since the backwards direction would solve index shifting issue - it would be nice to at least have an option to make For loops iterate in backwards order.
Simple arrays are no problem here (I could get the total number of items in the array (via .GetCapacity()/.MaxIndex()/.Length()), but associative ones are, because I don't know of a way to iterate them in backwards order.
lexikos wrote:Are you sure that Python actually allows this? Some quick searching seems to indicate otherwise.
It is not safe to modify the sequence being iterated over in the loop (this can only happen for mutable sequence types, such as lists). If you need to modify the list you are iterating over (for example, to duplicate selected items) you must iterate over a copy.
Source: python - Why is it not safe to modify sequence being iterated on? - Stack Overflow
Many .NET collections do not allow it either, and will throw an exception if you attempt to modify them during enumeration.
Sorry, I was mistaken about Python. Turns out it was JS I thought off and it's not that simple there either.
User avatar
haichen
Posts: 631
Joined: 09 Feb 2014, 08:24

Re: 'For k, v In Array' loop should iterate through all items

24 Jan 2016, 08:47

-_+ I was a little confused about suddenly reading about associative arrays.
Of course my code only work for linear arrays as they are called here in the help.

As i understand it right, associative arrays (as the name says) are not in an ordered line.
There is no defined numbered index. ??

May be i'm wrong because i'm not a programmer..
but that seems to be a totally different thing, and you are mixing it.

The for loop could be changed with a custom enumerator.
But it's not clear for me if a reverse index is possible.
Someone else will know it.

Return to “Wish List”

Who is online

Users browsing this forum: No registered users and 34 guests