unintentional change to a table

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
IAmTheRealSpartacus
Prole
Posts: 9
Joined: Mon Nov 07, 2011 8:08 pm

unintentional change to a table

Post by IAmTheRealSpartacus »

Hi,

I'm trying to do the following thing.

I have a table, say

Code: Select all

  set = { { name = "gold",    amount = 10   },
          { name = "silver",  amount = 100  } }
Now I wrote a function that can add values to it from a similar set, like such:

Code: Select all

function addSet(setToBeAdded, s)

  local setToBeReturned = s

  for k1, v1 in ipairs(setToBeAdded) do
    for k2, v2 in ipairs(setToBeReturned) do
      if v1.name == v2.name then
        v2.amount = v2.amount + v1.amount
      end
    end
  end

  return setToBeReturned
end
The problem is that, even though I'm adding values to the table 'setToBeReturned', the new values show up in the original table, 'set', even though I explicitly tell the function to copy the values from the original table into the table 'setToBeReturned'. This gives problems in the next program, where I'm telling Lua to stop substracting from the main table if one of the values is zero. It recognizes that but still keeps on substracting. what am I doing wrong? (instructions: run code and press spacebar 11 times to see the amount of gold go negative)

Code: Select all

local set = {}
local success

function love.load()

  set = { { name = "gold",    amount = 10   },
          { name = "silver",  amount = 100  } }

  success = true

end

function love.draw()

  for k, v in ipairs(set) do
    love.graphics.print(v.amount, 0, k * 20)
  end

  if success == true then love.graphics.print("can substract", 100, 0)
  else love.graphics.print("cannot substract", 100, 0)
  end

end

function love.keypressed(key, unicode)

  -- when spacebar is pressed...
	if unicode == 32 then

    --add increment to set and store result in newSet
    increment = { { name = "gold", amount = -1} }
		newSet = addSet(increment, set)

    --check if none of the values are negative
    success = true

    for k, v in ipairs(newSet) do
      if v.amount < 0 then
        success = false
      end
    end

    --if none of the values are negative, copy newSet into set
    if success == true then set = newSet end

	end

	if key == "q" or key == "escape" then
		love.event.push("q")
	end

end


function addSet(setToBeAdded, s)

  local setToBeReturned = s

  for k1, v1 in ipairs(setToBeAdded) do
    for k2, v2 in ipairs(setToBeReturned) do
      if v1.name == v2.name then
        v2.amount = v2.amount + v1.amount
      end
    end
  end

  return setToBeReturned
end
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: unintentional change to a table

Post by Robin »

IAmTheRealSpartacus wrote:The problem is that, even though I'm adding values to the table 'setToBeReturned', the new values show up in the original table, 'set', even though I explicitly tell the function to copy the values from the original table into the table 'setToBeReturned'.
No, you don't copy anything. Lua never copies anything. a = b means "a points to whatever b points to", not "the value of b is copied and put in a".

This might be what you want:

Code: Select all

function addSet(setToBeAdded, s)

  local setToBeReturned = {}

  for k, v in ipairs(s) do
     setToBeReturned[k] = {v.name, v.amount}
  end
  for k1, v1 in ipairs(setToBeAdded) do
    for k2, v2 in ipairs(setToBeReturned) do
      if v1.name == v2.name then
        v2.amount = v2.amount + v1.amount
      end
    end
  end

  return setToBeReturned
end
This is still not an optimal solution, though.

EDIT:
This should make it O(n) instead of O(n^2):

Code: Select all

