Я люблю Lua. I love Lua.

Заполнение таблиц данными

Posted in Uncategorized by ilovelua on Январь 20, 2016

Нередко встает задача заполнить таблицу данными.
Допустим, нам нужно создать список параметров.
Каждый параметр — это таблица вида:

{
name = 'Name',
value = '',
type = 'string',
}

Соответственно, список параметров будет выглядеть как-то так:

{
	{
		name = 'Name',
		value = '',
		type = 'string',
	},
	{
		name = 'Order',
		value = 1,
		type = 'number',
	},
	{
		name = 'File',
		value = 'default.lua',
		type = 'string',
	},
}

Если параметров много, то для удобства заполнения я создаю функцию:

local function createParameter(name, value, type)
	return {
		name	= name,
		value	= value,
		type	= type,
	}
end

Список параметров начинает выглядеть так:

{
    createParameter('Name',     '',             'string'),
    createParameter('Order',    1,              'number'),
    createParameter('File',     'default.lua',  'string'),
}

Однако, если параметров в функцию передается много, то при добавлении новой записи часто трудно понять, какой параметр за какое поле отвечает. Чтобы облегчить себе задачу, я в таблицу данных добавляю функции заполнения параметров:

local function Property(name)
    return {
        name    = name,
        
        Value = function(self, value)
            self.value = value
            
            return self
        end,
        
        Type = function(self, type)
            self.type = type
            
            return self
        end,
    }
end

Теперь заполнение таблицы параметров выглядит так:

{
    Property('Name')    :Value('')              :Type('string'),
    Property('Order')   :Value(1)               :Type('number'),
    Property('File')    :Value('default.lua')   :Type('string'),
}

Теперь каждый параметр подписан, а если есть опциональные поля, то их можно пропустить, а не писать такое:

createParameter('Name', '', nil, nil, nil, 'string'),

 

PS
Не помню, сам я это придумал, или где-то украл 🙂

Tagged with: , ,

Избавление от циклических зависимостей модулей

Posted in Uncategorized by ilovelua on Март 14, 2014

Итак, у вас есть два модуля, каждый из которых использует другой.

ModuleA.lua:

local ModuleB	= require('ModuleB')

return {
	fun1 = function() print('~~~ModuleA fun1') end,
	fun2 = function() print('~~~ModuleA calls ModuleB.fun1()', ModuleB().fun1()) end,
}

ModuleB.lua:

local ModuleA	= require('ModuleA')

return {
	fun1 = function() print('~~~ModuleB fun1') end,
	fun2 = function() print('~~~ModuleB calls ModuleA.fun1()', ModuleA().fun1()) end,
}

В главной программе вы наивно пытаетесь их включить:

local ModuleA = require('ModuleA')
local ModuleB = require('ModuleB')

и получаете ошибку:

can't load module_test.lua: [string "ModuleB.lua"]:1: loop or previous error loading module 'ModuleA'

Как быть? Шаблон Медиатор приходит на помощь!

ModuleMediator.lua:

return {
	getModuleA = function() return require('ModuleA') end,
	getModuleB = function() return require('ModuleB') end,
}

Исправляем файлы модулей.
ModuleA.lua:

local ModuleMediator	= require('ModuleMediator')

return {
	fun1 = function() print('~~~ModuleA fun1') end,
	fun2 = function() print('~~~ModuleA calls ModuleB.fun1()', ModuleMediator.getModuleB().fun1()) end,
}

ModuleB.lua:

local ModuleMediator	= require('ModuleMediator')

return {
	fun1 = function() print('~~~ModuleB fun1') end,
	fun2 = function() print('~~~ModuleB calls ModuleA.fun1()', ModuleMediator.getModuleA().fun1()) end,
}

В главной программе все осталось по старому:

local ModuleA = require('ModuleA')
local ModuleB = require('ModuleB')

ModuleA.fun1()
ModuleA.fun2()

ModuleB.fun1()
ModuleB.fun2()

Запускаем:

~~~ModuleA fun1
~~~ModuleB fun1
~~~ModuleA calls ModuleB.fun1()
~~~ModuleB fun1
~~~ModuleA fun1
~~~ModuleB calls ModuleA.fun1()

Работает!
Я там маленько ошибся, когда вызывал функцию другого модуля, которая ничего не возвращает, внутри функции print(), но переписывать не хочется, извините.

Перехватчик для события удаления стейта.

Posted in Uncategorized by ilovelua on Февраль 11, 2014

Если Lua-библиотека, написанная на С, взаимодействует с несколькими Lua стейтами, то может потребоваться узнать о том, что какой-то из Lua стейтов удаляется. Сделать это можно так: при регистрации библиотеки в Lua стейте создаем безымянную userdata переменную, и у нее устанавливаем метатаблицу с методом __gc. Поскольку при удалении стейта у всех объектов вызываются финализаторы, то будет вызвана и эта функция безымянного объекта.

