2019-05-12
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2019-05-12相关的知识,希望对你有一定的参考价值。
参考技术A 关于漫威英雄的社交网络系列已写了好几篇文章,是时候结束它了。作为本系列的最后一篇文章,我们将来研究一下如何在cypher语句映射的虚拟图上进行中心性计算,我们将使用Neo4j和图形算法库来找出漫威英雄网络中最有影响力的英雄或其他重要的英雄。关于漫威社交网络的研究已完成的文章有以下几篇:
在Neo4j中构建漫威社交网络
在Neo4j中对漫威社交网络进行初步分析
Neo4j中基于三角形个数和连通分量的漫威英雄初步社群分析
Neo4j中使用Louvain和标签传播算法对漫威英雄进行客户群分析
正如我在前面文章中说过的,使用Cypher映射虚拟图是真的好用,他可以使我们简单快速的映射一个我们想要的虚拟图,我们简称为“Cypher加载”。为了更好的理解这个神奇的功能,我们需要深入了解它是如何工作。
与直接使用结点标签和关系类型加载子图不同,Cypher加载允许我们定义关系的方向,通常是以下三种值“incoming”,"outgoing"或者"both"(双向/无向),但是Cypher加载不支持单条无向关系。
这看起来可不太友好,但事实上,Cypher加载允许我们使用Cypher查询语句映射各种虚拟图去尝试执行图像算法。我在之前的文章中也使用过,只是没有详细的介绍它而已。
假设我们现在有两个英雄结点以及在他们之间有一个单向的关系。将此图加载为无向图或者有向图的唯一区别就是:在使用Cypher查询语句映射时是否指定关系的方向。当我们在查询时不指定方向,Cypher引擎会为每个关系返回两个方向的结果,这样我们映射图也就是双向的,你也可以称他为无向的。
在图论与网络分析中,中心性是用来表示网络中结点重要性的指标之一。在社交网络中,其用来表明最有影响力的人。在互联网、城市网络甚至传染病学中,用来表明关键结点。中心性概念最初应用在社交网络中,并且它的很多术语都可以反应出其社会学起源,随后中心性被推广到其它类型网络的分析中。[1]
Pagerank是因Google专有的搜索算法。它通过计算链接到页面的链接数量和质量来决定当前页面的重要性。其基本的假设是,重要的结点肯定会有许多的页面链向它。关于Pagerank更多内容可以看此文(https://neo4j.com/docs/graph-algorithms/3.5/algorithms/page-rank/)
我们使用Cypher加载来漫威网络中最大社群的结点,并且设置关系的weight阈值为100.
美国队长是Pagerank分数最高的,他位于网络的中心,有24个关系,并且与其他重要英雄如雷神托尔、蜘蛛侠、钢铁侠都有联系。如果我们仔细看一下与美国队长有联系的英雄,会发现一个有趣现象,他们因为与美国队长有联系,而使得他们的pagerank分也比较高。
接近中心性定义了一个点到所有其他点最短距离的和,换句话说,要计算接近中心性,首先需要计算每对结点之间的最短路径长度。然后再对每个结点计算其到所有其他结点的最短路径和。[2]
接近中心性可以理解为信息流到达网络中任一点的所消耗的时间指标。这个接近中心性分数越高,代表信息流从一个结点到另一个结点所花的时间就越长。因此,我们可以认为接近中心性代表着一个结点到达其他结点的潜在能力。关于更多信息可见文档(https://neo4j.com/docs/graph-algorithms/3.5/algorithms/closeness-centrality/)
我们仍将使用Cypher加载漫威网络中最大社群中的结点并将关系的weight阈值设置为100。对于接近中心性而言,最重要的是需要加载一个独立社群。
不幸的是,当图是非连通图时,接近中心性是无法使用的,因为在非连通图中,两个点可能属于不同的社群,则这两个点是无法连接的,这样他们之间的距离就是无穷大。对于这样的图中的每一个点,都有属于另一个社群的点与之无法连接。因此图中所有顶点的都是没有用的,而接近中心性的计算也被限制在最大的社群中,其他社群中结点的作用是被忽略的。
上图中可以看出,美国队长的位置非常特殊,事实上,在所有类型的中心性上,美国队长都是排第一。我们可以看到,在较紧密的社群里,其结点的接近中心性的值相对较大,而在边缘或较少连接的结点上,其接近中心性的值也较小。另外,我们还注意到图中结点的分布也很重要,一般中间社群结点的接近中心性值要比周边社群的高。例如,钢铁侠和幻视要比蜘蛛侠的接近中心性高。但有意思的是蜘蛛侠的Pagerank值要比较他们大。
从毕达哥拉斯和柏拉图时代起,人们就知道,和谐就是“谐调和优美的比率”的表述,后来,音乐家用来表达规范音阶,建筑学家描述建筑的完美比例。[4]
社交网络分析是一门快速发展的且跨学科领域,它由社会学、物理学、历史学、数学、政治等多种学科共同发展而来。可能是由于缺少综合性研究,造成其中有些方法存在着不足,但已被普遍所接受(Freeman, 1978; Faust & Wasserman, 1992),而接近中心性在非连通网络的不适用性就是其一。和谐中心性也正是用来在非连通网络中代替接近中心性的。实际情况显示,在真实环境下,我们解读的结果发现其与接近中心性指标一致,计算复杂度相同,但最重要的是它可用于非连通网络![3]
因为和谐中心性是为了帮助接近中心性解决其在非连通图上的问题,所以,得到的结果也是相似的。
在图论中,中介中心性是一种基于最短路径的中心性指标。在连通图中,每对点都存在着至少一条最短路径,对于无权重图,最短路径是指路径所包含的关系数最少,对于权重图,最短路径是指路径所含边的权重之和最小。而每个点的中介中心性值就是通过这个点的最短路径的条数。更多描述见(https://neo4j.com/docs/graph-algorithms/current/algorithms/betweenness-centrality/)
我们仍然使用Cypher加载那个最大的社群并设置关系的weight阈值为100
美国队长仍然排在第一位,但这次野兽排到了第二位,这并不奇怪,因为他产中间和右边社群的桥梁。蜘蛛侠和浩克扮演着与野兽相同的角色,但不同的是,他们俩所关联的社群较小,所以,他们的中介中心性值也更低。
[1] https://en.wikipedia.org/wiki/Centrality
[2] http://qualquant.org/wp-content/uploads/networks/2008+1-7-3.pdf
[3] https://infoscience.epfl.ch/record/200525/files/[EN]ASNA09.pdf?
[4] https://arxiv.org/pdf/cond-mat/0008357.pdf
[6] https://en.wikipedia.org/wiki/Betweenness_centrality
模板与泛型编程
16.1.1函数模板
//template parameter list templateint compare(const T1& v1, const T2&v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; }
When we call a function template, the compiler (ordinarily) uses the arguments of the call to deduce the template parameter(s) for us.
The compiler uses the deduced template parameter(s) to instantiatea specific version of the function for us.
模板类型参数
函数模板的参数列表必须明确标明typename或者class,两个是同义的(typename在模板引入C++之后才出现,很多早先的程序使用class)
非类型模板参数
// 整数 templateint compare(const char(&p1)[N], const char(&p2)[M]) { strcpy(p1,p2); } // 指针 template<const char* C> void func1(const char* str) { cout << C << " " << str << endl; } // 引用 template<char(&R)[9]> void func2(const char* str) { cout << R << " " << str << endl; } // 函数指针 template<void(*f)(const char*)> void func3(const char* c) { f(c); } void print(const char* c) { cout << c << endl; } char arr[9] = "template"; // 全局变量,具有静态生存期 int main() { func1 ("pointer"); func2 ("reference"); func3 ("template function pointer"); return 0; }
绑定到非类型整型的实参必须是一个常量表达式,绑定到指针或引用的非类型参数的实参必须有静态的生存期(比如全局变量)
inline和constexpr的函数模板
templateinline int compare(const T1& v1, const T2&v2); template constexpr int compare(const T1& v1, const T2&v2);
对于函数模板,编译器可以通过参数推断,得出模板中的类型信息,因而在使用函数模板时可以省略
16.1.2类模板
对于函数模板,编译器可以通过参数推断,得出模板中的类型信息,因而在使用函数模板时可以省略
- 类模板中,模板参数被被当做stand-ins(替身),使用时被替换
- 类模板中的成员函数只有在使用时才会被实例化,这使的某种类型即使不完全符合模板要求,仍然可以使用该类型的模板。
- 实例化后的类型为ClassName
类型。 - 类包含友元时,模板类包含非模板友元,则友元可以访问模板的所有类型实例;友元是模板,则所有的友元实例都可以访问模板类的所有实例。
- 对于static成员,每一个不同类型实例,都有不同的static成员,相同类型的实例实例化出来的对象共享static成员。
#include#include using namespace std; template class BlobPtr;// forward declarations needed for friend declarations in Blob template class Blob; // needed for parameters in operator== template bool operator==(const Blob &, const Blob &); template class Blob { // each instantiation of Blob grants access to the version of // BlobPtr and the equality operator instantiated with the same type friend class BlobPtr ; friend bool operator== (const Blob &, const Blob &); public: using size_type = vector ::size_type; //construct function Blob():data(make_shared
16.1.3模板参数
模板参数的作用域在声明之后定义结束之前。
模板参数可以覆盖外部作用域的类型参数。
模板参数可以有默认值
使用类的类型成员时需要使用typename关键字,如typename T::value_type()
//默认模板参数 templateint> //使用参数类型的类型成员作为返回值的类型 typename vector ::size_type differ(vector & lh, vector & rh) { return lh.size() - rh.size(); }
16.1.4类的成员也可以是模板
普通类中的成员可以为模板
模板类中的成员可以为模板
//为了在unique_ptr析构对象的时候打印出来信息 class DebugDelete { public: DebugDelete(std::ostream&s = std::cerr) :os(s) {} templatevoid operator()(T*p)const { os << "deleting unique_ptr" << std::endl; delete p; } private: std::ostream &os; }; int main() { //会生成一个int版本的DebugDelete实例 unique_ptr<int, DebugDelete> p(new int, DebugDelete()); return 0; }
16.1.5控制实例化
模板被使用的时候才会实例化,其实例可能出现在多个对象文件中,当独立编译的原文件使用相同的模板并提供了相同的模板参数,每个文件都会有一个实例。为了解决这个开销,可以使用显式实例化。
extern template declaration; //实例化的声明
template declaration;//实例化的定义
编译器遇到external声明,将会在其他位置寻找定义,但在其他位置必须有一个定义。
实例化定义会实例化所有成员,所以类型必须能够引用所有成员函数
16.2模板实参推断
- 在调用模板的时候,一般不会发生参数类型的转换。会发生的转换有两种,非const转换成const,数组或函数名转换成指针。
- 如果函数参数不是模板参数类型,这个参数是可以进行类型转换的
- 使用相同模板参数类型的函数形参类型必须相同,不会进行参数转换
- 当模板实参类型无法推断,例如返回值类型时,必须显式指定。(通过fun
- 显式指定了模板实参类型的,也可以进行类型转换(fun
(15),15转换为double)。
16.2.3尾置返回类型与类型转换
有时候返回值的类型与参数的类型有关,这时候可以用尾置返回类型
templateauto fcn(It beg, It end) -> decltype(*beg) { return *beg; }
由于解引用运算返回的是引用类型,所以返回值是引用类型。
如果要返回的是值,则使用remove_reference(是一个转换模板,定义在type_traits头文件中)。
template//第二个typename表示type成员为类型 auto fcn(It beg, It end) -> typename remove_reference
其他的类型转换模板请参见这里。
16.2.6理解std::move
Std::move将一个对象转变为右值引用对象。
template<class _Ty> inline constexpr typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable return (static_cast::type&&>(_Arg)); }
在调用时
std::string s1("hi!"), s2; s2 = std::move(std::string("bye!")); s2 = std::move(s1);
使用调用
s2 = std::move(std::string("bye!"));
- 推断出模板类型_Ty为string
- remove_reference<_Ty>::type为string
- move返回类型为string&&
使用调用
s2 = std::move(s1);
- 推断出模板类型_Ty为string&
- remove_reference<_Ty>::type为string
- move返回类型为string& &&,折叠后为string&
- move函数参数t实例化为string& &&,会折叠为string&
16.2.7转发
某些函数需要将其一个或多个实参和类型不变地转发给其他函数,包括是否是const以及左值还是右值。
这个模板将一个函数的参数转发到里边的函数。
templatevoid flip1(F f, T1 t1, T2, t2) { f(t2, t1); }
这个模板在值传递的实例中是正确的,但是在如下调用:
function<void(int, int&)> fun = [](int a, int& b) {cout << a << ++b << endl; }; int a = 1, b = 2; flip1(fun, a, b); cout << a << b << endl;
你会发现,函数并没有改变第二个参数的值,没有将引用这个参数类型传入。
修改模板为
templatevoid flip1(F f, T1&& t1, T2&& t2) { f(t1, t2); }
这也会存在一些情况下的错误
function<void(int&&, int&)> fun = [](int &&a, int& b) {cout << a << ++b << endl; }; int a = 1; flip1(fun, 1, a);
1为右值传递到模板flip1中后,作为一个变量的形参,会成为一个左值(变量是左值),左值绑定到fun的第一个右值参数时,会出现错误。
再次修改为
templatevoid flip1(F f, T1&& t1, T2&& t2) { f(std::forward (t1), std::forward (t2)); }
使用forward,将返回类型为T&&的结果,如果出入为值,则返回为右值,传入为左值引用,则引用折叠后返回左值引用。
16.3重载与模板
如下两个模板
templatestd::string debug_rep(const T& t) { std::ostringstream ret; ret << t; return ret.str(); } template std::string debug_rep(T* p) { std::ostringstream ret; ret << "pointer: " << p << " " << (p ? debug_rep(*p) : "null pointer"); return ret.str(); }
如果有调用
int a = 1; std::cout << debug_rep(a) << std::endl;
只有第一个模板时可行的,则选择第一个实例化
如果有调用
int a = 1; std::cout << debug_rep(&a) << std::endl;
则有
debug_rep(const int*&) debug_rep(int*)
这两个版本的实例化,由于第一个需要有非const转换到const,所以选择了第二个版本。
如果有调用
const int* a = &b; std::cout << debug_rep(a) << std::endl;
则上述两个版本都是精确的,然而debug_rep(T* p)对于这个调用来讲,是更特例的版本,只针对于指针,所以选择最特例话的版本。(从另一个角度讲,(const T& t)适用任何类型,这样debug_rep(T* p)永远不会被使用,这条规则才能使其被使用)
另外,非模板函数会优先匹配
16.4可变参数模板
一个可变参数模板(variadic template)中可变数目的参数被称为参数包(parameter packet),分为两种参数包:模板参数包(template parameter packet)、函数参数包(function parameter)。
// Args is a template parameter pack; rest is a function parameter pack // Args represents zero or more template type parameters // rest represents zero or more function parameters templatevoid foo(const T &t, const Args& ... rest) { cout << sizeof...(Args) << endl; // number of type parameters cout << sizeof...(args) << endl; // number of function parameters }
用typename…表示零或多个类型的列表,一个类型名后边跟省略号,表示非类型参数的列表。如果一个函数参数的类型是模板参数包,则此参数是一个函数参数包,可以用sizeof…求出参数的个数。
16.4.1例子,打印所有参数
第一个模板在第二个模板递归调用的最后一次中,时最为匹配的,调用时终止了第二个模板的递归。
// function to end the recursion and print the last element // this function must be declared before the variadic version of print is defined templateostream &print(ostream &os, const T &t) { return os << t; // no separator after the last element in the pack } // this version of print will be called for all but the last element in the pack template ostream &print(ostream &os, const T &t, const Args&... rest) { os << t << ", "; // print the first argument return print(os, rest...); // recursive call; print the other arguments }
16.4.2包扩展
templateostream &print(ostream &os, const T &t, const Args&... rest) //扩展Args,生成参数列表 { os << t << ", "; return print(os, rest...); //扩展实参,形成实参列表 }
在扩展实参的时候,还可以这样用
print(os, debug_rep(rest)...);
可以将扩展出来的每一个参数,执行函数调用
return print(os, debug_rep(p1), debug_rep(p2), debug_rep(p3));
16.4.3转发参数包
// fun haszero or more parameters each of which is // an rvalue reference to a template parameter type template
16.5模板特例化
// firstversion; can compare any two types templateint compare(const T&, const T&); // second version to handle string literals template int compare(const char(&)[N], const char(&)[M]);
定义的这两个模板,可以比较任意对象类型。但是,如果传入的是字符串类型的指针(char*)时,回到第一个模板中调用,然而比较了参数指针的大小。
为了能够处理字符指针的类型,可以定义一个特列
// specialversion of compare to handle pointers to character arrays template <> int compare(const char* const &p1, const char* const &p2) { return strcmp(p1, p2); }
当提供一个特例化,其类型必须与之前的模板参数中对应的类型匹配。如上是为了特例化第一个模板template
需要注意的是,特例化本质是实例化一个模板,而非重载。因此特例化不影响函数匹配。这与将其定义为非模板函数是有区别的。
加入定义了非模板函数,这样在传入字符常量的时候,例如
compare("hi", "mom");
会匹配到我们定义的非模板函数,而这个实际上我们希望匹配到int compare(const char(&)[N], const char(&)[M]);
注意,特例化必须声明在作用域中,故声明在一个头文件中。
类模板的特例化
如果自定义类需要使用无序容器,必须定义自己的hasher版本,或者特例化hash
- 一个重载调用运算符,接收一个容器关键字类型的对象,返回一个size_t
- 两个类型成员,result_type和argument_type,分别为调用运算符的返回类型和参数类型
- 默认构造函数和拷贝赋值运算符(可以隐式定义)。
注意,特例化需要与被特例化的模板在同一个作用域,所以使用namespace参数。
示例:对Sales_data
// openthe std namespace so we can specialize std::hash namespace std { template <> // we‘re defining a specialization with struct hash// the template parameter of Sales_data { // the type used to hash an unordered container must define these types typedef size_t result_type; typedef Sales_data argument_type; // by default, this type needs == size_t operator()(const Sales_data& s) const; // our class uses synthesized copy control and default constructor }; size_t hash ::operator()(const Sales_data& s) const { //下边是hash函数的算法,使用了string、uint、double的hash算法 return hash<string>()(s.bookNo) ^ hash ()(s.units_sold) ^ hash<double>()(s.revenue); } }
注意,有时候会使用私有成员进行hash,所以声明为友元
// needed for the friend declaration templateclass std::hash; class Sales_data { friend class std::hash ; // other members as before };
类模板的部分特例化
我们可以部分特例化类模板,不能部分特例化函数模板
template <class T> struct remove_reference { typedef T type; }; // partialspecializations that will be used for lvalue and rvalue references template <class T> struct remove_reference
特例化模板的成员
templatestruct Foo { Foo(constT &t = T()) : mem(t) { } void Bar() { } T mem; // other members of Foo }; template<> // we‘re specializing a template void Foo<int>::Bar() // we‘re specializing the Bar member of Foo { // do whatever specialized processing that applies to ints }
当使用int类型的模板时,会使用这个特例化的Bar()函数成员。
以上是关于2019-05-12的主要内容,如果未能解决你的问题,请参考以下文章
2019以终为始,不迷茫的2020 | 前端小菜鸡的年度总结~
测试文章-2019以终为始,不迷茫的2020 | 前端小菜鸡的年度总结~