This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Senmori's profile picture

[Lua][Question][Solved]Why does this work?

Started by Senmori, 08 February 2013 - 08:46 AM
Senmori #1
Posted 08 February 2013 - 09:46 AM
Ok, so first of all this isn't my code. I used this to try and get more familiar with manipulating strings.

The code was found here with all credit to the guy who made it: http://www.gammon.co.../forum/?id=7805

Pastebin: http://pastebin.com/xXquxtsL

Code:
Spoiler

fuel = turtle.getFuelLevel()
ID = os.getComputerLabel()

function commas (num)
  assert (type (num) == "number" or
		  type (num) == "string")

  local result = ""
		 -- split number into 3 parts, eg. -1234.545e22
		 -- sign = + or -
		 -- before = 1234
		 -- after = .545e22
  local sign, before, after =
	 string.match (tostring (num), "^([%+%-]?)(%d*)(%.?.*)$")
		 -- pull out batches of 3 digits from the end, put a comma before them
	   while string.len (before) > 3 do
		  result = "," .. string.sub (before, -3, -1) .. result
		  before = string.sub (before, 1, -4)  -- remove last 3 digits
	   end -- while
		  -- we want the original sign, any left-over digits, the comma part,
		  -- and the stuff after the decimal point, if any
		 return sign .. before .. result .. after
	   end -- function commas

print("ID:  "..ID)
print("Fuel: "..commas(fuel))

Specifically I'm talking about these lines:

  local sign, before, after =
	  string.match (tostring (num), "^([%+%-]?)(%d*)(%.?.*)$")

		-- pull out batches of 3 digits from the end, put a comma before them

  while string.len (before) > 3 do
		result = "," .. string.sub (before, -3, -1) .. result
		before = string.sub (before, 1, -4)  -- remove last 3 digits
  end -- while

I've looked through the lua libraries and I still don't understand why it works.

Help? D:

EDIT: In case anyone was wondering what this did here's a picture of it.
Spoiler
Orwell #2
Posted 08 February 2013 - 10:39 AM
I've looked through the lua libraries and I still don't understand why it works.
I suggest that you read http://www.lua.org/pil/20.2.html and the part about string.match at http://lua-users.org...LibraryTutorial .

Now, basically what the string.gmatch does is using patterns (they are a form of regular expressions) to extract certain parts out of a string. The % denotes that the next character will indicate a group of characters it can contain rather than one. For example '%d' can be any decimal number and '%a' can be any letter. There also some magical characters that denote the closure or repetition of a pattern. For example '%d*' can be a series of 0 or more digits, '%d?' can be zero or one digits. Parentheses will enclose a part that needs to be extracted. Finally, ^ denotes the beginning of a string and $ the end of a string.
string.match() will match such a pattern to a string and if the pattern accepts the string, it will return the parts between () in order. For example:

res = string.match("a1","(%d)")  -- res would be 1
or:

x,y = string.match("123:456","(%d+): (%d+)")  -- x would be 123 and y would be 456

So, in your example:

string.match (tostring (num), "^([%+%-]?)(%d*)(%.?.*)$")
The variable num will first be converted to a string and will be passed to string.match as the string to evaluate. Then, for the pattern…
^ says that there should be the beginning of the string here. Then [%+%-] is a bit harder. Everything between [ and ] is a possible value for a character to have. So [ab] means that there can be either a 'a' or a 'b' at that spot. Also, characters with a special meaning need to be escaped. + and - both have a special meaning in patterns, so by putting a % in front of it, Lua knows that you actually want a plus or minus sign there. So, the second part [%+%-]? means that there can be a + or a - sign or nothing at all (that's what the ? says). Next, (%d*) will extract a series of decimal numbers; this spot can also be empty. Finally: (%.?.*) will match a dot and any following characters: %. means the actual dot, but . stands for any character. So there can be a dot behind the number (doesn't have to be) and any number of any characters behind that. (this will be the decimal digits and the dot in front of it). Then there is the $ sign telling Lua that the string has to end here. The 3 pair of parentheses will each yield a return value, in this case:

local sign, before, after
The sign is either +,- or an empty string. 'before' is the integer part of a number and 'after' is the decimal part of a number including the dot. If the string doesn't match the pattern, every variable will be nil.

