Small Useful Functions

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.
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: Small Useful Functions

Post by Inny »

Code: Select all

function reduce(fn, array, initial)
  local result = initial or 0
  for i = 1, #array do
    result = fn(result, array[i], i)
  end
  return result
end
I'm fascinated with functional programming lately.

example:

Code: Select all

sum = function(array) return reduce(function(a, v) return a + v end, array) end
Actually, you're supposed to use bind with this

Code: Select all

function bind(fn, arg)
  return function(...) return fn(arg, ...) end
end

add = function(a, v) return a + v end
sum = bind(reduce, add)
Edit: szensk is right, the 'initial' parameter shouldn't be named zero, as it's meant to be the identity element for the group-operation it works over, and a multiplication based reduction would need to start with 1, not 0.
Last edited by Inny on Wed Apr 15, 2015 12:32 am, edited 1 time in total.
szensk
Party member
Posts: 155
Joined: Sat Jan 19, 2013 3:57 am

Re: Small Useful Functions

Post by szensk »

Very cool.

nit: Reduces parameter 'zero' should, for clarity, be called something like 'initial'. It is necessary for 'zero' to have a value other than 0 (for example: product) which must be why you have it as a parameter. That's a bit of unexpected value for 'zero', yea?
User avatar
Ref
Party member
Posts: 702
Joined: Wed May 02, 2012 11:05 pm

Re: Small Useful Functions

Post by Ref »

Robin wrote:That last one can be one line:

Code: Select all

function inherit(parent, child)
	return setmetatable(child or {}, {__index = parent})
end
Ran into a hard to find problem related to the use of the above function.
Turns out that if the parent table has a table-field, it is passed to the child by reference.
If child's table-field changed, so will parent's and all other related children's.

Example: parent = {{x=100,y=100,w=100,h=100},color={0,0,255}}
Changing child's 'x' value will change the parent's 'x'.

Have created the following work-around.
Seems to work but would appreciate a more experienced programmer's check - improvement.
(Can only handle single level of table nesting.)

Code: Select all

function inherit( parent, child )					-- adds missing fields to child from parent
	for I, v in pairs( parent ) do
		if type( v ) == 'table' then				-- check for table in parent
			if not child[i] then child[i] = {} end	-- supply child table-field if not present
			setmetatable( child[i] , { __index = parent[i] } )
			end
		end
	setmetatable( child, { __index = parent } )          -- set for remaining fields
	end
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Small Useful Functions

Post by ivan »

Here are a couple of nifty table.util functions.

Reverse (will only work with 'lists')

Code: Select all

--- Reverses the elements in a list
-- @param t Table
-- @param r Destination table (optional)
-- @return Reversed table
function table.reverse(t, r)
  r = r or t
  local n = #t
  if t == r then
    -- reverse in place
    for i = 1, floor(n/2) do
      local i2 = n - i + 1
      r[i], r[i2] = r[i2], r[i]
    end
  else
    -- reverse copy
    for i = 1, n do
      r[n - i + 1] = t[i]
    end
  end
  return r
