-
Notifications
You must be signed in to change notification settings - Fork 2
使用Luajit中的ffi
Smile Yik edited this page Oct 23, 2022
·
2 revisions
最后更新于2022年10月23日 | 历史记录
此页面内容对应于LuaInMinecraftBukkit插件的最新版本(及以上版本)(version: 1.7.1), 历史文档可以插件此页面的历史记录
在这个例子里面将会使用c++编写一个原始版本的apriori算法, 并使用lua脚本插件 让其在Minecraft服务器中运行.
ffi的使用方法可以在官网找到: ext_ffi
在 LuaInMinecraftBukkit 插件目录下的 config.lua
配置文件中, 使用如下配置来启动
luagit2.1.0beta3
setting:native_version("luajit_2_1_0_beta3")
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <stdio.h>
#include <algorithm>
using namespace std;
class itemset {
private:
vector<string> items;
public:
int support = 0;
itemset() {
}
itemset(const vector<string>& tmp) {
for (auto begin = tmp.begin(), end = tmp.end(); begin != end; ++begin) {
items.push_back(*begin);
}
}
itemset(string* strs, int strs_length) {
for (auto begin = strs, end = strs + strs_length; begin != end; ++begin) {
items.push_back(*begin);
}
}
void add_item(string item) {
// add item
for (auto begin = items.begin(), end = items.end(); begin != end; ++begin) {
if (*begin == item) {
return;
}
}
items.push_back(item);
}
void add_items(const itemset& set) {
// add item
for (auto begin = set.items.begin(), end = set.items.end(); begin != end; ++begin) {
add_item(*begin);
}
}
void all_subsets(int& length, vector<itemset>& subsets) {
length = items.size();
int i;
for (i = 0; i < length; ++i) {
string first = *items.begin();
items.erase(items.begin());
subsets.push_back(items);
items.push_back(first);
}
}
string to_string() {
string s = "";
for (auto begin = items.begin(), end = items.end(); begin != end; ++begin) {
s += *begin + ", ";
}
return s + ::to_string(support);
}
void normalize() {
sort(items.begin(), items.end());
}
bool operator==(const itemset& other) {
normalize();
return other.items == items;
}
bool is_in_itemsets(vector<itemset> frequent_itemset, const int& size) {
int i;
for (i = 0; i < size; ++i) {
frequent_itemset.at(i).normalize();
if (*this == frequent_itemset.at(i)) {
return true;
}
}
return false;
}
bool is_subset_of(const itemset& set) {
std::set<string> tmp;
for (auto begin = set.items.begin(), end = set.items.end(); begin != end; ++begin) {
tmp.insert(*begin);
}
for (auto begin = items.begin(), end = items.end(); begin != end; ++begin) {
if (!tmp.count(*begin)) {
return false;
}
}
return true;
}
const size_t size() {
return items.size();
}
const vector<string> get_items() {
return items;
}
};
bool has_infrequent_subset(vector<itemset>& frequent_itemset, const int& fre_itemsets_size, itemset& candidate_itemset) {
int size;
vector<itemset> subsets;
candidate_itemset.all_subsets(size, subsets);
int i;
for (i = 0; i < size; ++i) {
if (!(subsets.at(i)).is_in_itemsets(frequent_itemset, fre_itemsets_size)) {
return true;
}
}
return false;
}
void apriori_gen(vector<itemset> d, const int min_sup, vector<itemset>& frequent_itemset, const int& fre_itemsets_size, vector<itemset>& new_itemsets) {
int i, j;
int size = frequent_itemset.at(0).size();
set<itemset> checked;
for (i = 0; i < fre_itemsets_size; ++i) {
for (j = i + 1; j < fre_itemsets_size; ++j) {
itemset tmp = itemset((frequent_itemset.at(i)).get_items());
tmp.add_items(frequent_itemset.at(j));
if (tmp.size() == size + 1 && !has_infrequent_subset(frequent_itemset, fre_itemsets_size, tmp)) {
for (auto & b : d) {
if (tmp.is_subset_of(b)) {
tmp.support++;
}
}
if (tmp.support >= min_sup) {
tmp.normalize();
bool flag = true;
for (auto & it : new_itemsets) {
if (it == tmp) {
flag = false;
break;
}
}
if (flag) {
new_itemsets.push_back(tmp);
}
}
}
}
}
}
void apriori_gen_f1(vector<itemset>& d, vector<itemset>& f1, const int& min_sup) {
map<string, int> tmp;
auto begin = d.begin();
auto end = d.end();
while (begin != end) {
const vector<string> items = begin->get_items();
for (string s : items) {
if (tmp.count(s)) {
tmp[s]++;
} else {
tmp[s] = 1;
}
}
++begin;
}
for (auto & b : tmp) {
if (min_sup > b.second) {
continue;
}
itemset it;
it.add_item(b.first);
it.support = b.second;
f1.push_back(it);
}
}
void apripri(vector<itemset> d, int min_sup) {
if (min_sup <= 0) {
min_sup = 1;
}
vector<itemset> f1;
apriori_gen_f1(d, f1, min_sup);
while (!f1.empty()) {
cout << "---------------" << endl;
for (auto & begin : f1) {
cout << begin.to_string() << endl;
}
vector<itemset> new_itemsets;
apriori_gen(d, min_sup, f1, f1.size(), new_itemsets);
f1 = new_itemsets;
}
}
/*
* 创建一个vector<itemset>并返回其指针.
*/
uint64_t new_itemsets() {
vector<itemset>* itemsets = new vector<itemset>();
return (uint64_t) itemsets;
}
/**
* 根据vector<itemset>指针地址, 释放此指针
*/
void delete_itemsets(uint64_t itemsetsPtr) {
vector<itemset>* itemsets = (vector<itemset>*) (itemsetsPtr);
delete itemsets;
}
/**
* 在vector<itemset>末尾创建一个itemset, 并返回其索引.
*/
int new_itemset(uint64_t itemsetsPtr) {
vector<itemset>* itemsets = (vector<itemset>*) (itemsetsPtr);
int idx = itemsets->size();
itemsets->push_back(itemset());
return idx;
}
/**
* 根据vector<itemset>指针及itemset索引, 为itemset添加一个字符串.
*/
void add_item_to_set(uint64_t itemsetsPtr, int idx, char* str) {
vector<itemset>* itemsets = (vector<itemset>*)(itemsetsPtr);
(*itemsets)[idx].add_item(string(str));
itemsets->push_back(itemset());
}
/**
* 根据vector<itemset>指针及提供的最小支持度, 进行计算.
*/
void apripri(uint64_t itemsetsPtr, int min_sup) {
vector<itemset>* itemsets = (vector<itemset>*)(itemsetsPtr);
apripri(*itemsets, min_sup);
}
int main(int argc, char **argv)
{
std::cout << "Hello, world!" << std::endl;
string str1[] = {"I1", "I2", "I5"};
string str2[] = {"I2", "I4"};
string str3[] = {"I2", "I3"};
string str4[] = {"I1", "I2", "I4"};
string str5[] = {"I1", "I3"};
string str6[] = {"I2", "I3"};
string str7[] = {"I1", "I3"};
string str8[] = {"I1", "I2", "I3", "I5"};
string str9[] = {"I1", "I2", "I3"};
vector<itemset> set1;
set1.push_back(itemset(str1, 3));
set1.push_back(itemset(str2, 2));
set1.push_back(itemset(str3, 2));
set1.push_back(itemset(str4, 3));
set1.push_back(itemset(str5, 2));
set1.push_back(itemset(str6, 2));
set1.push_back(itemset(str7, 2));
set1.push_back(itemset(str8, 4));
set1.push_back(itemset(str9, 3));
apripri(set1, 1);
return 0;
}
在这个较长的代码里, 需要在意的代码仅仅只有如下这些, 因为我们在意的是如何利用 lua去调用这个算法, 而不是apripri算法的实现.
/*
* 创建一个vector<itemset>并返回其指针.
*/
uint64_t new_itemsets();
/**
* 根据vector<itemset>指针地址, 释放此指针
*/
void delete_itemsets(uint64_t itemsetsPtr);
/**
* 在vector<itemset>末尾创建一个itemset, 并返回其索引.
*/
int new_itemset(uint64_t itemsetsPtr);
/**
* 根据vector<itemset>指针及itemset索引, 为itemset添加一个字符串.
*/
void add_item_to_set(uint64_t itemsetsPtr, int idx, char* str);
/**
* 根据vector<itemset>指针及提供的最小支持度, 进行计算.
*/
void apripri(uint64_t itemsetsPtr, int min_sup);
c++ -shared -fPIC apriori_lib.cpp -o apriori_lib.so
local ffi = require "ffi"
local apriori = ffi.load(self:getRequirePath("c_libraries/apriori_lib.so"))
-- 加载函数, c++中的函数重载后会变函数名.
ffi.cdef[[
uint64_t _Z12new_itemsetsv();
void _Z15delete_itemsetsm(uint64_t itemsetsPtr);
int _Z11new_itemsetm(uint64_t itemsetsPtr);
void _Z15add_item_to_setmiPc(uint64_t itemsetsPtr, int idx, char* str);
void _Z7apriprimi(uint64_t itemsetsPtr, int min_sup);
]]
-- 多个购物篮中的物品名称数据
local strs = {
{"I1", "I2", "I5"},
{"I2", "I4"},
{"I2", "I3"},
{"I1", "I2", "I4"},
{"I1", "I3"},
{"I2", "I3"},
{"I1", "I3"},
{"I1", "I2", "I3", "I5"},
{"I1", "I2", "I3"}
}
-- 当脚本插件被启用时会执行这个方法
function onEnable()
-- 创建一个vector<itemset>指针
local itemsets_ptr = apriori._Z12new_itemsetsv()
for i = 1, #(strs) do
local items = strs[i]
-- 在vector<itemset>中新建一个itemset, 并返回其下标
local idx = apriori._Z11new_itemsetm(itemsets_ptr)
for j = 1, #(items) do
-- 将购物篮里的物品名加入itemset中
apriori._Z15add_item_to_setmiPc(
itemsets_ptr, idx, ffi.cast("char*", items[j])
)
end
end
-- 使用apriori算法分析购物篮
apriori._Z7apriprimi(itemsets_ptr, 2)
-- 删除vector<itemset>指针
apriori._Z15delete_itemsetsm(itemsets_ptr)
print("加载完毕")
end
-- 当脚本插件被结束时会执行这个方法
function onDisable()
print("卸载完毕")
end
[18:31:06] [Server thread/INFO]: [LuaInMinecraftBukkit] Enabling LuaInMinecraftBukkit v1.7.1
[18:31:06] [Server thread/INFO]: [LuaInMinecraftBukkit] 版本已加载: LUAJIT_2_1_0_BETA3
[18:31:06] [Server thread/INFO]: [LuaInMinecraftBukkit] 正在启用混合模式.....
[18:31:06] [Server thread/INFO]: [LuaInMinecraftBukkit] 正在加载插件: apriori_test(apriori_test), 作者: SmileYik, 版本: 1.0
[18:31:06] [Server thread/INFO]: [LuaInMinecraftBukkit] 正在以Native模式启用插件: apriori_test(apriori_test), 作者: SmileYik, 版本: 1.0
>---------------
I1, 6
I2, 7
I3, 6
I4, 2
I5, 2
---------------
I1, I2, 4
I1, I3, 4
I1, I5, 2
I2, I3, 4
I2, I4, 2
I2, I5, 2
---------------
I1, I2, I3, 2
I1, I2, I5, 2
加载完毕[18:31:06] [Server thread/INFO]: [LuaInMinecraftBukkit] called closure [apriori_test, onEnable]: 2ms
[18:31:06] [Server thread/INFO]: Server permissions file permissions.yml is empty, ignoring it
[18:31:06] [Server thread/INFO]: Done (9.138s)! For help, type "help"
- 因为c++支持方法重载, 故在其编译过程中会对函数名按照一定规则进行重命名, 这就是为什么 在ffi.cdef中, 声明的函数名与之前编写的函数名不一样, 要较为方便的获取重命名后的函数名, 在linux系统下可以使用命令去查找
nm -D <动态链接库> | grep <函数名>
举个例子, 就以上写的代码, 我想查找add_item_to_set
函数编译后的函数名, 可以这样:
❯ nm -D apriori_lib.so | grep add_item_to_set
000000000001d15d T _Z15add_item_to_setmiPc
这样查找出来的函数名就是_Z15add_item_to_setmiPc
, 之后在ffi.cdef中这样声明
ffi.cdef[[
void _Z15add_item_to_setmiPc(uint64_t itemsetsPtr, int idx, char* str);
]]
这样就能正常使用了, 要是嫌弃函数名称难读, 可以这样重命名函数名为你想要的函数名
local ffi = require "ffi"
local apriori_lib = ffi.load(self:getRequirePath("c_libraries/apriori_lib.so"))
ffi.cdef[[
uint64_t _Z12new_itemsetsv();
void _Z15delete_itemsetsm(uint64_t itemsetsPtr);
int _Z11new_itemsetm(uint64_t itemsetsPtr);
void _Z15add_item_to_setmiPc(uint64_t itemsetsPtr, int idx, char* str);
void _Z7apriprimi(uint64_t itemsetsPtr, int min_sup);
]]
-- 重新命名函数名
local apriori = {
add_item_to_set = apriori_lib._Z15add_item_to_setmiPc,
new_itemsets = apriori_lib._Z12new_itemsetsv,
delete_itemsets = apriori_lib._Z15delete_itemsetsm,
new_itemset = apriori_lib._Z11new_itemsetm,
apripri = apriori_lib._Z7apriprimi
}
之后使用 apriori
变量去调用对应函数即可