[C#]使用 C# 代码实现拓扑排序
0.参考资料
尊重他人的劳动成果,贴上参考的资料地址,本文仅作学习记录之用。
- https://www.codeproject.com/Articles/869059/Topological-sorting-in-Csharp
- https://songlee24.github.io/2015/05/07/topological-sorting/
- https://www.cnblogs.com/skywang12345/p/3711483.html
1.介绍
自己之前并没有接触过拓扑排序,顶多听说过拓扑图。在写前一篇文章的时候,看到 Abp 框架在处理模块依赖项的时候使用了拓扑排序,来确保顶级节点始终是最先进行加载的。第一次看到觉得很神奇,看了一下维基百科头也是略微大,自己的水平也是停留在冒泡排序的层次。ヽ(≧□≦)ノ
看了第二篇参考资料才大致了解,在此记录一下。
2.原理
先来一个基本定义:
在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:
- 每个顶点出现且只出现一次。
- 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。
例如,有一个集合它的依赖关系如下图:
可以看到他有一个依赖关系:
- Module D 依赖于 Module E 与 Module B 。
- Module E 依赖于 Module B 与 Module C 。
- Module B 依赖于 Module A 与 Module C 。
- Module C 依赖于 Module A 。
- Module A 无依赖 。
这个就是一个 DAG 图,我们要得到它的拓扑排序,一个简单的步骤如下:
- 从 DAG 图中选择一个没有前驱的顶点并输出。
- 从 DAG 图中删除该顶点,以及以它为起点的有向边。
- 重复步骤 1、2 直到当前的 DAG 图为空,或者当前图不存在无前驱的顶点为止。
按照以上步骤,我们来进行一个排序试试。
最后的排序结果就是:
Module D -> Module E -> Module B -> Module C -> Module A
emmmm,其实一个有向无环图可以有一个或者多个拓扑序列的,因为有的时候会存在一种情况,即以下这种情况:
这个时候你就可能会有这两种结果
D->E->B->C->F->A
D->E->B->F->C->A
因为 F 与 C 是平级的,他们初始化顺序即便不同也没有什么影响,因为他们的依赖层级是一致的,不过细心的朋友可能会发现这个顺序好像是反的,我们还需要将其再反转一次。
3.实现
上面这种方法仅适用于已知入度的时候,也就是说这些内容本身就是存在于一个有向无环图之中的,如果按照以上方法进行拓扑排序,你需要维护一个入度为 0 的队列,然后每次迭代移除入度为 0 顶点所指向的顶点入度。
例如有以下图:
按照我们之前的算法,
- 首先初始化队列,将 5 与 4 这两个入度为 0 的顶点加入队列当中。
- 执行 While 循环,条件是队列不为空。
- 之后首先拿出 4 。
- 然后针对其指向的顶点 0 与 顶点 1 的入度减去 1。
- 减去指向顶点入度的时候同时判断,被减去入度的顶点其值是否为 0 。
- 这里 1 入度被减去 1 ,为 0 ,添加到队列。
- 0 顶点入度减去 1 ,为 1。
- 队列现在有 5 与 1 这两个顶点,循环判断队列不为空。
- 5 指向的顶点 0 入度 减去 1,顶点 0 入度为 0 ,插入队列。
这样反复循环,最终队列全部清空,退出循环,得到拓扑排序的结果4, 5, 2, 0, 3, 1 。
4.深度优先搜索实现
在参考资料 1 的代码当中使用的是深度优先算法,它适用于有向无环图。
有以下有向环图 G2:
对上图 G2 进行深度优先遍历,首先从入度为 0 的顶点 A 开始遍历:
它的步骤如下:
-
访问 A 。
-
访问 B 。
-
访问 C 。
在访问了 B 后应该是访问 B 的另外一个顶点,这里可以是随机的也可以是有序的,具体取决于你存储的序列顺序,这里先访问 C 。
-
访问 E 。
-
访问 D 。
这里访问 D 是因为 B 已经被访问过了,所以访问顶点 D 。
- 访问 F 。
因为顶点 C 已经被访问过,所以应该回溯访问顶点 B 的另一个有向边指向的顶点 F 。
- 访问 G 。
因此最后的访问顺序就是 A -> B -> C -> E -> D -> F -> G ,注意顺序还是不太对哦。
看起来跟之前的方法差不多,实现当中,其 Sort()
方法内部包含一个 visited 字典,用于标记已经访问过的顶点,sorted 则是已经排序完成的集合列表。
在字典里 Key 是顶点的值,其 value 值用来标识是否已经访问完所有路径,为 true
则表示正在处理该顶点,为 false
则表示已经处理完成。
现在我们来写实现吧:
public static IList Sort(IEnumerable source, Func> getDependencies)
{
var sorted = new List(); var visited = new Dictionarybool>(); foreach (var item in source) { Visit(item, getDependencies, sorted, visited); } return sorted; } public static void Visit(T item, Func> getDependencies, List sorted, Dictionarybool> visited) { bool inProcess; var alreadyVisited = visited.TryGetValue(item, out inProcess); // 如果已经访问该顶点,则直接返回 if (alreadyVisited) { // 如果处理的为当前节点,则说明存在循环引用 if (inProcess) { throw new ArgumentException("Cyclic dependency found."); } } else { // 正在处理当前顶点 visited[item] = true; // 获得所有依赖项 var dependencies = getDependencies(item); // 如果依赖项集合不为空,遍历访问其依赖节点 if (dependencies != null) { foreach (var dependency in dependencies) { // 递归遍历访问 Visit(dependency, getDependencies, sorted, visited); } } // 处理完成置为 false visited[item] = false; sorted.Add(item); } }
顶点定义:
// Item 定义
public class Item { // 条目名称 public string Name { get; private set; } // 依赖项 public Item[] Dependencies { get; set; } public Item(string name, params Item[] dependencies) { Name = name; Dependencies = dependencies; } public override string ToString() { return Name; } }
调用:
static void Main(string[] args) { var moduleA = new Item("Module A"); var moduleC = new Item("Module C", moduleA); var moduleB = new Item("Module B", moduleC); var moduleE = new Item("Module E", moduleB); var moduleD = new Item("Module D", moduleE); var unsorted = new[] { moduleE, moduleA, moduleD, moduleB, moduleC }; var sorted = Sort(unsorted, x => x.Dependencies); foreach (var item in sorted) { Console.WriteLine(item.Name); } Console.ReadLine(); }
结果:
dotNet Core WEB程序使用 Nginx反向代理
之前记录过一篇 使用 jexus 作为dotNetCore的反向代理,发现jexus的内存占用较大,最终选择使用Nginx的原因就是占用内存较小(https://www.cnblogs.com/jhy55/p/9229933.html)
Jexux与 Nginx性能对比参考(https://www.aliyun.com/jiaocheng/202277.html)
直入正题。。。。。。。。。。
Nginx 安装
安装所需环境
Nginx 是 C语言 开发,建议在 Linux 上运行,当然,也可以安装 Windows 版本,本篇则使用 CentOS 7 作为安装环境。
一. gcc 安装
安装 nginx 需要先将官网下载的源码进行编译,编译依赖 gcc 环境,如果没有 gcc 环境,则需要安装:
yum install gcc-c++
二. PCRE pcre-devel 安装
PCRE(Perl Compatible Regular Expressions) 是一个Perl库,包括 perl 兼容的正则表达式库。nginx 的 http 模块使用 pcre 来解析正则表达式,所以需要在 linux 上安装 pcre 库,pcre-devel 是使用 pcre 开发的一个二次开发库。nginx也需要此库。命令:
yum install -y pcre pcre-devel
三. zlib 安装
zlib 库提供了很多种压缩和解压缩的方式, nginx 使用 zlib 对 http 包的内容进行 gzip ,所以需要在 Centos 上安装 zlib 库。
yum install -y zlib zlib-devel
四. OpenSSL 安装
OpenSSL 是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及 SSL 协议,并提供丰富的应用程序供测试或其它目的使用。
nginx 不仅支持 http 协议,还支持 https(即在ssl协议上传输http),所以需要在 Centos 安装 OpenSSL 库。
yum install -y openssl openssl-devel
官网下载
1.直接下载.tar.gz安装包,地址:https://nginx.org/en/download.html
2.使用wget命令下载(推荐)。
wget -c https://nginx.org/download/nginx-1.14.0.tar.gz
我下载的是1.14.0版本,这个是目前的稳定版。
解压
依然是直接命令:
tar -zxvf nginx-1.14.0.tar.gz cd nginx-1.14.0
其实在 nginx-1.10.1 版本中你就不需要去配置相关东西,默认就可以了。当然,如果你要自己配置目录也是可以的。
1.使用默认配置
./configure
编译安装
make make install
查找安装路径:
whereis nginx
启动、停止nginx
cd /usr/local/nginx/sbin/ ./nginx ./nginx -s stop ./nginx -s quit ./nginx -s reload
./nginx -s quit:此方式停止步骤是待nginx进程处理任务完毕进行停止。
./nginx -s stop:此方式相当于先查出nginx进程id再使用kill命令强制杀掉进程。
查询nginx进程:
ps aux|grep nginx
重启 nginx
1.先停止再启动(推荐):
对 nginx 进行重启相当于先停止再启动,即先执行停止命令再执行启动命令。如下:
./nginx -s quit ./nginx
2.重新加载配置文件:
当 ngin x的配置文件 nginx.conf 修改后,要想让配置生效需要重启 nginx,使用-s reload不用先停止 ngin x再启动 nginx 即可将配置信息在 nginx 中生效,如下:
./nginx -s reload
启动成功后,在浏览器可以看到这样的页面:
添加到系统服务,开机自启动
vim /etc/init.d/nginx
添加内容如下
#!/bin/bash
#chkconfig:- 99 20
#description:Nginx Service Control Script
cmd='/usr/local/nginx/sbin/nginx'
pid='/usr/local/nginx/logs/nginx.pid'
case "$1" in
start)
$cmd
;;
stop)
kill -s QUIT $(cat $pid)
;;
restart)
$0 stop
$0 start
;;
reload)
kill -s HUP $(cat $pid)
;;
*)
echo 'Usage:$0 {start|stop|restart|reload}'
exit 1
esac
exit 0
重启服务
service nginx restart
到这里nginx安装完成了
站点配置
HTTP配置代理
如果在 nginx.conf 文件中添加站点配置,多站点的情况下,不利于管理
多站点配置,需要启用这个配置,然后在conf.d文件夹下,创建多个配置文件即可。比如lotteryAdmin.conf、lotteryAPI.conf
新建文件夹
mkdir /usr/local/nginx/conf.d/
vim /usr/local/nginx/conf/nginx.conf
在尾部 http { }内添加
include /usr/local/nginx/conf.d/*.conf;
然后 :wq!保存
vim /usr/local/nginx/conf.d/lotteryAdmin.conf
添加内容
server {
listen 80;
index /Main/index;
server_name admin1.lottery.com; #域名
location / {
# 传递真实IP到后端
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://localhost:5001;#dotNetCore程序的端口号
}
}
然后 :wq!保存
重启 Nginx 服务
service nginx restart
查看Nginx配置是否正常
/usr/local/nginx/sbin/nginx -t
现在可以访问你的站点了
HTTPS配置代理
申请到证书之后,我这里有 xxx.key和xxx.pem两个文件
server {
listen 443;
server_name api.lottery.com;
ssl on;
index index.html index.htm;
ssl_certificate /dotnet/httpsKey/cretc.pem;
ssl_certificate_key /dotnet/httpsKey/cretc.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass https://localhost:5003;#dotNetCore程序的端口号
}
}
按 :wq!保存
service nginx restart
如果报错
unknown directive "ssl"............
切换到你解压的nginx目录下
####### 下载你当前版本的nginx包,并且解压 进到目录
./configure --with-http_ssl_module
####### 切记千万不要make install 那样就覆盖安装了
make
####### 将原来的nginx备份 备份之前先kill当前正在启动的nginx
cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak
####### make之后会在当前目录生成 objs 目录
cp objs/nginx /usr/local/nginx/sbin/nginx
####### 然后重新启动nginx
/usr/local/nginx/sbin/nginx
在重启nginx服务
service nginx restart
这个时候可以访问你的站点了
C#里面获得应用程序的当前路径
在C#里面获得应用程序的当前路径
Environment.CurrentDirectory
System.IO.Directory.GetCurrentDirectory()
——上面两种获得的是当前路径,这个路径不一定是程序所在的路径。任何会改变当前路径的方法都会改变它,例如:OpenFileDialog每换一次目录就会改变它。因此,用这2个方法获取程序路径并不可靠。
AppDomain.CurrentDomain.BaseDirectory
AppDomain.CurrentDomain.SetupInformation.ApplicationBase
——这两个只能在WindowForm中使用;这两个方法是可靠的获取程序路径的方法。返回的路径最末以"\"结尾。可以方便的在后面加入任何Path。当然就算没有也可以用Path.Combine来合并路径以获得想要的路径。
Application.StartupPath
——这个方法也是可靠的获取程序路径的方法。不过返回的路径最末并没有"\"结尾。另外因为是Forms命名空间的。因此如果使用WPF还是不用的好。
Application.ExecutablePath
——这个方法获取的是执行程序的完整文件名。是最可靠的方式,只需要去掉程序文件名就可以获得路径。
关于Nginx设置端口号,在Asp.net 获取不到的,解决办法
不知道你有没有遇到过这样的问题,网站访客多起来后,心里很是高兴,加上了Nginx反向代理,出问题了
原来是这么写的:
Request.Url.ToString()
输出是这样的:
http://www.zhyj2013.com:6038/Default/Index
平白无故多出个端口号
Nginx是这样配置的
server { listen 80; server_name www.zhyj2013.com; location / { root html; index index.html index.htm; proxy_pass http://localhost:6038; proxy_set_header Host $http_host:$server_port; proxy_set_header X-Real-IP $remote_addr; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
大家都是这样配置的,没有什么问题,网上也找不到Asp.Net解决问题的方法,最后我翻看了System.Web.dll 发现他读取appSetting节点 aspnet:UseHostHeaderForRequestUrl 子节点,于是,在项目的WebConfig加上配置,好了
再刷新
输出是这样的:
http://www.zhyj2013.com/Default/Index
.Net程序员 初学Ubuntu ,配置Nignix
1、安装VM虚拟机
2、升级VI编辑器
3、安装Nginx
4、测试localhost
5、编辑配置文件
仅仅用了记录一个过程,不会太详细
1、安装虚拟机,网上一大片,不是特别难
2、为什么要升级VI编辑器?
vi相当于windows的记事本,但是习惯了windows的我,不会用,上下左右箭头都与平时不一样,查了很久的各种命令,找到一篇博文,地址如下:
解决Ubuntu中vi命令的编辑模式下不能正常使用方向键和退格键的问题
2.1、vi 的使用,在黑窗口里面输入 sudo vi filename ,回车,根据提示输入密码,回车,这样就能看到文件的内容了,也是在当前黑窗看到的,跟windows很不一样
此时,是在命令行模式,不能编辑输入文字,一直回车知道自己想编辑的那一行,把光标移动到想要编辑的字符,
输入 i ,此时,进入插入模式,可以写文字了,不想写了,按下esc键,重回到了命令模式,此时也没有保存文件
如果想保存文件,在命令模式下输入 :w! ,回车,就保存了,输入的时候,看黑窗的最下方。
如果想退出的话,可以输入 :q ,就回到了打开之前的
参考的网址是:
[Linux/Ubuntu] vi/vim 使用方法讲解
Ubuntu vi 常用命令集合
3、安装niginx 是因为用到了一点点
打开黑窗口 输入,
sudo apt-get install nginx
按照提输入密码,最后没有报错就是安装完成了。
参考网站
在虚拟机内打开浏览器,输入localhost应该能看到一个nginx的默认页面
3.1、安装后,我找到了这么几个文件夹
/etc/nginx/ 这个是主文件夹
/etc/nginx/nginx.conf 这是主配置文件,但是内容跟windows的时候,稍微有点不一样,server配置在
/etc/nginx/sites-available/default 这是主要的配置文件,我主要是在这里面修改了反向代理的配置
/var/www/http/index.nginx-debian.html 这是nginx的默认首页
4、测试,在上一步已经测试过了
5、编辑配置文件,这个是核心的,由于之前我在windows上简单的配置过,就使用第一步的vi编辑器做了配置,这里把我的文件内容贴下面,增加以下篇幅
server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; index index.html index.htm index.nginx-debian.html; server_name _; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; } location /tydyxsk/ { proxy_pass http://www.tydyxsk.com/; } location /Site/ { proxy_pass http://www.tydyxsk.com/Site/; } location /attached/ { proxy_pass http://www.tydyxsk.com/attached/; } }
只有后面的三个 location是我配置的,最终实现了如下效果:
输入http://localhost ,显示默认页面
输入http://localhost/tydyxsk/,显示太原德育新时空的主站
截图几张
作为一个老鸟,真的发现不学习就落后
夜深了,写了个JQuery的省市区三级级联效果
刚刚练手的JQuery,希望大神们指正
主要实现以下功能:
1、三级菜单级联加载数据
2、可以在不操作脚本的情况下,给元素加属性实现级联功能
3、自定义动态显示数据
咨询问题:
对于一般比较固定不变的数据,是不是应该生成静态的文件,直接加载整个数据文件,访问文件比较好?还是这样动态的访问比较好?
一般的数据文件,小的会在50k,大的会在3M
这个可以讨论下
1 /** 2 省市区县级联 3 */ 4 (function($){ 5 6 /* 7 * 8 *获取ele的相对元素 9 * 10 **/ 11 function CssFirstElement(ele, css) { 12 ele = $(ele); 13 if (!ele) { 14 var event = event ? arguments[0] : window.event; 15 ele = event.srcElement ? event.srcElement : event.target; 16 ele = $(ele); 17 } 18 var targetInpage; 19 20 if ((typeof css == 'string') && css.constructor == String && css.substr(0, "css:#".length) == "css:#") { 21 //以css:# 开头,是一个ID选择器 22 targetInpage = $( $(css.substring(4))[0] ); 23 } else if ((typeof css == 'string') && css.constructor == String && css.substr(0, "css:".length) == "css:") { 24 //以css: 开头,是一个基于元素的父级元素 25 var css0 = css.substring(4); 26 if (css0.split(' ').length==1) { 27 var parentCss = css0.substring(0, css0.indexOf(' ')); 28 var eleCss = css0.substring(css0.indexOf(' ')).replace(/(^\s*)|(\s*$)/g, ""); 29 targetInpage = ele.parents(eleCss + ":first"); 30 } else { 31 var parentCss = css0.substring(0, css0.indexOf(' ')); 32 var eleCss = css0.substring(css0.indexOf(' ')).replace(/(^\s*)|(\s*$)/g, ""); 33 targetInpage = ele.parents(parentCss+":first").find(eleCss); 34 } 35 36 } else if ((typeof css == 'string') && css.constructor == String) { 37 targetInpage = $(css); 38 } else { 39 //对象直接返回 40 return []; 41 } 42 43 return targetInpage.length > 0 ? $(targetInpage.get(0)) : []; 44 } 45 function SelectData(element) { 46 this.element = element;//元素 47 this.optionUrl = element.attr("data-optionUrl");//加载选项的url 48 this.defaultValue = element.attr("data-defaultValue");//加载选项试的默认值 49 this.display = element.attr("data-display");//返回对象的 option 显示的键 50 this.value = element.attr("data-value");//返回对象的 option 绑定的键 51 this.changeTarget = CssFirstElement(element, element.attr("data-changeTarget"));//元素选择改变后,触发目标元素重新加载选项 52 this.showAll = element.attr("data-showAll");//是否显示全部选项 53 this.immediately = element.attr("data-immediately");//是否立即加载选项 54 this.param = element.attr("data-param");//请求时携带的参数 55 //有需要触发改变选项的目标,就添加change事件,触发目标请求时,携带的参数 56 if (this.changeTarget.length > 0) { 57 this.element.on("change.selectDataFill",$.proxy(this.changeEvent,this)); 58 } 59 this.element.data("SelectDataFillObj",this); 60 } 61 SelectData.prototype = { 62 changeEvent: function () { 63 this.changeTarget.attr("data-param", 64 this.element.get(0).name + "=" + this.element.get(0).value); 65 this.changeTarget.data("SelectDataFillObj").empty(); 66 67 var targetW=this.changeTarget; 68 while (true) { 69 targetW = targetW.data("SelectDataFillObj").changeTarget; 70 if (targetW && targetW.length>0) { 71 targetW.data("SelectDataFillObj").empty(); 72 } else { 73 break; 74 } 75 } 76 this.changeTarget.data("SelectDataFillObj").LoadData(); 77 }, 78 empty: function () { 79 this.element.empty(); 80 if (this.showAll) { 81 $("").appendTo(this.element); 82 } 83 }, 84 FillDataSuccess: function (data) { 85 debugger; 86 var element = this.element; 87 this.empty(); 88 89 for (var i = 0; i < data.list.length; i++) { 90 var item = data.list[i]; 91 $("").appendTo(element); 94 } 95 if (this.element.val()!='') { 96 this.element.trigger("change.selectDataFill"); 97 } 98 }, 99 LoadData: function () { 100 $.getJSON(this.optionUrl,this.element.attr("data-param")).success($.proxy(this.FillDataSuccess, this)); 101 } 102 }; 103 $.fn.SelectDataFill = function () { 104 105 this.each(function (index, element) { 106 107 var element = $(element); 108 var obj = new SelectData(element); 109 if (obj.immediately) { 110 obj.LoadData(); 111 } 112 113 }); 114 115 } 116 117 118 })(jQuery);
应用:
12 12 21 30 38
以上代码中的请求路径,如 /BasicPlugin/Area/StreetJson 返回的json格式为:
1 { 2 list:[ 3 {ID:1,Name:"名称",Code:"001"}, 4 {ID:1,Name:"名称",Code:"001"} 5 ] 6 }
效果如下:
一级选择改变,相关联的均重新加载数据,如果加载项中有默认值,则会选中默认值
暂时公开访问地址可以看到效果:
http://uiprogrammer.sxxxt.cn/Home/Index