end
Check if the table is empty (should work on any table - doesn't have to be a list)

Code: Select all

--- Checks if a table is empty
-- @param t Table
-- @return True if empty or false otherwise
function table.empty(t)
  return next(t) == nil
end
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: Small Useful Functions

Post by Robin »

Ref wrote:Ran into a hard to find problem related to the use of the above function.
Turns out that if the parent table has a table-field, it is passed to the child by reference.
If that's what you want, I would just forgo inheritance all-together, and just deep-copy the prototype:

Code: Select all

function deepcopy(t, cache)
    if type(t) ~= 'table' then
        return t
    end

    cache = cache or {}
    if cache[t] then
        return cache[t]
    end

    local new = {}

    cache[t] = new

    for key, value in pairs(t) do
        new[deepcopy(key, cache)] = deepcopy(value, cache)
    end

    return new
end
Help us help you: attach a .love.
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Small Useful Functions

Post by s-ol »

Floor all arguments:

Code: Select all

local __floor = math.floor
function math.floor(...)
  tbl = {...}
  for i,v in ipairs(tbl) do
    tbl[i] = __floor(v)
  end
  return unpack(tbl)
end
Perfect for use with love.transform:

Code: Select all

love.transform(math.floor(x, y))
This way it also still works for single arguments, so we can safely overwite math.floor itself.

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
User avatar
Ref
Party member
Posts: 702
Joined: Wed May 02, 2012 11:05 pm

Re: Small Useful Functions

Post by Ref »

Robin wrote:
Ref wrote:Ran into a hard to find problem related to the use of the above function.
Turns out that if the parent table has a table-field, it is passed to the child by reference.
If that's what you want, I would just forgo inheritance all-together, and just deep-copy the prototype:

Code: Select all

function deepcopy(t, cache)
    if type(t) ~= 'table' then
        return t
    end

    cache = cache or {}
    if cache[t] then
        return cache[t]
    end

    local new = {}

    cache[t] = new

    for key, value in pairs(t) do
        new[deepcopy(key, cache)] = deepcopy(value, cache)
    end

    return new
end
Yes and No.
Would like deep copy while retaining all fields in the child.
Any common fields between parent and child should default to child's.
Parent should be unaffected by process.
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Small Useful Functions

Post by ivan »

S0lll0s wrote:Floor all arguments:

Code: Select all

local __floor = math.floor
function math.floor(...)
  tbl = {...}
  for i,v in ipairs(tbl) do
    tbl[i] = __floor(v)
  end
  return unpack(tbl)
end
Here are two alternative implementations that don't create the intermediate table "tbl":

Code: Select all

local _unpack = table.unpack
local _floor = math.floor
function floor2(...)
  local tbl = {...}
  for i = 1, #tbl do
    tbl[i] = _floor(tbl[i])
  end
  return _unpack(tbl)
end

local _select = select
local _cache = {}
function floor3(...)
  local n = _select('#', ...)
  for i = 1, n do
    _cache[i] = _floor(_select(i, ...))
  end
  for i = n + 1, #_cache do
    _cache[i] = nil
  end
  return _unpack(_cache)
end

local _pack2
_pack2 = function(i, a1, a2, ...)
  _cache[i] = _floor(a1)
  if a2 then
    _pack2(i + 1, a2, ...)
  end
end
function floor4(...)
  local n = _select('#', ...)
  _pack2(1, ...)
  for i = n + 1, #_cache do
    _cache[i] = nil
  end
  return _unpack(_cache)
end

function floor5(a1, a2, ...)
  if a2 then return _floor(a1), floor5(a2, ...)
  else return _floor(a1) end
end

local tests = 100000
local input = {}
for i = 1, tests do
  input[i] = {}
  for j = 1, math.random(1, 10) do
    input[i][j] = math.random()*100
  end
end
local t1, m1 = os.clock(), collectgarbage("count")
for i = 1, tests do
  floor2(_unpack(input[i]))
end
local t2, m2 = os.clock(), collectgarbage("count")
for i = 1, tests do
  floor3(_unpack(input[i]))
end
local t3, m3 = os.clock(), collectgarbage("count")
for i = 1, tests do
  floor4(_unpack(input[i]))
end
local t4, m4 = os.clock(), collectgarbage("count")
for i = 1, tests do
  floor5(_unpack(input[i]))
end
local t5, m5 = os.clock(), collectgarbage("count")
print(t2 - t1, m2 - m1)
print(t3 - t2, m3 - m2)
print(t4 - t3, m4 - m3)
print(t5 - t4, m5 - m4)
Running on http://www.lua.org/cgi-bin/demo for 1-10 arguments:

Code: Select all

time:0.08	mem:14063.2890625 -- with the intermediate 'tbl', notice the memory usage
time:0.09	mem:1.453125 -- with select
time:0.08	mem:3.109375 -- with custom pack
time:0.08	mem:0 -- recursive, fastest for up to 10 arguments
and with 100 arguments:

Code: Select all

time:0.08	  mem:16175.0625  -- with the intermediate 'tbl'
time:0.19	mem:2.0  -- with select, notice the slowdown from the many calls to "select"
time:0.13	mem:138.78125  -- with custom pack, probably the most 'balanced' result
time:0.15	mem:0 -- recursive
In conclusion, I would advise against functions with variable number of arguments in critical code,
including "math.max", "math.min" and "unpack"
User avatar
HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Above the Pocket Dimension
Contact:

Re: Small Useful Functions

Post by HugoBDesigner »

A friend of mine asked me if stencils would be translated by "love.graphics.translate". They do.

However, he also stated that, if that's the case, scissors actually DON'T get translated. He asked me to fix that, and so I did:

Code: Select all

_translate = love.graphics.translate
_translateX, _translateY = {0}, {0}
_pop = love.graphics.pop
_origin = love.graphics.origin
_scissor = love.graphics.setScissor

function love.graphics.pop()
	table.remove(_translateX)
	table.remove(_translateY)
	_pop()
end

function love.graphics.translate(x, y)
	table.insert(_translateX, x)
	table.insert(_translateY, y)
	_translate(x, y)
end

function love.graphics.getTranslation()
	return love.graphics.getTranslationX(), love.graphics.getTranslationY()
end

function love.graphics.getTranslationX()
	local x = 0
	for i, v in ipairs(_translateX) do
		x = x + v
	end
	return x
end

function love.graphics.getTranslationY()
	local y = 0
	for i, v in ipairs(_translateY) do
		y = y + v
	end
	return y
end

function love.graphics.origin()
	_translateX = {0}
	_translateY = {0}
	_origin()
end

function love.graphics.setScissor(x, y, w, h)
	if x and type(x) == "number" then
		x = x + love.graphics.getTranslationX()
	end
	if y and type(y) == "number" then
		y = y + love.graphics.getTranslationY()
	end
	_scissor(x, y, w, h)
end
With this code, scissors will also be translated, and I also included some useful functions, like "love.graphics.getTranslation", which returns the result of all translation operations you've done. Really useful if you're using translations a lot and wants to draw something not being translated without doing all the "pops" and "translates" again ;)
@HugoBDesigner - Twitter
HugoBDesigner - Blog
Lapin
Prole
Posts: 19
Joined: Fri Mar 20, 2015 11:53 am

Re: Small Useful Functions

Post by Lapin »

Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot] and 20 guests