int onDestroyLuaState(lua_State* L)
{
	// делаем что-то полезное
	return 0;
}

const luaL_Reg libname[] = {
	// common functions
	{"Func1", func1},
	{"Func2", func2},
        ...
	{0, 0},
};

extern "C" __declspec(dllexport) int luaopen_libname(lua_State* L)
{
	lua_newuserdata(L, 0); // dummy for GC
	lua_createtable(L, 0, 1); // metatable
	lua_pushcfunction(L, onDestroyLuaState);
	lua_setfield(L, -2, "__gc");
	lua_setmetatable(L, -2);

	luaL_register(L, luaModuleName, libname);

	return 1;
}
Tagged with: ,

Новые и старые модули

Posted in Uncategorized by ilovelua on Январь 23, 2014

Это перевод вот этой статьи.

Создание и использование модулей

Есть два способа создания модулей — старый(и не рекомендуемый) для 5.0 и ранних версий 5.1, и новый для 5.1 и 5.2. Кроме нового способа мы также рассмотрим старый, поскольку вам могут встретиться программы, в которых он используется.

Создадим файл примера с следующим содержанием:

local mymodule = {}

function mymodule.foo()
    print("Hello World!")
end

return mymodule

Теперь, чтобы использовать этот модуль в интерактивном интерпретаторе, напишите:

> mymodule = require "mymodule"
> mymodule.foo()
Hello World!

В обычном файле скрипта рекомендуется сделать переменную mymodule локальной:

local mymodule = require "mymodule"
mymodule.foo()

Если мы сделаем переменную mymodule локальной в интерактивном интерпретаторе, то на следующей строке она выйдет из области видимости и мы не сможем ей воспользоваться. Поэтому мы должны сделать переменную mymodule глобальной.

Поскольку вы можете подключать один и тот же модуль в различных файлах, Lua кеширует модули в таблице package.loaded. Чтобы показать, как работает кеширование, изменим, например, функцию foo в mymodule.lua чтобы она печатала "Hello Module!". Если мы продолжим сессию в интерпретаторе, то произойдет следующее:

> mymodule = require "mymodule"
> mymodule.foo()
Hello World!

Чтобы действительно перезагрузить модуль, нужно сначала удалить его из кеша:

> package.loaded.mymodule = nil
> mymodule = require "mymodule"
> mymodule.foo()
Hello Module!

Еще одна приятная вещь — это возможность именовать модули произвольным образом, поскольку модули это обычные таблицы, сохраненные в переменной. Если название mymodule.lua покажется нам слишком длинным, мы можем его сократить:

> m = require "mymodule"
> m.foo()
Hello Module!

Другие способы создания модулей

Существуют различные способы создания модулей, и вы можете выбрать нужный вам в зависимости от задачи и ваших предпочтений.

Создать таблицу в начале скрипта и добавлять функции в нее:

local mymodule = {}

local function private()
    print("in private function")
end

function mymodule.foo()
    print("Hello World!")
end

function mymodule.bar()
    private()
    mymodule.foo() -- need to prefix function call with module
end

return mymodule

Сделать все функции локальными и добавить их в таблицу в конце скрипта:

local function private()
    print("in private function")
end

local function foo()
    print("Hello World!")
end

local function bar()
    private()
    foo() -- do not prefix function call with module
end

return {
  foo = foo,
  bar = bar,
}

Комбинация двух предыдущих способов:

local mymodule = {}

local function private()
    print("in private function")
end

local function foo()
    print("Hello World!")
end
mymodule.foo = foo

local function bar()
    private()
    foo()
end
mymodule.bar = bar

return mymodule

Вы даже можете изменить окружение блока кода(the chunk’s environment), чтобы сохранить в нем только необходимые глобальные переменные:

local print = print -- the new env will prevent you from seeing global variables

local M = {}
if setfenv then
	setfenv(1, M) -- for 5.1
else
	_ENV = M -- for 5.2
end

local function private()
    print("in private function")
end

function foo()
    print("Hello World!")
end

function bar()
    private()
    foo()
end

return M

Или, если вы не хотите сохранять глобальные переменные:

local M = {}
do
	local globaltbl = _G
	local newenv = setmetatable({}, {
		__index = function (t, k)
			local v = t[k]
			if v == nil then return globaltbl[k] end
			return v
		end,
		__newindex = M,
	})
	if setfenv then
		setfenv(1, newenv) -- for 5.1
	else
		_ENV = newenv -- for 5.2
	end
end

local function private()
    print("in private function")
end

function foo()
    print("Hello World!")
end

function bar()
    private()
    foo()
end

return M

