Mail Robot
Last updated
Was this helpful?
Last updated
Was this helpful?
โฉ โฉ โฉ Mail Robot
้ไบๅๅ ่ฃน๏ผ็ฎๅ่ทๅ็ตๆ๏ผ
ใๅ ๆถไปถๅ้ไปถใ่ใๆๆ่ฟ็่ทใ๏ผๅจไผฏไปฒไน้๏ผ็ด้็งปๅ 12 ๆฌกใ
ใๅบๅฎ่ทฏ็ทใ็ด้ 18 ๆฌกใ
ใ็ก้ ญ่ผ่ ๅผใๅนณๅ้่ฆ 69 ๆฌกใ
replit โฉ , require โฉ Graph
const { log } = console;
// โญ import
const _Array = require('../ext/Array_ext.js'); // Array extension
const { Graph } = require('../DataStructure/Graph.js'); // Graph
// 2022.12.29 - 21:23 - (โข) first version
//
// ----------------------------------------------------------------------------
// โญ Robot (โmeans "mutating" methods)
// ex: `new Robot(graph, parcels, location)`
// ----------------------------------------------------------------------------
// private members:
// .#location - robot's current location
// .#graph - robot's internal "map"
// .#parcels - parcels to be delivered
// .#moveCount - robot's move count
// ๐น .#moveToNeighbor() - โmove to neighbor
// ----------------------------------------------------------------------------
// ๐ฆ Robot.strategy - strategy on deciding next move
// ----------------------------------------------------------------------------
// ๐ธ .undeliveredParcels - parcels haven't been delivered
// ๐ธ .carriedParcels - parcels on robot
// ๐ธ .parcelsToCollect - (undelivered) parcels not on robot
// ๐ธ .isDone - check if robot is done
// ๐ธ .moveCount - count robot's moves
// ๐น .move() - โmove to another node (not necessarily neighbor)
// ๐น .run() - โrun robot on chosen strategy
// ----------------------------------------------------------------------------
// ๐ธ .debugMode
class Robot {
// private fields
#location; // robot's current location
#graph; // robot's internal "map"
#parcels; // parcels to be delivered
#moveCount = 0;
// public fields
debugMode = false;
// --------------------------------
// strategies for next move
// --------------------------------
// ๐ฆ Robot.strategy
static strategy = {
// ๐ฆ Robot.strategy.randomMove():
randomMove() {
return function() {
return this.#graph.neighborsOf(this.#location).randomElement;
}
},
// ๐ฆ Robot.strategy.mailRoute():
// - If we find a route that passes all places in the village,
// the robot could run that route twice, at which point it
// is guaranteed to be done.
mailRoute() {
// mail truck's route
const route = [
"A", "C", "A", "B", "T", "D", "E",
"G", "S", "G", "F", "M", "P"
];
let i = route.indexOf(this.#location);
if (i === -1) { throw new Error(`no such vertex: ${start}!`) };
return function() {
i += 1;
if (i === route.length) i = 0;
return route[i];
};
},
// ๐ฆ Robot.strategy.nearest:
// - go to the nearest node to collect/send parcels
nearest() {
return function() {
// targets: carriedParcels (to) & parcelsToCollect (from)
// (duplicates removed)
const toGet = this.parcelsToCollect.map(p => p.from).removeDuplicates();
const toSend = this.carriedParcels.map(p => p.to).removeDuplicates();
const targets = toGet.union(toSend);
if (targets.length === 0) return null;
// find nearest target
const nearestNode = targets.reduce((nearest, node) => {
const d1 = this.#graph.distance(nearest, this.#location);
const d2 = this.#graph.distance(node, this.#location);
if (d2 < d1) nearest = node;
return nearest;
});
if (this.debugMode) {
log(
`-`.repeat(30) + '\n' +
`โข robot: ${this.#location}, nearest: ${nearestNode}\n` +
` get: [${toGet}], send: [${toSend}]\n` +
` targets: [${targets}]`,
);
}
return nearestNode;
}
},
// ๐ฆ Robot.strategy.collectFirst:
// - always collect first, then send.
collectFirst() {
return function() {
// targets: carriedParcels (to) & parcelsToCollect (from)
// (duplicates removed)
const toGet = this.parcelsToCollect.map(p => p.from).removeDuplicates();
const toSend = this.carriedParcels.map(p => p.to).removeDuplicates();
const targets = (toGet.isEmpty) ? toSend : toGet;
if (targets.isEmpty) return null;
// find nearest target
const nearestNode = targets.reduce((nearest, node) => {
const d1 = this.#graph.distance(nearest, this.#location);
const d2 = this.#graph.distance(node, this.#location);
if (d2 < d1) nearest = node;
return nearest;
});
if (this.debugMode) {
log(
`-`.repeat(40) + '\n' +
`โข robot: ${this.#location}, ${toGet.isEmpty ? 'send' : 'collect'}: ${nearestNode}\n` +
` get: [${toGet}], send: [${toSend}]\n` +
` targets: [${targets}]`,
);
}
return nearestNode;
}
},
};
// โญ constructor
constructor(graph, parcels, location = 'P') {
this.#graph = graph;
this.#parcels = parcels;
this.#location = location;
}
// ๐ธ .undeliveredParcels
// undelivered parcels (including parcels not currently carried by robot)
get undeliveredParcels() {
return this.#parcels.filter(p => !p.isDelivered);
}
// ๐ธ .carriedParcels
// parcels on robot
get carriedParcels() {
return this.undeliveredParcels.filter(p => p.location === this.#location);
}
// ๐ธ .parcelsToCollect
// parcels not on robot
get parcelsToCollect() {
return this.undeliveredParcels.filter(p => p.location !== this.#location);
}
// ๐ธ .isDone
// is job done?
get isDone() {
return this.undeliveredParcels.length === 0;
}
// ๐น .#moveToNeighbor()
// - private method
#moveToNeighbor = function(neighbor) {
// check if "neighbor" is really a neighbor.
if (!this.#graph.areNeighbors(this.#location, neighbor)) {
throw new Error(`Robot: โ invalid move: ${this.#location} -> ${neighbor}!`);
}
// update carried parcels
this.carriedParcels.forEach(p => p.move(neighbor));
// update self
const from = this.#location;
this.#location = neighbor;
this.#moveCount += 1;
if (this.debugMode) log(`[${this.#moveCount}] robot: ${from} -> ${neighbor}`);
};
// ๐น .move()
move(to) {
const path = this.#graph.findShortestPath(this.#location, to);
if (!path) throw new Error(`Robot: โ ${to} is not reachable!`);
if (this.debugMode) log(`โข path: [${path}]`);
path.slice(1).forEach(node => this.#moveToNeighbor(node));
}
// ๐ธ .moveCount
get moveCount() { return this.#moveCount; }
// ๐น .run()
// run the robot
// example: robot.run(Robot.strategy.mailRoute);
run(strategy) {
// return a proper function depending on `strategy`
const nextStop = strategy.call(this).bind(this);
// while not done, keep running
while (!this.isDone) {
const to = nextStop();
if (!to) throw new Error(`โ robot can't go anywhere from ${this.#location}!`);
this.move(to);
}
// now it's done
if (this.debugMode) log(
`-`.repeat(40) + '\n' +
`๐ delivery complete in ${this.#moveCount} moves!\n`
);
}
}
// export
module.exports = { Robot };
// 2022.12.29 - 21:23 - (โข) first version
//
// โญ Parcel
//
// ex: `new Parcel(from, to)`
// -----------------------------------------------
// private fields:
//
// #from
// #to
// #location
// -----------------------------------------------
// ๐ง Parcel.constantSamples
// ๐ฆ Parcel.randomSamples()
// ๐ฆ Parcel.copyParcels() - copy parcels โญ
// -----------------------------------------------
// ๐ธ .isDelivered - check if parcel is delivered
// ๐ธ .location - current location
// ๐ธ .from - `from` address
// ๐ธ .to - `to` address
// ๐น .move() -โ(mutating) move to next stop
// -----------------------------------------------
// ๐น .toString()
// ๐น .copy() - new copy of this parcel
//
class Parcel {
// private properties
#from;
#to;
#location; // current locaation
// ๐ฆ Parcel.samples()
// - create some random parcels
static randomSamples(nodes, count = 5) {
const result = [];
for (let i = 0; i < count; i++) {
// choose a random "from" address
const from = nodes.randomElement;
// choose a random 'to' address until "to" != "from"
let to;
do { to = nodes.randomElement } while ( to === from);
// now, to !== from
result.push(new Parcel(from, to));
}
return result;
}
// ๐ง Parcel.constantSamples
static get constantSamples() {
return [
new Parcel('M', 'A'), new Parcel('M', 'E'), new Parcel('C', 'G'),
new Parcel('G', 'A'), new Parcel('G', 'E'),
];
}
// ๐ฆ Parcel.copyParcels()
static copyParcels(parcels) {
return parcels.map(parcel => parcel.copy());
}
// โญ constructor
constructor(from, to) {
this.#from = from; // fixed
this.#to = to; // fixed
this.#location = from;
}
// ๐ธ .isDelivered
get isDelivered() { return this.#location === this.#to; }
// ๐ธ .location
get location() { return this.#location; }
// ๐ธ .from
get from() { return this.#from; }
// ๐ธ .to
get to() { return this.#to; }
// ๐น move()
move(to) {
// check if parcel has been delivered already
if (this.isDelivered) {
console.log(`โ ๏ธ can't move parcel: parcel is delivered!`);
return;
}
this.#location = to;
}
// ๐น .toString()
toString() {
return `Parcel ${this.#from} -> ${this.#to} (current: ${this.#location})`;
}
// ๐น .copy()
copy() {
return new Parcel(this.#from, this.#to);
}
}
// export
module.exports = { Parcel };
'use strict'; // โญ toggle sloppy/strict mode
const { log } = console;
// โญ import
const { Graph } = require('./DataStructure/Graph.js');
const { Robot } = require('./Robot/Robot.js');
const { Parcel } = require('./Robot/Parcel.js');
// ---------------------------------------------------------------------------
// โญ roads (edges)
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"
];
const edges = roads.map(s => s.split('-').map(s => s[0]));
// [
// [ 'A', 'B' ], [ 'A', 'C' ],
// [ 'A', 'P' ], [ 'B', 'T' ],
// [ 'D', 'E' ], [ 'D', 'T' ],
// [ 'E', 'G' ], [ 'G', 'F' ],
// [ 'G', 'S' ], [ 'M', 'F' ],
// [ 'M', 'P' ], [ 'M', 'S' ],
// [ 'M', 'T' ], [ 'S', 'T' ]
// ]
// โญ node names
const node = Object.create(null);
roads
.flatMap(s => s.split('-').map(s => [s[0], s]))
.forEach(([char, name]) => node[char] = name);
// {
// A: "Alice's House",
// B: "Bob's House",
// C: 'Cabin',
// P: 'Post Office',
// T: 'Town Hall',
// D: "Daria's House",
// E: "Ernie's House",
// G: "Grete's House",
// F: 'Farm',
// S: 'Shop',
// M: 'Marketplace'
// }
// adjacency list
const list = Graph.edgesToAdjacencyList(edges);
// A: [B,C,P]
// B: [A,T]
// C: [A]
// P: [A,M]
// T: [B,D,M,S]
// D: [E,T]
// E: [D,G]
// G: [E,F,S]
// F: [G,M]
// S: [G,M,T]
// M: [F,P,S,T]
// โญ Graph
const graph = new Graph(list);
// โญ Parcel
// const parcels = Parcel.randomSamples([...graph.nodes]);
const parcels = Parcel.constantSamples;
// log(parcels.map(p => String(p)));
// [
// 'Parcel M -> A (current: M)',
// 'Parcel M -> E (current: M)',
// 'Parcel C -> G (current: C)',
// 'Parcel G -> A (current: G)',
// 'Parcel G -> E (current: G)'
// ]
// โญ 4 strategies
const mode = [
Robot.strategy.collectFirst,
Robot.strategy.nearest,
Robot.strategy.mailRoute,
Robot.strategy.randomMove,
];
// โญ Robot
// const robot = new Robot(graph, Parcel.copyParcels(parcels));
// robot.debugMode = true;
// robot.run(mode[1]);
// โญ count robots moves on 4 differenct strateges.
// - sending 5 parcels each time.
function moveCounts() {
// parcel samples
const parcels = Parcel.randomSamples([...graph.nodes]);
// robots
const robots = [
new Robot(graph, Parcel.copyParcels(parcels)),
new Robot(graph, Parcel.copyParcels(parcels)),
new Robot(graph, Parcel.copyParcels(parcels)),
new Robot(graph, Parcel.copyParcels(parcels)),
];
// run/compare robots
const counts = robots.map((robot, i) => {
robot.run(mode[i]);
return robot.moveCount;
});
return counts;
}
// run moveCounts() a few times.
function benchmark(times = 100) {
const records = [];
for (let i = 0; i < times; i++) {
records.push(moveCounts());
}
return records
.reduce(([a, b, c, d], [p, q, r, s]) => [a + p, b + q, c + r, d + s])
.map(x => x / times);
}
// โ
log expressions that never throw
// ---------------------------------------------------------------------------
;[
benchmark(100),
// [ 11.51, 11.49, 17.70, 64.10 ] (100 times)
// [ 12.41, 12.64, 18.03, 69.27 ]
// [ 12.23, 12.40, 18.11, 69.54 ]
// [ 12.10, 12.28, 18.38, 69.61 ]
// [ 12.08, 11.90, 18.60, 69.63 ]
// [ 12.04, 12.11, 18.16, 68.52 ] (1000 times)
].forEach(x => log(x));
Queue - used in breadth-first search.
replit โฉ
replit โฉ
replit โฉ
Eloqent JavaScript โฉ โญ๏ธ
MakeUseOf โฉ
simplilearn โฉ
VisuAlgo โฉ