In pairs not finding all indexes in array?

Im trying to make a player detection system in a for loop. To achieve this, I am searching a region for parts and removing any part that isnt a player part from the table. My current draft works up until there are 4 indexes left in the table and rechecks the region, thus adding the removed parts back to the table. I have tried adding task.wait()s above and underneath the table.remove call, but it always resets the table when the 4th index is reached, no matter how long of a time I give it. Is there something wrong with the way I’m finding the parts index? Or are there weird rules to removing values from arrays that I’m not aware of? Any help would be appreciated!

Server Script:

for i = 1, 20 do 
	task.wait(0.05)
	local playerEffects = game.Workspace:FindFirstChild(player.Name.." Effects")
	local partsInHibox = workspace:FindPartsInRegion3(Hitbox, game.Workspace.Baseplate, 20) --Hitbox is predefined as Region3.new(pos1, pos2) [pos1 and pos2 are the bottom left and bottom right corner of a part]
	for _, v in pairs(partsInHibox) do 
					
			if v.Name == "Head" or v.Name == "Left Arm" or v.Name == "Left Leg" or v.Name == "Right Arm" or v.Name == "Right Leg" or v.Name == "Torso" then 
				local PlayerTarget = Players:GetPlayerFromCharacter(v.Parent)
				print(PlayerTarget)
				EarthAttack:FireClient(PlayerTarget, "ScreenShakeTrue") --remoteEvent in ReplicatedStorage
			elseif v.Name ~= "Head" or v.Name ~= "Left Arm" or v.Name ~= "Left Leg" or v.Name ~= "Right Arm" or v.Name ~= "Right Leg" or v.Name ~= "Torso" then
				local function findValue(array, itemName) --this is literally taken from the roblox documentation about changing array values, I half understand what its saying tbh
					for currentIndex = 1, #array do
						if array[currentIndex] == itemName then
							return currentIndex
						end
					end
				end
			local valueFound = findValue(partsInHibox, v.Name)
			table.remove(partsInHibox, valueFound)
		end
						
		print(partsInHibox)
		if partsInHibox == {} then
			EarthAttack:FireAllClients("ScreenShakeFalse") --same remoteEvent
		end			
	end
end
1 Like

Removing something from an array moves the values at higher indices to lower indices. If you do this while iterating through the array from lowest index to highest index, this will cause the loop to miss some values.

For example, if we have an array with three values and on the first iteration we remove the value at index 1, the value that was at index 2 is moved to index 1 and the value that was at index 3 is moved to index 2. On the next iteration, index will be two and value will be the value that was originally at index 3. The value that was originally at index 2 was entirely skipped.

A simple way to fix this is to iterate backwards.

for i = #array, 1, -1 do
	local v = array[i]
	if condition then
		table.remove(array, i)
	end
end

Another alternative is this.

local numberOfValuesRemovedSoFar = 0
for loopIteration = 1, #array do
	local arrayIndex = loopIteration - numberOfValuesRemovedSoFar
	if condition then
		table.remove(array, arrayIndex)
		numberOfValuesRemovedSoFar += 1
	end
end

Also, the condition partsInHibox == {} will never be true because {} creates a new table object and two different tables will not be considered equal even if their contents are equal.

The purpose of the condition seems to be checking whether the partsInHibox array is empty. Since partsInHibox is an array, you can do this by checking #partsInHitbox == 0.

If you needed to check whether a dictionary is empty, you could check next(yourTable) == nil.

Additionally, your condition for removing parts would be true for any part although it is only evaluated for the parts for which the first condition is false. For example, if the name of the part was Head (which won’t happen, though), v.Name ~= "Head" would be false but v.Name ~= "Left Arm" would be true, and since you are using the logical operator or, this would result in the elseif condition being true.

When inverting a condition like the one in the if statement, you need to change not only the comparison operator (which you have done) but also the logical operator. In this elseif, the correct operator would be and.

However, in this case, inverting it is unnecessary since you can just use an else.

Here’s a modified version of your code.

for i = 1, 20 do 
	task.wait(0.05)
	local playerEffects = game.Workspace:FindFirstChild(player.Name.." Effects")
	local partsInHitbox = workspace:FindPartsInRegion3(Hitbox, game.Workspace.Baseplate, 20) --Hitbox is predefined as Region3.new(pos1, pos2) [pos1 and pos2 are the bottom left and bottom right corner of a part]
	for arrayIndex = #partsInHitbox, 1, -1 do
		local v = partsInHitbox[arrayIndex]
					
		if v.Name == "Head" or v.Name == "Left Arm" or v.Name == "Left Leg" or v.Name == "Right Arm" or v.Name == "Right Leg" or v.Name == "Torso" then 
			local PlayerTarget = Players:GetPlayerFromCharacter(v.Parent)
			print(PlayerTarget)
			EarthAttack:FireClient(PlayerTarget, "ScreenShakeTrue") --remoteEvent in ReplicatedStorage
		else		
			table.remove(partsInHibox, arrayIndex)
		end
						
		print(partsInHitbox)
		if #partsInHitbox == 0 then
			EarthAttack:FireAllClients("ScreenShakeFalse") --same remoteEvent
		end			
	end