Обратите внимание, что в этом случае доступ к глобальным переменным и переменным модуля может стать медленнее, поскольку используется функция __index. Пустая «прокси» таблица используется вместо таблицы _G(глобальное пространство имен) для того, чтобы не было возможности из модуля обращаться к глобальным переменным и не происходило следующее:
> require "mymodule"
> mymodule.foo()
Hello World!
> mymodule.print("example") -- unwanted __index metamethod
example

Старый способ создания модулей

В Lua 5.0 and 5.1 есть функция module, которая используется таким образом:
mymodule.lua:

module("mymodule", package.seeall)

function foo() -- create it as if it's a global function
    print("Hello World!")
end

И модуль может использоваться так:

> require "mymodule"
> mymodule.foo()
Hello World!

Работает это так: создается новая таблица для модуля, сохраняется в глобальной переменной, имя которой задается первым параметром функции module, затем эта таблица устанавливается окружением для кода модуля, так что если вы создаете в модуле глобальную переменную, то она сохраняется в таблице модуля.

Из-за этого вы внутри модуля не видите глобальных переменных(например, print). Одно из решений — сохранить все необходимые глобальные переменные в локальных перед вызовом функции module, но это может быть утомительно, и решением было добавить второй параметр в функцию module, который должен быть функцией, в которую таблица модуля передается как параметр. package.seeall устанавливает для модуля метатаблицу с функцией __index, которая указывает на глобальное пространство имен, и благодаря этому модуль может обращаться к глобальным переменным. Проблема этого решения в том, что пользователь модуля может обращаться к глобальным переменным через модуль:

> require "mymodule"
> mymodule.foo()
Hello World!
> mymodule.print("example")
example

В лучшем случае это странно и неожиданно, и может быть дырой в безопасности(если вы используете модуль в защищенном(sandboxed) скрипте).
Причина, по которой не рекомендуется использовать функцию module, кроме тех, что приведены выше, это то, что пользователь модуля получает глобальную переменную с именем модуля, а так же то, что в Lua 5.2 функция не может изменять окружение своего вызывающего(по крайней мере без помощи отладочной библиотеки), что делает невозможным реализацию функции module.

Таблица package

Как было сказано ранее, Lua использует библиотеку package для управления модулями.

package.path (для модулей написанных на Lua) и package.cpath (для модулей написанных на C) это места, где Lua ищет модули.

> =package.path
./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua
> =package.cpath
./?.so;/usr/local/lib/lua/5.1/?.so;/usr/lib/x86_64-linux-gnu/lua/5.1/?.so;/usr/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so

package.loaded это таблица, где уже загруженные модули хранятся по именам. Как обсуждалось ранее, это работает как кеш, чтобы модули не загружались многократно, функция require сначала ищет модуль в этой таблице, и, если ничего не найдено, пытается его загрузить.

package.preload это таблица функций, связанных с именами модулей. Прежде чем искать в файловой системе, require проверяет, не содержит ли package.preload нужный ключ. Если функция найдена, то результат ее вызова и будет значением, которое возвратит require.

Остальные поля не очень важны для обычного использования модулей, но если вас интересует, как работает система модулей, это детально описано в мануале здесь.

Скорость цикла for

Posted in Uncategorized by ilovelua on Август 8, 2013

В чужом коде увидел такое:

for i = 1, #data.values do
	local value = data.values[i]
	...
end

И тут же осудил внутри себя этого нерадивого разработчика «Экий ты, брат, ленивец! Нет бы написать «правильно»!»:

for i, value in ipairs(data.values) do
	...
end

И тут меня сомнение взяло: второй вариант мне лично кажется красивее и правильнее, но быстр ли он? И быстро был написан тест:

local data = {
	values = {}
}

for i = 1, 10000 do
	table.insert(data.values, i)
end

local function simpleForTest()
	local j
	local t = os.clock()

	for k = 1, 100 do
		for i = 1, #data.values do
			j = data.values[i]
		end
	end

	print('~~~#', os.clock() - t)
end

local function ipairsTest()
	local j
	local t = os.clock()

	for k = 1, 100 do
		for i, val in ipairs(data.values) do
			j = val
		end
	end

	print('~~~ipairs', os.clock() - t)
end

for i = 1, 10 do
	simpleForTest()
	ipairsTest()
end

А результаты он давал такие:
~~~# 0.063
~~~ipairs 0.121
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.062
~~~ipairs 0.121
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.062
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122
~~~# 0.063
~~~ipairs 0.122

То есть «некрасивый» код работает в два раза быстрее «красивого». Вот так.

Upvalues и вопросы производительности

Posted in Uncategorized by ilovelua on Июль 23, 2013

Дано: список событий(массив таблиц).
Задача: с делать фильтр по полям событий.

Решение 1(прямое, как палка):

