Eloquent JavaScript #07# Project: A Robot
索引
Notes
Excercise
注释即笔记:
const roads = [
"Alice's House-Bob's House", "Alice's House-Cabin",
"Alice's House-Post Office", "Bob's House-Town Hall",
"Daria's House-Ernie's House", "Daria's House-Town Hall",
"Ernie's House-Grete's House", "Grete's House-Farm",
"Grete's House-Shop", "Marketplace-Farm",
"Marketplace-Post Office", "Marketplace-Shop",
"Marketplace-Town Hall", "Shop-Town Hall"
]; function buildGraph(edges) {
// graph的存储格式类似于
// graph.place_a: [place_b, place_c]
// 表示由a能够直接到达b或者c
let graph = Object.create(null); function addEdge(from, to) {
// 首先判断该起点有没有被添加
if(graph[from] == null) {
graph[from] = [to];
} else {
graph[from].push(to);
}
} // 'Alice's House-Bob's House'.split("-")
// → ['Alice's House', 'Bob's House']
for(let [from, to] of edges.map(r => r.split("-"))) {
addEdge(from, to);
addEdge(to, from);
} return graph;
} const roadGraph = buildGraph(roads); /**
* 不要条件反射地把每个概念都搞成对象,
* 这样的程序通常是难以理解和维护的。
* 相反,用最小的数值集合来描述村庄&机器人
* 状态就可以了。
*/ class VillageState {
constructor(place, parcels) {
this.place = place;
this.parcels = parcels;
} move(destination) {
// move表示机器人的一次移动。
// 首先检查是否存在一条从当前位置this.place到目的地destination的路
// 如果不存在就说明不是合法的移动,返回旧的VillageState。
// 如果存在,就更新机器人的当前位置,也就是更新VillageState.place
// 为destination,当然同时需要更新包裹的状态:1-还没被机器人拿到
// 的包裹不去管它,2-拿到的包裹则更新当前位置place(目的地address不改变)
// 3-最后过滤掉已经送到的包裹(目的地就在本地)
// PS. 整个move方法实际上是重造了一个VillageState,并没改变旧的
// VillageState对象
if(!roadGraph[this.place].includes(destination)) {
return this;
} else {
let parcels = this.parcels.map(p => {
if(p.place != this.place) return p;
return {
place: destination,
address: p.address
};
}).filter(p => p.place != p.address);
return new VillageState(destination, parcels);
}
}
} /**
* 可以用Object.freeze冻结对象
* 所有对该对象属性的写入操作会被忽略
* 这需要计算机做一些额外工作
* let object = Object.freeze({value: 5});
object.value = 10;
console.log(object.value);
// → 5
*/ /**
* But the most important limit
* on what kind of systems we can build
* is how much we can understand.
* Anything that makes your code easier
* to understand makes it possible
* to build a more ambitious system.
* 写程序最重要的限制是我们能够理解多少
*/ // robot实际上是一个函数接口,
// 该函数输入state和memory
// 输出决策action用于实际移动
// 这样写就能够动态更改策略了。
function runRobot(state, robot, memory) {
for(let turn = 0;; turn++) {
if(state.parcels.length == 0) {
console.log(`Done in ${turn} turns`);
break;
}
let action = robot(state, memory);
state = state.move(action.direction);
memory = action.memory;
console.log(`Moved to ${action.direction}`);
}
} function randomPick(array) {
let choice = Math.floor(Math.random() * array.length);
return array[choice];
} // 最愚蠢但有效的策略--随机决定下一个方向。
// 虽然这里只有一个参数,但是js允许传
// 入更多(或者更少)的参数。在这里,传入的
// memeory被忽略了
function randomRobot(state) {
return {
direction: randomPick(roadGraph[state.place])
};
} // 初始化
VillageState.random = function(parcelCount = 5) {
let parcels = [];
for(let i = 0; i < parcelCount; i++) {
let address = randomPick(Object.keys(roadGraph));
let place;
do {
place = randomPick(Object.keys(roadGraph));
// 包裹的起点和终点不可以是同一个地方
} while (place == address);
parcels.push({
place,
address
});
}
return new VillageState("Post Office", parcels);
}; // runRobot(VillageState.random(), randomRobot);
// 版本一,步数不稳定 // runRobotAnimation(VillageState.random(), randomRobot);
// 作者写的动画版本,相当直观酷炫。。 // 第二个策略:事先指定一条可以通过所有地点的路线
// 走两遍就可以确保投递所有邮件
const mailRoute = [
"Alice's House", "Cabin", "Alice's House", "Bob's House",
"Town Hall", "Daria's House", "Ernie's House",
"Grete's House", "Shop", "Grete's House", "Farm",
"Marketplace", "Post Office"
]; // [a,b,c].slice(1)
// → [b,c]
// [a,b,c].slice(1, 2)
// → [b] // 包括start不包括end
function routeRobot(state, memory) {
if(memory.length == 0) {
memory = mailRoute;
}
// memory类似于队列
// 等价: return {direction: memory.shift(), memory: memory}
return {
direction: memory[0],
memory: memory.slice(1)
};
} // runRobot(VillageState.random(), routeRobot, []);
// 版本二,最多26步 /**
* The problem of finding a route
* through a graph is a typical search problem.
* We can tell whether a given solution (a route)
* is a valid solution, but we can’t directly compute
* the solution the way we could for 2 + 2.
* Instead, we have to keep creating potential solutions
* until we find one that works.
*/ // 返回一点到另一点的最短路线,参考:C++ 电路布线/最短路径问题
function findRoute(graph, from, to) {
let work = [{at: from, route: []}]; // 其实也是个队列
for(let i = 0; i < work.length; i++) {
let {at, route} = work[i]; // 原来还可以这样赋值。。
for(let place of graph[at]) {
// 搜索四周围,如果找到了目的地就直接+1返回。
if(place == to) return route.concat(place);
if(!work.some(w => w.at == place)) { // 判断点是否已经入队
work.push({at: place, route: route.concat(place)});
}
}
}
} function goalOrientedRobot({place, parcels}, route) {
// 首先判断当前制定的路线走完没有,
// 走完就重新制定下一条路线
// 逐个包裹处理(当然也有可能顺
// 路完成其它包裹的fetch和投递)
if(route.length == 0) {
let parcel = parcels[0];
if(parcel.place != place) {
// 制定取包裹路线
route = findRoute(roadGraph, place, parcel.place);
} else {
// 制定投递路线
route = findRoute(roadGraph, place, parcel.address);
}
}
return {direction: route[0], memory: route.slice(1)};
} runRobot(VillageState.random(), goalOrientedRobot, []);
// 版本三,平均十来步的样子
Exercises
① Measuring a robot
function testRobot(state, robot, memory) {
for(let turn = 0;; turn++) {
if(state.parcels.length == 0) {
return turn;
break;
}
let action = robot(state, memory);
state = state.move(action.direction);
memory = action.memory;
}
}
function compareRobots(robot1, memory1, robot2, memory2) {
let tasks = [];
for (let i = 0; i != 100; ++i) {
tasks.push(VillageState.random());
}
let total1 = 0, total2 = 0;
for (let task of tasks) {
total1 += testRobot(task, robot1, memory1);
total2 += testRobot(task, robot2, memory2);
}
console.log(`average turns: robot1 ${total1 / 100}, robot2 ${total2 / 100}`);
}
compareRobots(routeRobot, [], goalOrientedRobot, []);
// → average turns: robot1 18.07, robot2 15.03
- - -- - - - - -- -- -- - - - - -- - -- - -
② Robot efficiency
没做任何优化的穷举,而且还用递归。。。6个以上包裹浏览器应该会直接崩溃掉。。
/**
max表示一个包裹要被处理的次数
arr为长度为包裹数量的全0数组
arr[i]表示第i个包裹被处理的次数
当arr为[max, max, max, ...]时
表示所有包裹已经被处理完
返回的sequences包含处理包裹的
所有顺序集合
*/
function makeSequences(max, arr) { const sequences = []; const fillArrWith = (max, arr, start, sequence) => {
// 填充起始点
arr[start] += 1;
sequence.push(start);
// 判断是否已经填充满
let sum = 0;
for (let x of arr) {
sum += x;
};
if (sum == max * arr.length) {
sequences.push(sequence);
return;
}
// 寻找下一个填充点
for (let i = 0; i != arr.length; ++i) {
if (arr[i] < max) fillArrWith(max, arr.slice(), i, sequence.slice());
}
}; for (let i = 0; i != arr.length; ++i) {
fillArrWith(max, arr.slice(), i, []);
} return sequences;
} /**
把生成的序列转化为具体业务相关的表达。
得到route并不是实际的route 而是可能不相邻的地点
routes包含所有能够完成任务的路线
*/
function sequencesToRoutes(sequences, {place, parcels}) {
const routes = []; const flag = parcels.map(() => 0); // 用于之后拷贝用
// 逐个序列处理
for (let sequence of sequences) {
let route = [place]; // 添加起点
let localFlag = flag.slice(); // 标记包裹的状态
for (let num of sequence) {
if (localFlag[num] == 0) { // 第一次处理包裹num:到place取包裹
localFlag[num]++; // 包裹num已取,这样可以保证某包裹的place一定优先于该包裹的address入队
if (route[route.length - 1] != parcels[num].place) { // 避免出现两个连续重复place
route.push(parcels[num].place);
}
} else { // 第二次处理包裹num: 送包裹,去该包裹的目的地
if (route[route.length-1] != parcels[num].address) {
route.push(parcels[num].address);
}
}
}
routes.push(route);
} return routes;
} /**
* 计算单个路线需要的最短步数
* turnsMap用于保存已经计算的两点值,避免重复计算
*/
function turnsOfRoute(route, turnsMap=new Map()) {
let totalTurns = 0;
for (let i = 0; i != route.length - 1; ++i) {
// 两点、两点处理。
let routeKey = route[i].concat(route[i + 1]);
let turns = turnsMap.get(routeKey);
if (turns != undefined) {
totalTurns += turns;
} else {
turns = findRoute(roadGraph, route[i], route[i + 1]).length;
// 计算 a到b 的最小步数 ↑
// 保存计算结果 ↓
turnsMap.set(routeKey, turns);
routeKey = route[i + 1].concat(route[i]); // a到b和b到a的最短路是一样的。
turnsMap.set(routeKey, turns); totalTurns += turns;
}
}
return totalTurns;
} /**
* 寻找最短路线
*/
function shortestRoute(routes) {
let min = Infinity;
let tempRoute;
let turnsMap = new Map(); // 用于保存已经计算的两点值,避免重复计算
for (let route of routes) {
let turns = turnsOfRoute(route, turnsMap);
if (turns < min) {
min = turns;
tempRoute = route; // 保存最短路线
}
} // 将最短路线转化为相邻的可以实际移动的地点序列
let result = [];
for (let i = 0; i != tempRoute.length - 1; ++i) {
// 仍然是两点、两点处理
let midRoute = findRoute(roadGraph, tempRoute[i], tempRoute[i + 1]);
if (result[result.length - 1] != midRoute[0]) { // 避免出现两个连续重复place
result = result.concat(midRoute);
} else {
result = result.concat(midRoute.shift());
}
}
return result;
} function simpleRobot({place, parcels}, route) {
if(route.length == 0) {
let sequences = makeSequences(2, parcels.map(() => 0)); // 转化成一种比较抽象的东西。。。
let routes = sequencesToRoutes(sequences, {place, parcels});
route = shortestRoute(routes);
}
return {direction: route[0], memory: route.slice(1)};
} //runRobot(VillageState.random(), simpleRobot, []);
// 版本四,穷举。。 平均 10.64
//runRobotAnimation(VillageState.random(), simpleRobot, []);
- - -- - - - - -- -- -- - - - - -- - -- - -
③ Persistent group
结果没错,实现上有点跑偏。。(英语阅读能力不足造成的)
class PGroup {
add(x) {
let result = Object.create(PGroup.prototype);
if (this.container == undefined) {
this.container = [];
}
if (!this.container.includes(x)) {
result.container = this.container.concat(x);
} else {
result.container = this.container;
}
return result;
}
delete(x) {
let result = Object.create(PGroup.prototype);
if (this.container != undefined) {
result.container = this.container.filter(a => !(a === x));
}
return result;
}
has(x) {
if (this.container != undefined) {
return this.container.includes(x);
}
return false;
}
}
PGroup.empty = Object.create(PGroup.prototype);
let a = PGroup.empty.add("a");
let ab = a.add("b");
let b = ab.delete("a");
console.log(b.has("b"));
// → true
console.log(a.has("b"));
// → false
console.log(b.has("a"));
// → false
Eloquent JavaScript #07# Project: A Robot的更多相关文章
- Eloquent JavaScript #13# HTTP and Forms
索引 Notes fetch form focus Disabled fields form’s elements property 阻止提交 快速插入单词 实时统计字数 监听checkbox和rad ...
- Eloquent JavaScript #10# Modules
索引 Notes 背景问题 模块Modules 软件包Packages 简易模块 Evaluating data as code CommonJS modules ECMAScript modules ...
- Eloquent JavaScript #11# The Document Object Model
索引 Notes js与html DOM 在DOM树中移动 在DOM中寻找元素 改变Document 创建节点 html元素属性 布局 style CSS选择器 动画 Exercises Build ...
- Eloquent JavaScript #09# Regular Expressions
索引 Notes js创建正则表达式的两种方式 js正则匹配方式(1) 字符集合 重复匹配 分组(子表达式) js正则匹配方式(2) The Date class 匹配整个字符串 Choice pat ...
- Eloquent JavaScript #04# Objects and Arrays
要点索引: JSON More ... 练习 1.补:js字符串的表达方式有三种: "" 和 '' 没什么区别,唯一区别在于 "" 中写 "要转义字符 ...
- Eloquent JavaScript #03# functions
索引: let VS. var 定义函数的几种方式 more... 1.作者反复用的side effect side effect就是对世界造成的改变,例如说打印某些东西到屏幕,或者以某种方式改变机器 ...
- Eloquent JavaScript #02# program_structure
第一章中作者介绍了各种值,但是这些独立的值是没有意义的,只有当值放在更大的框架的时候才会彰显它们的价值.所以第二章开始介绍程序结构. 1.var VS. let 以及 const 作者推荐用 let ...
- Eloquent JavaScript #01# values
When action grows unprofitable, gather information; when information grows unprofitable, sleep. ...
- [Laravel] 07 - Project: functions in Controller
故事背景 一.项目预览 From: https://www.imooc.com/video/12521 表单操作 一.新增信息 既然是操作,自然会想到:控制器. 控制器 [1] 路由 ----> ...
随机推荐
- lnmp/nginx系统真正有效的图片防盗链完整设置详解
http://www.it300.com/article-15345.html 关于nginx防盗链的方法网上有很多教程,都可以用,但是我发现很多教程并不完整,所做的防盗链并不是真正的彻底的防盗链! ...
- Spark --- 启动、运行、关闭过程
// scalastyle:off println package org.apache.spark.examples import scala.math.random import org.apac ...
- 最短路径-并查集+Floyd[转载]
题目描述 N个城市,标号从0到N-1,M条道路,第K条道路(K从0开始)的长度为2^K,求编号为0的城市到其他城市的最短距离 输入描述: 第一行两个正整数N(2<=N<=100)M(M&l ...
- 【UML】-NO.41.EBook.5.UML.1.001-【UML 大战需求分析】- 类图(Class Diagram)
1.0.0 Summary Tittle:[UML]-NO.41.EBook.1.UML.1.001-[UML 大战需求分析]- 类图 Style:DesignPattern Series:Desig ...
- 第二弹:超全Python学习资源整理(进阶系列)
造一个草原要一株三叶草加一只蜜蜂.一株三叶草,一只蜂,再加一个梦.要是蜜蜂少,光靠梦也行. - 狄金森 "成为编程大牛要一门好语言加一点点天分.一门好语言,一点点天分,再加一份坚持.要是天分 ...
- IntelliJ IDEA 17 本地LicenseServer激活
注意:此方法适用于Idea v2017.2.x 版本及以前版本. IntelliJ IDEA及破解包下载地址:百度网盘 密码:hlko 一.将IntelliJIDEALicenseServer.e ...
- 胸片和CT断层图像是怎么来的?
本文作者系医科大学青年教师,关注公众号"计算机视觉life"菜单栏回复"医学" 进群交流 如何得到CT断层图像? 相信小伙伴体检的时候都拍过胸片,假如哪个不幸的 ...
- cocos2d-x JS 计算赋值时出现 NaN
NaN “Not a Number”.出现这个数值比较少见,以至于我们可以不理它.当运算无法返回正确的数值时,就会返回“NaN”值.NaN 值非常特殊,因为它“不是数字”,所以任何数跟它都不相等,甚至 ...
- Appium基础(二)demo
具体配置,请看上一篇 一.启动Android模拟 Start 打开如下界面:这个过程可能会有些慢,黑屏等等,请耐心等待 二.appium配置 1.点机器人图标 PlatformVersion 选19, ...
- 数据加密之MD5加密
MD5是一个安全的散列算法,有两个特点:1.输入两个不同的明文(一段原始的数字信息)不会得到相同的输出值2.根据输出值,不能得到原始的明文,即过程不可逆所以要解密MD5没有现成的算法,只能用穷举法,把 ...