end
3 Likes

Thank you so much! I didnt realize the for loop is passing every value through the if statement at once. I thought it finds each value separately and applies whatever written code below to it, and it alone, which would have stumped me for a while. Is it okay to ask however, what i = #partsInHitbox, 1, -1 is telling the script to do exactly? And on top of that, why it enables you to no longer need to find the index of the specific parts that need to be removed? Thanks for the help regardless, this has helped me understand a bit more about arrays :slight_smile:

The #-operator tells the length of an array (number of values in the array). So #partsInHitbox is the number of parts in the partsInHitbox array. Every value in an array has an index that is an integer. The value can be accessed using this index. In Luau (and Lua), array indices start from 1. They are consecutive integers and the index of the last value in the array is equal to the length of the array. So #partsInHitbox is also the index of the last value in the array.

The line for i = #partsInHitbox, 1, -1 do tells the following:

  • On the first loop iteration, i is equal to #partsInHitbox
  • In order for the loop body to run, i must be greater than or equal to 1 (1 is a lower bound for i).
  • After every iteration of the loop (iteration means running the loop body), i is incremented by -1. So on the second iteration, i will be #partsInHitbox - 1, on the third iteration it will be #partsInHitbox - 2, and so on.

If there were 5 parts in partsInHitbox, the inner loop’s variable would get the following values: 5, 4, 3, 2, 1.

The loop increment (-1 in this case) tells not only how much to increment the loop variable but also whether the loop limit (1 in this case) is a lower bound or an upper bound for the loop variable. If the increment is negative, the limit is a lower bound, if it’s positive, the limit is an upper bound. If it’s zero, the loop will be infinite unless broken with break or return (or error). If no increment is provided, the increment will be 1. So in the outer loop (for i = 1, 20 do), the increment is 1.

When there’s a variable in an outer scope and a variable with the same name in an inner scope, the code in the inner scope will use the inner scope’s variable while code that isn’t in the inner scope will use the outer scope’s variable. So the inner loop will not affect the outer loop’s i. Since i in this inner loop represents an array index unlike the i variable in the outer loop, I’ll actually edit the code I gave such that it’s called arrayIndex instead of just i. Removing the inner loop’s own i also means that i will be the same in the outer scope and the inner scope which is better for clarity as well.

Because the loop variable of the inner loop represents an index, we can directly use it in table.remove. Even the inner loop that you used earlier (for _, v in pairs(partsInHibox) do) gives you both index and value (_ is the index and v is the value). However, as mentioned, it isn’t suitable for this use case because of the skipping problem.

If you only knew the value then getting the index of the value would require searching through the indices until you find the index at which the value is. But when you know the index, you can directly fetch or remove the value using it. Getting the value from an array using the index of the value is as simple as value = array[index].

I don’t understand what you mean with “the for loop is passing every value through the if statement at once”. Could you clarify that? The loop runs one iteration at a time and processes one (index, value)-pair on each iteration.

Hmm, I’m confused. Would this not be removing all parts from a table? How is it knowing that I want to exclude the “Player” parts, and leave those in partsInHitbox.

When you originally said,

if the name of the part was Head (which won’t happen, though), v.Name ~= "Head" would be false but v.Name ~= "Left Arm" would be true

I thought you were saying that all the parts are getting passed down the argument at once, thus triggering the first if to be true, but another but also triggering the elseif to be true as well. I now see you meant I was creating a double negative with ~= and or. Are you saying though, that an entire iteration is getting taken up by a singular i,v pair? I thought the for loop was creating separate threads for each i,v pair and all the threads created counted as the first instance.

Yes, an entire loop iteration is taken by a singular i, v pair. The for loop does not create separate threads for each pair. When it has finished one iteration, it moves to the next iteration, and thus to the next pair. What do you mean with “all the threads created counted as the first instance”?

And no, the code will not remove all parts from the table (unless there are only non-character parts in the table, of course). The code in the else block will only run if the condition on the if line is false.

Meaning every hypothetical thread that the for loop was creating for each i,v pair (which it evidently doesn’t do), was all being stored under a table or value that was named “First Iteration” or something similar. Thus the next group of threads created for the i,v pairs in inPartsHitbox, created a new table or value called “Second Iteration” and so on. Thats how I thought the for loops were operating, but now I understand otherwise. Will be much more conscious on the bounds I set for my loops in the future for sure now, thank you!

Also yes, I broke it down after I sent that and see now why the v variable is created, and what its doing in the argument :slightly_smiling_face:

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.