function getEventVisible(event, filter)
	if filter.initiatorId then
		if filter.initiatorId ~= event.initiatorMissionID then
			return false
		end
	end

	if filter.targetId then
		if filter.targetId ~= event.targetMissionID then
			return false
		end
	end

	if filter.objectId then
		if event.initiatorMissionID ~= filter.objectId and event.targetMissionID ~= filter.objectId then
			return false
		end
	end

	if filter.weaponId then
		if filter.weaponId ~= event.weapon then
			return false
		end
	end

	if filter.eventType then
		if filter.eventType ~= eventEventType then
			return false
		end 
	end

	if filter.initiatorCoalition then
		if filter.initiatorCoalition ~= DebriefingMissionData.getUnitCoalitionName(event.initiatorMissionID) then
			return false
		end
	end

	if filter.targetCoalition then
		if filter.targetCoalition ~= DebriefingMissionData.getUnitCoalitionName(event.targetMissionID) then
			return false
		end
	end

	return true
end

Тут мы видим, что некоторые поля используются несколько раз. Это срочно нужно оптимизировать:

function getEventVisible2(event, filter)
	local initiatorId = filter.initiatorId
	local targetId = filter.targetId
	local objectId = filter.objectId
	local weaponId = filter.weaponId
	local eventType = filter.eventType
	local initiatorCoalition = filter.initiatorCoalition
	local targetCoalition = filter.targetCoalition
	local getUnitCoalitionNameFunc = DebriefingMissionData.getUnitCoalitionName
	local eventInitiatorId = event.initiatorMissionID
	local eventTargetId = event.targetMissionID
	local eventWeaponId = event.weapon
	local eventEventType = event.type
	local eventInitiatorCoalition = getUnitCoalitionNameFunc(eventInitiatorId)
	local eventTargetCoalition = getUnitCoalitionNameFunc(eventTargetId)

	if initiatorId then
		if initiatorId ~= eventInitiatorId then
			return false
		end
	end

	if targetId then
		if targetId ~= eventTargetId then
			return false
		end
	end

	if objectId then
		if eventInitiatorId ~= objectId and eventTargetId ~= objectId then
			return false
		end
	end

	if weaponId then
		if weaponId ~= eventWeaponId then
			return false
		end
	end

	if eventType then
		if eventType ~= eventEventType then
			return false
		end 
	end

	if initiatorCoalition then
		if initiatorCoalition ~= eventInitiatorCoalition then
			return false
		end
	end

	if targetCoalition then
		if targetCoalition ~= eventTargetCoalition then
			return false
		end
	end

	return true
end

Тут можно было бы откинуться на кресле и сделать глоток крепкого виски… Но чу! Это что же такое получается? Для фильтра, который один для всех событий, значения полей каждый раз извлекаются из таблицы filter. Непорядок. А ну-ка, где тут у нас upvalues?

function getFilterFunc(filter)
	local initiatorId = filter.initiatorId
	local targetId = filter.targetId
	local objectId = filter.objectId
	local weaponId = filter.weaponId
	local eventType = filter.eventType
	local initiatorCoalition = filter.initiatorCoalition
	local targetCoalition = filter.targetCoalition
	local getUnitCoalitionNameFunc = DebriefingMissionData.getUnitCoalitionName

	return function(event)
		local eventInitiatorId = event.initiatorMissionID
		local eventTargetId = event.targetMissionID
		local eventWeaponId = event.weapon
		local eventEventType = event.type
		local eventInitiatorCoalition = getUnitCoalitionNameFunc(eventInitiatorId)
		local eventTargetCoalition = getUnitCoalitionNameFunc(eventTargetId)

		if initiatorId then
			if initiatorId ~= eventInitiatorId then
				return false
			end
		end

		if targetId then
			if targetId ~= eventTargetId then
				return false
			end
		end

		if objectId then
			if eventInitiatorId ~= objectId and eventTargetId ~= objectId then
				return false
			end
		end

		if weaponId then
			if weaponId ~= eventWeaponId then
				return false
			end
		end

		if eventType then
			if eventType ~= eventEventType then
				return false
			end 
		end

		if initiatorCoalition then
			if initiatorCoalition ~= eventInitiatorCoalition then
				return false
			end
		end

		if targetCoalition then
			if targetCoalition ~= eventTargetCoalition then
				return false
			end
		end

		return true
	end
end

Ну а теперь…(ТЕСТЫ!!! ТЕСТЫ!!!)
Я не вижу ваши руки!

local events = DebriefingEventsData.getEvents()
local filterFunc = getFilterFunc(filter_)