Also, to wrap it up, this example will have the dot in the after part. In certain most cases, you want just the decimal digits and not that dot. This can be done by putting the '%.?' outside of the parentheses:

local sign, before, after = string.match (tostring (num), "^([%+%-]?)(%d*)%.?(.*)$")

I'm not going to get into the other lines in the code just yet, I'm going to wait for any questions you might have. :)/>
Senmori #3
Posted 08 February 2013 - 11:21 AM
In between the first link (http://www.lua.org/pil/20.2.html) and your explanation it makes perfect sense actually. I guess I just needed a breakdown of what it was actually doing.
My only question concerning this would be for '(.*)'. Why wouldn't you use '(.-)'? In my circumstance wouldn't it be better since I'm not really looking for anything after a decimal?
ChunLing #4
Posted 08 February 2013 - 11:25 AM
If you don't want anything after a decimal then just omit the third pattern.

It's worth noting that this:
while string.len (before) > 3 do
   result = "," .. string.sub (before, -3, -1) .. result
   before = string.sub (before, 1, -4)  -- remove last 3 digits
end -- while
will only work properly if result has been declared and assigned an initial value (like "") beforehand (which I am quite sure is the case).
Orwell #5
Posted 08 February 2013 - 11:29 AM
In between the first link (http://www.lua.org/pil/20.2.html) and your explanation it makes perfect sense actually. I guess I just needed a breakdown of what it was actually doing.
My only question concerning this would be for '(.*)'. Why wouldn't you use '(.-)'? In my circumstance wouldn't it be better since I'm not really looking for anything after a decimal?
The example from that guy isn't very solid. :P/> But '(.-)' would require it to have something behind the dot. Can you describe the exact format you want to match, like giving some example strings and the outcome you'd want?
Senmori #6
Posted 08 February 2013 - 12:02 PM
There's no real format I need to match which is why I need to match as many as possible. I just needed the most efficient way to format a string. I'm making a master list of all my turtles and their fuel levels. Once updated(every 'x' moves) they will send an update to the master server and it will display their current fuel level beside that turtles' label. Once I accomplish that I'll need to add all the current fuel levels and figure out how much fuel is used on average per turtle(fuel added up / # of turtles) or maybe how much power is used on average.
I don't really know why I'm making it but it seems like a fun project.

The decimals would probably come into play with the averaging of fuel, which is why I'd need the decimals.

My next question is how would I stop showing after 'x' amount of decimal points. Meaning if the average was 500.5649978, how would I only show 500.56.
Of course that's an example but I'd rather my monitors not get spammed with useless characters.
ChunLing #7
Posted 08 February 2013 - 12:06 PM
You might want to just use string.format(), it's probably going to be a bit faster than anything you could write yourself.
Orwell #8
Posted 08 February 2013 - 12:19 PM
Indeed, I was just gonna say that you can do this for the number:

local num = 123.456789
local representation = string.format("%.3f", num)
print(representation)  -- would print '123.457'

For the other part, it's still not clear what you want to do, but here is something that may come in handy. Imagine that you have multiple entries that you want to send over rednet. And it's always in the format of 'turtle7:34'. Like that's the fuel level of turtle number 7. And you will concatenate all the records with ';'. This could give: "turtle7:34;turtle9:651;turtle3:15".
You can make a table of that like this:

local str = "turtle7:34;turtle9:651;turtle3:15"
local map = {}
for label, fuel in string.gmatch(str, "(%w*): (%w*)") do   -- drop the space between : and (, the forum keeps making it a smiley
  map[label] = tonumber( fuel )
end
print( map["turtle7"] ) -- would print '34'
Senmori #9
Posted 08 February 2013 - 12:45 PM
For clarification I just want to print turtle labels and fuel levels to a monitor like so "Turtle 7: 500" for however many turtles I have. That's easy enough to do with tables.

Thanks for the help, and I think I'll go with string.format() and, most likely, the two codes you put up Orwell.

Thanks again Orwell and ChunLing. I only have one cookie though so you two would have to fight to the death over it.
^_^/>
ChunLing #10
Posted 08 February 2013 - 06:59 PM
For the cookie!

Meh, doesn't quite have the same ring as "For the Emperor!"