function addSet(setToBeAdded, s)
  local temp = {}
  local setToBeReturned = {}

  for k, v in ipairs(s) do
     temp[v.name] = v.amount
  end
  for k, v in ipairs(setToBeAdded) do
    temp[v.name] = temp[v.name] + v.amount -- you can add a check to see if it exists in temp if you want
  end

  for k, v in pairs(temp) do
    setToBeReturned[#setToBeReturned+1] = {name = k, amount = v}
  end
  return setToBeReturned
end
Note it does not preserve order, though. (So the items may be shuffled.)
Help us help you: attach a .love.
User avatar
rhezalouis
Party member
Posts: 100
Joined: Mon Dec 07, 2009 10:27 am
Location: Indonesia
Contact:

Re: unintentional change to a table

Post by rhezalouis »

Hi TheRealSpartacus,

First, you might want to change your data structure (or how you define this set in Lua) because you actually could use the item names (i.e. strings) as the key for your table instead of storing it inside the table.
So, instead of using this array:
IAmTheRealSpartacus wrote:

Code: Select all

set = { { name = "gold",    amount = 10   },
        { name = "silver",  amount = 100  } }
your set would be better if defined like this hash table (single value per entry):

Code: Select all

tAmount = {
  gold = 10;
  silver = 100;
}
tPrice = {
  gold = 150;
  silver = 30;
}
or if you want these several values (amount and price) to be put together in the same entry:

Code: Select all

tInventory = {
  gold = {amount = 10; price = 150};
  silver = {amount = 100; price = 30}
}
So when you update an entry in this table, your function could instantly pick the relevant entry; it doesn't have to iterate through all of the entries just to find the entry you want to manipulate.

Second, regardless of this data type suggestion, the issue you had is happening because when you do:

Code: Select all

set = {}
you are actually doing two things:
  1. create an anonymous table
  2. store the reference to that table in the variable set
so when you create a local variable like this:
IAmTheRealSpartacus wrote:

Code: Select all

local setToBeReturned = s
you only pass the reference number (which is used to access the underlying anonymous table) from variable set to local variable setToBeReturned. Lua won't create you a new table for that local variable (remember that a table is only created using this ' = {}' operation). Which means when you make any changes to 'this local variable', you actually change the table that is referenced by it. To show this you could do:

Code: Select all

a = {1}           --create a table at an address; store the address to a variable
local b = a       --copy the reference/address to that table to a local variable
print(a, b)       -->table: 08DFE790	table: 08DFE790 --as you can see, these variables are representing the same object
print(a[1], b[1]) -->1	1
b[1] = 2          --changes made using the reference stored in the local variable 'b' would affect 'a'
print(a[1], b[1]) -->2	2
Try to use the new data structure and find out another way to implement your requirement.

P.S. Hi Robin, let me ftfy.
Robin wrote:Lua never copies anything-being-referenced but its reference.
Aargh, I am wasting my posts! My citizenshiiiip... :o
IAmTheRealSpartacus
Prole
Posts: 9
Joined: Mon Nov 07, 2011 8:08 pm

Re: unintentional change to a table

Post by IAmTheRealSpartacus »

Thanks a lot guys! I'm new to Lua and I figured that at some point I'd get things confused with other languages.
Lua never copies anything. a = b means "a points to whatever b points to", not "the value of b is copied and put in a".
Robin: In hindsight I should have seen this coming :)

rhezalouis: thanks, I think the following has become my preferred data structure:

Code: Select all

tInventory = {
  gold = {amount = 10; price = 150};
  silver = {amount = 100; price = 30}
}
As I won't need a price field, would it suffice to say:

Code: Select all

tInventory = { gold = 10; silver = 100 }
?
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: unintentional change to a table

Post by Robin »

IAmTheRealSpartacus wrote:As I won't need a price field, would it suffice to say:

Code: Select all

tInventory = { gold = 10; silver = 100 }
?
Sure, that is in fact the structure of the "temp" table in my second suggestion.
Help us help you: attach a .love.
IAmTheRealSpartacus
Prole
Posts: 9
Joined: Mon Nov 07, 2011 8:08 pm

Re: unintentional change to a table

Post by IAmTheRealSpartacus »

Robin wrote:
IAmTheRealSpartacus wrote:As I won't need a price field, would it suffice to say:

Code: Select all

tInventory = { gold = 10; silver = 100 }
?
Sure, that is in fact the structure of the "temp" table in my second suggestion.
I've tried it out but for some reason the table appears empty... At least according to table.getn. See this code where I try to print the table:

Code: Select all

local t = {}

function love.draw()

	t = { gold = 10; silver = 100 }	

	love.graphics.setColor(255,255,255,255)

	if table.getn(t) == 0 then love.graphics.print("empty", 0, 0) end
	
	i = 0
	for k, v in ipairs(t) do
		love.graphics.print(k,   0, i * 10)
		love.graphics.print(v, 100, i * 10)
		i = i + 1
	end
	
end
I end up with a black screen and white letters saying "empty". What's going wrong?
User avatar
thelinx
The Strongest
Posts: 857
Joined: Fri Sep 26, 2008 3:56 pm
Location: Sweden

Re: unintentional change to a table

Post by thelinx »

table.getn (which is deprecated, use #myTable) only counts the sequence part of the table. (The part that's indexed by numbers, not keys)

ipairs also only loops through the sequence part. Use pairs(myTable) to loop through all values in the table.
IAmTheRealSpartacus
Prole
Posts: 9
Joined: Mon Nov 07, 2011 8:08 pm

Re: unintentional change to a table

Post by IAmTheRealSpartacus »

thelinx wrote:table.getn (which is deprecated, use #myTable) only counts the sequence part of the table. (The part that's indexed by numbers, not keys)

ipairs also only loops through the sequence part. Use pairs(myTable) to loop through all values in the table.
I feel like such a noob :) Thanks!
Post Reply

Who is online

Users browsing this forum: Google [Bot] and 152 guests