print('~~~event count:', #events)

local t = os.clock()

for i = 1, 1000 do
	local result = {}
	for i, event in ipairs(events) do	
		if getEventVisible(event, filter_) then
			table.insert(result, event)
		end
	end
end

print('~~~getEventVisible', os.clock() - t)

t = os.clock()

for i = 1, 1000 do
	local result = {}
	for i, event in ipairs(events) do	
		if getEventVisible2(event, filter_) then
			table.insert(result, event)
		end
	end
end

print('~~~getEventVisible2', os.clock() - t)

t = os.clock()

for i = 1, 1000 do
	local result = {}
	for i, event in ipairs(events) do	
		if filterFunc(event) then
			table.insert(result, event)
		end
	end
end

print('~~~filterFunc', os.clock() - t)

Результаты:
~~~event count: 1467
~~~getEventVisible 1.32
~~~getEventVisible2 2.583
~~~filterFunc 2.286
~~~event count: 1467
~~~getEventVisible 0.698
~~~getEventVisible2 2
~~~filterFunc 1.706
~~~event count: 1467
~~~getEventVisible 1.326
~~~getEventVisible2 2.581
~~~filterFunc 2.284
~~~event count: 1467
~~~getEventVisible 1.642
~~~getEventVisible2 2.203
~~~filterFunc 1.984
~~~event count: 1467
~~~getEventVisible 1.506
~~~getEventVisible2 2.061
~~~filterFunc 1.822
~~~event count: 1467
~~~getEventVisible 1.338
~~~getEventVisible2 2.577
~~~filterFunc 2.275
~~~event count: 1467
~~~getEventVisible 0.75999999999999
~~~getEventVisible2 2.013
~~~filterFunc 1.712
~~~event count: 1467
~~~getEventVisible 1.322
~~~getEventVisible2 2.576
~~~filterFunc 2.281
~~~event count: 1467
~~~getEventVisible 1.323
~~~getEventVisible2 2.574
~~~filterFunc 2.288
~~~event count: 1467
~~~getEventVisible 1.687
~~~getEventVisible2 2.234
~~~filterFunc 1.999
~~~event count: 1467
~~~getEventVisible 1.717
~~~getEventVisible2 2.087
~~~filterFunc 1.808
~~~event count: 1467
~~~getEventVisible 1.743
~~~getEventVisible2 2.109
~~~filterFunc 1.83

Ой, что это? Наш прекрасный оптимизированный код работает хуже неоптимизированного? Похоже, что так. Причем местами «неоптимизированная» версия быстрее «оптимизированной» почти в 2 раза!
Думаю, что в getEventVisible2() создается слишком много локальных переменных, причем некоторые из них могут не понадобиться. А что не так с filterFunc()? Там вроде локальных переменных совсем немного? Похоже, что доступ к «замороженным» переменным не так уж и быстр, либо Lua при вызове этой функции должна откуда-то загрузить все upvalues.
В общем, как обычно с оптимизацией — все нужно проверять.

Поиск позиции для вставки элемента в отсортированный массив

Posted in Uncategorized by ilovelua on Июль 22, 2013

Eng: how to find proper position to insert value into ordered array.
Upd: Вот здесь все есть http://lua-users.org/wiki/OrderedAssociativeTable см функцию table.binsert(). Она, кстати, и быстрее моей.
Тут на работе понадобилось решить такую задачу. Сначала сделал все по простому:

function findPositionInSortedArraySlow(value, array)
	for i, arrayValue in ipairs(array) do
		if value <= arrayValue then
			return i
		end
	end

	return #array + 1
end

Оно, конечно, работает, но неаккуратненько как-то… Сразу в голову полезли мысли о быстрой сортировке вставками. Поскольку массив отсортированный, то позицию можно быстро найти, каждый раз разделяя массив пополам и проверяя нужную половину. В результате получилось это:

function findPositionInSortedArrayFast(value, array)
	local beginIndex = 1
	local endIndex = #array
	local floorFunc = math.floor

	if 0 ~= endIndex then
		local middleIndex

		while true do		
			if value <= array[beginIndex] then
				return beginIndex
			end

			if value >= array[endIndex] then
				return endIndex + 1
			end

			middleIndex = beginIndex + floorFunc((endIndex - beginIndex) / 2)

			if value < array[middleIndex] then
				endIndex = middleIndex
			else
				beginIndex = middleIndex + 1
			end
		end
	end

	return beginIndex
end

Тесты для проверки правильности. Обратите внимание, что в некоторых случаях результаты работы алгоритмов не совпадают. Это происходит в случаях, когда valuе присутствует в массиве array. Быстрый алгоритм выдает позицию за или перед присутствующим элементом в зависимости от того, четное или нечетное количество элементов в массиве. Медленный алгоритм всегда выдает позицию перед присутствующим элементом. В моем случае это абсолютно не критично.

local array = {}
local value = 1
assert(1 == findPositionInSortedArrayFast(value, array))
assert(1 == findPositionInSortedArraySlow(value, array))
value = 100
assert(1 == findPositionInSortedArrayFast(value, array))
assert(1 == findPositionInSortedArraySlow(value, array))

array = {100}
value = 1
assert(1 == findPositionInSortedArrayFast(value, array))
assert(1 == findPositionInSortedArraySlow(value, array))
value = 100
assert(1 == findPositionInSortedArrayFast(value, array))
assert(1 == findPositionInSortedArraySlow(value, array))
value = 101
assert(2 == findPositionInSortedArrayFast(value, array))
assert(2 == findPositionInSortedArraySlow(value, array))

array = {100, 200}
value = 1
assert(1 == findPositionInSortedArrayFast(value, array))
assert(1 == findPositionInSortedArraySlow(value, array))
value = 100
assert(1 == findPositionInSortedArrayFast(value, array))
assert(1 == findPositionInSortedArraySlow(value, array))
value = 101
assert(2 == findPositionInSortedArrayFast(value, array))
assert(2 == findPositionInSortedArraySlow(value, array))
value = 200
-- аглоритмы выдают разные(хотя и правильные!) значения
assert(3 == findPositionInSortedArrayFast(value, array))
assert(2 == findPositionInSortedArraySlow(value, array))
value = 201
assert(3 == findPositionInSortedArrayFast(value, array))
assert(3 == findPositionInSortedArraySlow(value, array))

array = {100, 200, 300}
value = 1
assert(1 == findPositionInSortedArrayFast(value, array))
assert(1 == findPositionInSortedArraySlow(value, array))
value = 100
assert(1 == findPositionInSortedArrayFast(value, array))
assert(1 == findPositionInSortedArraySlow(value, array))
value = 101
assert(2 == findPositionInSortedArrayFast(value, array))
assert(2 == findPositionInSortedArraySlow(value, array))
value = 200
-- аглоритмы выдают разные(хотя и правильные!) значения
assert(3 == findPositionInSortedArrayFast(value, array))
assert(2 == findPositionInSortedArraySlow(value, array))
value = 201
assert(3 == findPositionInSortedArrayFast(value, array))
assert(3 == findPositionInSortedArraySlow(value, array))
value = 300
-- аглоритмы выдают разные(хотя и правильные!) значения
assert(4 == findPositionInSortedArrayFast(value, array))
assert(3 == findPositionInSortedArraySlow(value, array))
value = 301
assert(4 == findPositionInSortedArrayFast(value, array))
assert(4 == findPositionInSortedArraySlow(value, array))

array = {100, 200, 300, 400}
value = 1
assert(1 == findPositionInSortedArrayFast(value, array))
assert(1 == findPositionInSortedArraySlow(value, array))
value = 100
assert(1 == findPositionInSortedArrayFast(value, array))
assert(1 == findPositionInSortedArraySlow(value, array))
value = 101
assert(2 == findPositionInSortedArrayFast(value, array))
assert(2 == findPositionInSortedArraySlow(value, array))
value = 200
-- аглоритмы выдают разные(хотя и правильные!) значения
assert(3 == findPositionInSortedArrayFast(value, array))
assert(2 == findPositionInSortedArraySlow(value, array))
value = 201
assert(3 == findPositionInSortedArrayFast(value, array))
assert(3 == findPositionInSortedArraySlow(value, array))
value = 300
assert(3 == findPositionInSortedArrayFast(value, array))
assert(3 == findPositionInSortedArraySlow(value, array))
value = 301
assert(4 == findPositionInSortedArrayFast(value, array))
assert(4 == findPositionInSortedArraySlow(value, array))
value = 400
-- аглоритмы выдают разные(хотя и правильные!) значения
assert(5 == findPositionInSortedArrayFast(value, array))
assert(4 == findPositionInSortedArraySlow(value, array))
value = 401
assert(5 == findPositionInSortedArrayFast(value, array))
assert(5 == findPositionInSortedArraySlow(value, array))

Проверка скорострельности. На массиве размером 100 элементов разницу в скорости методом os.clock() уловить не удалось 🙂
Попробуем на массиве размером в 1000 элементов. Сначала ищем позицию для элемента где-то в начале массива. В этом случае медленный алгоритм будет почти такой же быстрый, как быстрый 🙂

array = {}

for i = 1, 1000 do
	table.insert(array, i)
end

value = 1

local t = os.clock()

for i = 1, 1000 do
	findPositionInSortedArrayFast(value, array)
end

print('~~~fast', os.clock() - t)

t = os.clock()

for i = 1, 1000 do
	findPositionInSortedArraySlow(value, array)
end

print('~~~slow', os.clock() - t)

Результаты таковы:
~~~fast 0
~~~slow 0.0010000000000003

Теперь поищем позицию для элемента в конце массива:

value = 1001

local t = os.clock()

for i = 1, 1000 do
	findPositionInSortedArrayFast(value, array)
end

print('~~~fast', os.clock() - t)

t = os.clock()

for i = 1, 1000 do
	findPositionInSortedArraySlow(value, array)
end

print('~~~slow', os.clock() - t)

Результат:
~~~fast 0
~~~slow 0.122

Ну вот, намного быстрее 🙂

Скорость загрузки скриптов Lua

Posted in Uncategorized by ilovelua on Апрель 10, 2013

Тут на работе возник вопрос: насколько обильные комментарии замедляют скорость загрузки скриптов? Ответ: весьма незначительно. Ниже тест:

local testFolder = ...

require('lfs')

local folderCommented = testFolder .. 'commented/'

lfs.mkdir(folderCommented)

local folderUncommented = testFolder .. 'uncommented/'

lfs.mkdir(folderUncommented)

local commentedText = [[
-------------------------------------------------------------------------------
-- Удаляет подпись к заданной точке маршрута с карты.
function remove_waypoint_text%d(wpt)
  set_mapObjects(wpt.boss.mapObjects.route.numbers[wpt.index].id, nil)
  return table.remove(wpt.boss.mapObjects.route.numbers, wpt.index)
end

-------------------------------------------------------------------------------
--
function set_mapObjects%d(a_key, a_value)
	mapObjects[a_key] = a_value
end
]]

local fileCount = 100
local textCount = 100

for i = 1, fileCount do
	local filename = string.format('%s%03d.lua', folderCommented, i)
	local file = io.open(filename, 'w')
	for j = 1, textCount do
		-- зададим жару Lua - пусть все функции будут разными
		file:write(string.format(commentedText, j, j))
	end	
	file:close()
end

local uncommentedText = [[
function remove_waypoint_text%d(wpt)
  set_mapObjects(wpt.boss.mapObjects.route.numbers[wpt.index].id, nil)
  return table.remove(wpt.boss.mapObjects.route.numbers, wpt.index)
end

function set_mapObjects%d(a_key, a_value)
	mapObjects[a_key] = a_value
end
]]

for i = 1, fileCount do
	local filename = string.format('%s%03d.lua', folderUncommented, i)
	local file = io.open(filename, 'w')
	for j = 1, textCount do
		-- зададим жару Lua - пусть все функции будут разными
		file:write(string.format(uncommentedText, j, j))
	end	
	file:close()
end

-- загружаем файлы с комментариями
local startTime = os.clock()

for i = 1, fileCount do
	local filename = string.format('%s%03d.lua', folderCommented, i)
	dofile(filename)
end

local commentedTime = os.clock() - startTime
print('commented files loading time:', commentedTime)

-- загружаем файлы без комментариев
local startTime = os.clock()

for i = 1, fileCount do
	local filename = string.format('%s%03d.lua', folderUncommented, i)
	dofile(filename)
end

local uncommentedTime = os.clock() - startTime

print('uncommented files loading time:', uncommentedTime)
print(string.format('loading speed difference is: %0.2f%%', 100 - (uncommentedTime / commentedTime) * 100))

local commentedFilesSize = 4728400

print('total commented files size is %d bytes:', commentedFilesSize)

local uncommentedFilesSize =  2558400

print('total uncommented files size is %d bytes:', uncommentedFilesSize)

print(string.format('files size difference is: %0.2f%%', 100 - (uncommentedFilesSize / commentedFilesSize) * 100))

Запускаем:

commented files loading time: 0.382
uncommented files loading time: 0.363
loading speed difference is: 4.97%
total commented files size is %d bytes: 4728400
total uncommented files size is %d bytes: 2558400
files size difference is: 45.89%

Итого: комментированный фалов почти 4 с половиной мегабайта, некомментированных почти в 2 раза меньше. Разница в скорости загрузки ~5 процентов.

Резюме: о комментариях в коде можно не волноваться.

Сравнение таблиц Lua

Posted in Uncategorized by ilovelua on Апрель 1, 2013

Есть две таблицы вида:

[1] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
[3] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
...

и нужно их мне сравнить. Сравнение я сделал двумя циклами:

function areTablesEqual1(table1, table2)
	for k, v in pairs(table1) do
		if table2[k] ~= v then
			return false
		end
	end

	for k, v in pairs(table2) do
		if table1[k] ~= v then
			return false
		end
	end

	return true
end

Проверим:

local table1 = {	
	[1] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
	[3] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[5] = "{69DC8AE7-8F77-427B-B8AA-B19D3F478B66}",
	[7] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[9] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[11] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[13] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[15] = "{DB434044-F5D0-4F1F-9BA9-B73027E18DD3}",
	[17] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[19] = "{69DC8AE7-8F77-427B-B8AA-B19D3F478B66}",
}

local table2 = {	
	[1] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
	[3] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[5] = "{69DC8AE7-8F77-427B-B8AA-B19D3F478B66}",
	[7] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[9] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[11] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[13] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[15] = "{DB434044-F5D0-4F1F-9BA9-B73027E18DD3}",
	[17] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
	[19] = "{69DC8AE7-8F77-427B-B8AA-B19D3F478B66}",
}
assert(areTablesEqual1(table1, table1))
assert(areTablesEqual1(table1, table2))

И вдруг отчего-то захотелось мне сравнить их внутри одного цикла, а не двух. Для этого на помощь призвал я функцию next():

function areTablesEqual2(table1, table2)
	local result = true

	local key1, value1 = next(table1)
	local key2, value2 = next(table2)

	while key1 and key2 do
		if key1 ~= key2 or value1 ~= value2 then
			result = false
			break
		end

		key1, value1 = next(table1, key1)
		key2, value2 = next(table2, key2)
	end

	if result then
		result = (not key1) and (not key2)
	end

	return result
end

Проверим:

assert(areTablesEqual2(table1, table1))
assert(areTablesEqual2(table1, table2)) -- <-- fail!

Посмотрим, что там происходит внутри:

function areTablesEqual2(table1, table2)
	print('areTablesEqual2 output:')
	local result = true

	local key1, value1 = next(table1)
	local key2, value2 = next(table2)

	print('~~~table1', key1, value1)
	print('~~~table2', key2, value2)

	while key1 and key2 do
		if key1 ~= key2 or value1 ~= value2 then
			result = false
			break
		end

		key1, value1 = next(table1, key1)
		key2, value2 = next(table2, key2)

		print('~~~table1', key1, value1)
		print('~~~table2', key2, value2)
	end

	if result then
		result = (not key1) and (not key2)
	end

	return result
end

Вывод:
areTablesEqual2 output:
~~~table1 13 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table2 13 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table1 7 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table2 7 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table1 1 {6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}
~~~table2 1 {6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}
~~~table1 15 {DB434044-F5D0-4F1F-9BA9-B73027E18DD3}
~~~table2 15 {DB434044-F5D0-4F1F-9BA9-B73027E18DD3}
~~~table1 9 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table2 9 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table1 5 {69DC8AE7-8F77-427B-B8AA-B19D3F478B66}
~~~table2 5 {69DC8AE7-8F77-427B-B8AA-B19D3F478B66}
~~~table1 19 {69DC8AE7-8F77-427B-B8AA-B19D3F478B66}
~~~table2 19 {69DC8AE7-8F77-427B-B8AA-B19D3F478B66}
~~~table1 3 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table2 3 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table1 17 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table2 17 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table1 11 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table2 11 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table1 nil nil
~~~table2 nil nil
areTablesEqual2 output:
~~~table1 13 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table2 13 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table1 7 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table2 7 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table1 1 {6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}
~~~table2 1 {6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}
~~~table1 15 {DB434044-F5D0-4F1F-9BA9-B73027E18DD3}
~~~table2 15 {DB434044-F5D0-4F1F-9BA9-B73027E18DD3}
~~~table1 9 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table2 9 {BCE4E030-38E9-423E-98ED-24BE3DA87C32}
~~~table1 5 {69DC8AE7-8F77-427B-B8AA-B19D3F478B66}
~~~table2 5 {69DC8AE7-8F77-427B-B8AA-B19D3F478B66}
~~~table1 19 {69DC8AE7-8F77-427B-B8AA-B19D3F478B66}
~~~table2 19 {69DC8AE7-8F77-427B-B8AA-B19D3F478B66}
~~~table1 3 {BCE4E030-38E9-423E-98ED-24BE3DA87C32} <
~~~table2 17 {BCE4E030-38E9-423E-98ED-24BE3DA87C32} <

Ну вот, различный порядок обхода таблиц. Жаль, что не получилось. А идея была неплохая, да…

gluas plug-in for GIMP

Posted in Uncategorized by ilovelua on Февраль 14, 2013

А вот, допустим, захотелось вам на Lua обработать фотографию. Ну, там, цвета поменять,  или еще что нибудь гениальное сделать. Не вопрос! Идем на http://pippin.gimp.org/plug-ins/gluas/files/ и качаем бинарники плагина gluas. Затем gluas.exe распаковываем в пользовательскую папку с плагинами GIMP. Ее расположение можно посмотреть запустив GIMP->Edit-Preferences->Folders:

1

Затем открываем картинку в GIMP. Идем в Filters->Generic->gluas…:

2

Появляется окно со скриптом:

3

Нажимаем OK. Магия!

4

Tagged with: , , ,