注释即笔记:

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的更多相关文章

  1. Eloquent JavaScript #13# HTTP and Forms

    索引 Notes fetch form focus Disabled fields form’s elements property 阻止提交 快速插入单词 实时统计字数 监听checkbox和rad ...

  2. Eloquent JavaScript #10# Modules

    索引 Notes 背景问题 模块Modules 软件包Packages 简易模块 Evaluating data as code CommonJS modules ECMAScript modules ...

  3. Eloquent JavaScript #11# The Document Object Model

    索引 Notes js与html DOM 在DOM树中移动 在DOM中寻找元素 改变Document 创建节点 html元素属性 布局 style CSS选择器 动画 Exercises Build ...

  4. Eloquent JavaScript #09# Regular Expressions

    索引 Notes js创建正则表达式的两种方式 js正则匹配方式(1) 字符集合 重复匹配 分组(子表达式) js正则匹配方式(2) The Date class 匹配整个字符串 Choice pat ...

  5. Eloquent JavaScript #04# Objects and Arrays

    要点索引: JSON More ... 练习 1.补:js字符串的表达方式有三种: "" 和 '' 没什么区别,唯一区别在于 "" 中写 "要转义字符 ...

  6. Eloquent JavaScript #03# functions

    索引: let VS. var 定义函数的几种方式 more... 1.作者反复用的side effect side effect就是对世界造成的改变,例如说打印某些东西到屏幕,或者以某种方式改变机器 ...

  7. Eloquent JavaScript #02# program_structure

    第一章中作者介绍了各种值,但是这些独立的值是没有意义的,只有当值放在更大的框架的时候才会彰显它们的价值.所以第二章开始介绍程序结构. 1.var VS. let 以及 const 作者推荐用 let ...

  8. Eloquent JavaScript #01# values

    When action grows unprofitable, gather information; when information grows unprofitable, sleep.      ...

  9. [Laravel] 07 - Project: functions in Controller

    故事背景 一.项目预览 From: https://www.imooc.com/video/12521 表单操作 一.新增信息 既然是操作,自然会想到:控制器. 控制器  [1] 路由 ----> ...

随机推荐

  1. (转)spring计划任务,springMvc计划任务,Spring@Scheduled,spring定时任务

    一.计划任务实现类 1.用@Component注解标识计划任务类,这样spring可以自动扫描 2.在方法中使用注解标识要执行的方法:@Scheduled(cron="*/30 * * * ...

  2. NgDL:第三周:浅层NN

    1.激活函数 1.tanh函数比sigmoid效果更好,它的区间是[-1,1],所以均值为0,有类似于让数据中心化的效果. //此时Ng说,tanh函数几乎在所有场合都更优越. 2.但是如果yhat是 ...

  3. Swagger Editor本地安装

    一:安装Node JS 二:下载源码swagger-editor源码,解压 下载地址:https://github.com/swagger-api/swagger-editor 三:在解压目录下运行进 ...

  4. 模块讲解----pickle模块(只在python用的序列化与反序列化)

    特点 1.只能在python中使用,只支持python的基本数据类型. 2.可以处理复杂的序列化语法.(例如自定义的类的方法,游戏的存档等) 3.序列化的时候,只是序列化了整个序列对象,而不是内存地址 ...

  5. vue中解决跨域问题

    方法1.后台更改header header('Access-Control-Allow-Origin:*');//允许所有来源访问 header('Access-Control-Allow-Metho ...

  6. java 中使用ajax调用后台方法注意事项

    java 中使用ajax调用后台方法注意事项,后台方法一定要加@ResponseBody jQuery.validator.addMethod("checkRuleName",fu ...

  7. MySQL创建外键约束的报错Error : Can't create table '#sql-534_185' (errno: 150)

    总得来说是因为两个表的字段类型不一致,例如: 两个字段的类型或大小不严格匹配,一个为tinyint,另一个为char:或一个为int(10)另一个为int(9)也是不行的,即使都为int(10),但一 ...

  8. ps 证件照(1,2寸)

    制作证件照      9*9打印 1,1寸  图片裁剪 2, 2寸 图片裁剪 3,将裁剪完成后的图片选择添加画布  Alt Ctrl  c 将高和宽各加20px  ,背景选择白色 4,将得到的带有白色 ...

  9. mysqladmin -u root password

    ERROR : Error appeared during Puppet run: 192.77.108.242_mysql.ppError: mysqladmin -u root  password ...

  10. 认识ZTree

    ZTree基本知识 zTree 是一个依靠 jQuery 实现的多功能 “树插件”.优异的性能.灵活的配置.多种功能的组合是 zTree 最大优点. 一.最简单的树(标准的json数据): 1.set ...