Swift服务器入门+ReactJS代码示范:如何在内存中建立高性能索引
来源:互联网 发布:离线基站数据库 300万 编辑:程序博客网 时间:2024/06/04 22:46
昨晚刚刚在Github上发布了一个代码示范——如何使用Perfect HTTP服务器搭建一个专门用于破解英语字谜游戏(输入打乱顺序的一组字母然后自动找出合理的英语单词)的服务器,并实现了内存查询优化。这个示范程序展示了一些对于iOS程序员在转Swift全栈工程师时可能经常遇到的一些问题:
我的服务器一定要使用数据库或者文件才能完成某种基于数据的查询吗?
如果没有数据库或者文件支持,如何提高查询效率?
ReactJS如何与Swift服务器混搭?
下载、编译和运行
首先请确保您的系统已经安装了Swift 3.1工具链。
请使用下列bash命令下载、编译和运行本项目:
$ git clone https://github.com/PerfectExamples/Perfect-Unscrambler.git$ cd Perfect-Unscrambler$ swift build$ .build/debug/Unscrambler
HTTP服务器的启动会需要一段时间,主要是从网上在线下载一个英文字典并执行内存索引,因此请耐心等待直到您的终端控制台出现下列信息:
[BOOT] 354983 words are loaded into 311906 groups.[INFO] Starting HTTP server localhost on 0.0.0.0:8888
这意味着服务器已经成功启动,现在您可以使用浏览器查看地址http://localhost:8888
,如下图所示:
如果没问题的话,您的任何输入都可以立刻看到将您当前输入作为谜面的所有谜底。
如果需要停止服务器,请使用组合键CTRL-C
。
程序特点
不需要使用数据库: Perfect Unscrambler 采用了 Swift 原生的
Dictionary
字典作为主要数据结构,用于破解字谜而且不依赖任何一种数据库。In-Memory & Indexing: 本案例中,破解字谜的关键是使用了字母频率表作为解密索引。所有操作都是在内存中完成并将服务器性能优化到极限水平。
ReactJS 展示: 本项目同时还展示了如何使用 jQuery / ReactJS 来实现Perfect 类型的服务器查询。
基本思路
解决此类字谜游戏的关键是使用字典+字频表,即每个单词都一个建立一个对应的字母频率表,而字典中所有与之相同的字母频率表对应的单词都是有效的谜底答案,如下图所示:
图中单词“TEAM”(意为团队)可以记录为1个A、1个E、1个M和1个T,因此可以按顺序简记为 A1E1M1T1
。我们称这种统计方法叫做单词的字频签名(signature)。
假设我们手边正好有本英文字典,那么最直接的方法就是逐页逐条将所有字典中的单词均以字频签名的方式表达,那么一旦发现签名与谜面完全一致的单词,则可判定为有效的谜底。图例中,字典中找到了其他四个与“TEAM”具有共同字频特征“A1E1M1T1”的单词,分别是:”、“MEAT”、“META”和“TAME”。
制作索引
当然我们可以在运行服务器的时候,每接到一个谜面都到字典中重复上述过程,但是遍历整本字典是件耗时耗力的工作。为了避免不必要的重复计算,我们在这里介绍给这个字典建立索引的方法——也就是说,在服务器启动时加载字典的同时即刻开始为所有单词建立字频签名并将所有单词按照签名进行分组管理——很显然这是个好主意。
本案例使用一个专门设计好的 WordGroups
类对象用于完成上述工作。其中其核心数据结构是Swift 语言的 Dictionary<key, value>
字典,即<关键词-字符串>,也就是我们通常所说的[String:[String]]
类型变量。其中字频签名被用作了关键词,而字符串当然就是具有相同签名的一组谜底啦。
服务器启动时制作索引的过程如下:
- 下载字典 - 本项目下载了一个具有35万个单词的字典。
- 制作索引 - 本案例中服务器会建立大约30万个字频分组,也就是说,至少有5万个左右的单词在对应同一个谜面的游戏时会有不止一个的谜底答案。
- 启动HTTP服务器。
一旦成功启动,服务器就能够以惊人的速度破解任何字谜——采用相同的方式计算谜面的字频签名,然后根据签名直接从内存的单词分组中提取出来再返回到浏览器。如果无解则返回空数组。
源代码简介
整个案例工程只有三个Swift源程序:
Package.swift
即SPM软件包管理器在编译项目时标准的源程序,核心代码如下:
dependencies: [ .Package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", majorVersion: 2), .Package(url: "https://github.com/PerfectlySoft/Perfect-CURL.git", majorVersion:2) ]
上述依存关系中, Perfect-HTTPServer 包括了启动一个Swift语言在 mac / linux 上开发HTTP服务器的所有必要功能;而 Perfect-CURL 用于Swift语言在服务器上动态访问其他互联网服务器提供的编程接口服务。
WordGroups.Swift
该类对象代码只包括下列三个函数:
func signature()
:字频签名的核心算法,即用一个字频表代表字符串的频率特征。
// 首先准备一个空的字频表var table:[UInt8: Int] = [:]// 将输入的字符串转化为全部小写word.lowercased().utf8// 忽略所有非英文字母的数字或者符号.filter {$0 > 96 && $0 < 123}// 创建字频表.forEach { char in // 获取当前字母出现频率 let f = table[char] ?? 0 // 频率加1 table[char] = f + 1}//next// 必须按顺序排序return table.keys.sorted()// 将字频转化为形如 `a1`的字符串.map { key -> String in let buffer:[UInt8] = [key, 0] return String(cString: buffer) + String(describing: table[key] ?? 0)}// 最后将所有<字母-频率>合并成为一个字符串.joined()
init()
:构造函数,即首先下载字典(每行一个单词)文件然后转化为按字频分组的字典结构。
// 首先下载字典let curl = CURL(url: url)let r = curl.performFullySync()curl.close()// 将字典按行转换为单词列表r.bodyBytes.split(separator: 10).forEach { // 将一行内容转化为一个字符串 var s = Array($0) s.append(0) let word = String(cString: s) // 计算字频签名 let sig = signature(word: word) // 获取所在分组 var list = groups[sig] ?? [] // 将新单词加入分组 list.append(word) groups[sig] = list}//next
func solve()
:解谜——用户可以使用该函数输入任何谜面,该函数会采用同样算法计算输入字符串的字频特征签名,并根据该签名从内存字典分组中找到对应的分组;
// 计算谜面的字频签名let key = signature(word: scramble)// 直接返回该签名所在分组return groups[key] ?? []
main.swift
main.swift
是该服务器的门户入口,是一个典型的PerfectTemplate 模板程序。该服务器只提供两个路由资源:
/api
- 编程接口路由控制器,即如果调用$ curl http://localhost:8888/api?inp=meat
,则会返回形如["mate","meta","tame","team"]
的JSON格式作为谜底。
request, response in let input = sanitizedInput("inp", request) let list = words.solve(scramble: input) do { let json = try list.jsonEncodedString() response.appendBody(string: json) }catch { response.appendBody(string: "[\"Error\"]") }//end do response.completed()
/
- 根路由,即一个静态的HTML主页页面。但是客户端的行为 - 输入任何谜面则立刻看到谜底 - 是由在该主页中嵌入的一小段 jQuery/Reactive-Extension 脚本控制的。实际上这段javascript脚本是在Github Reactive-Extension Repo 说明页面上稍加修改而来:
// 调用服务器编程接口寻找答案,相当于 `$ curl http://localhost:8888/api?inp=term` function search (term) { return $.ajax({ url: 'api', dataType: 'json', data: { inp: term } }).promise(); }//end search function main() { var $input = $('#textInput'), $results = $('#results'); // 检查输入 var keyup = Rx.Observable.fromEvent($input, 'keyup') .map(function (e) { return e.target.value; }) .filter(function (text) { return text.length > 2; }) .debounce(200 /* 两次查询之间控制 200ms 间隙*/ ) .distinctUntilChanged(); var searcher = keyup.flatMapLatest(search); // 设置回调函数取回并显示数据 searcher.subscribe( function (data) { $results .empty() .append ($.map(data, function (v) { return $('<li>').text(v); })); }); }
更多信息
关于本项目更多内容,请参考perfect.org.
扫一扫 Perfect 官网微信号
- Swift服务器入门+ReactJS代码示范:如何在内存中建立高性能索引
- 如何在内存中申请大量内存
- 【理解】如何在内存中篡改数据
- 对象如何在内存中分配
- 如何在内存中创建Solid
- 如何在内存中画出一张图片
- 如何在前台脚本中调用服务器提供的webservice
- 程序中的变量是如何在内存中分布
- gzip压缩的数据如何在内存中解压
- 进程是如何在内存中分布的
- [OpenCV Qt教程] 如何在内存中压缩图像
- 如何在后台代码中执行原生sql?
- 如何在后台代码中执行原生sql?
- 如何在后台代码中执行原生sql?
- ReactJs性能优化(中)
- 编写高性能的 Swift 代码
- 如何用IIS建立高安全性Web服务器
- 如何用IIS建立高安全性Web服务器
- c++实验4-项目7(3)穷举法解决组合问题三色球问题
- Entity Framework(EF) 直接执行数据库命令并返回 DataTable 数据参数化 SQL 语句
- 使用WebMagic 编写 java 网络爬虫
- 第八章 数组
- JavaWeb开发之JSP隐式对象(二)
- Swift服务器入门+ReactJS代码示范:如何在内存中建立高性能索引
- 杭电ACM 1004 Let the Balloon Rise
- 离散对数和原根
- IMWeb训练营作业——使用vue完成todolist
- [LeetCode]238. Product of Array Except Self
- 集合框架-Collection集合-3
- CASIO DC-850 DATA-CAL Manual (操作使用方法)
- 9个offer,12家公司,35场面试,从微软到谷歌,应届计算机毕业生的2012求职之路
- 物联网时代来了,大数据也终于有了用武之地