rewrite in JavaScript
parent
06d4ed30c3
commit
9c4eefecbf
@ -0,0 +1,45 @@
|
||||
const EMPTY = "empty"
|
||||
const FILL = "fill"
|
||||
const POUR = "pour"
|
||||
const UNKNOWN = "unknown"
|
||||
|
||||
class Action {
|
||||
constructor({ kind = UNKNOWN, target, src } = {}) {
|
||||
this.kind = kind
|
||||
this.target = target
|
||||
this.src = src
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.kind === EMPTY;
|
||||
}
|
||||
|
||||
isFill() {
|
||||
return this.kind === FILL;
|
||||
}
|
||||
|
||||
isPour() {
|
||||
return this.kind === POUR;
|
||||
}
|
||||
|
||||
string() {
|
||||
if (this.kind === POUR) {
|
||||
return `${this.kind} ${this.src} → ${this.target}`
|
||||
}
|
||||
return `${this.kind} ${this.target}`
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fill(index) {
|
||||
return new Action({ kind: FILL, target: index })
|
||||
},
|
||||
|
||||
empty(index) {
|
||||
return new Action({ kind: EMPTY, target: index })
|
||||
},
|
||||
|
||||
pour(src, target) {
|
||||
return new Action({ kind: POUR, target, src })
|
||||
},
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
class Bucket {
|
||||
constructor({ capacity, volume = 0 }) {
|
||||
this.capacity = capacity
|
||||
this.volume = volume
|
||||
}
|
||||
|
||||
fill() {
|
||||
this.volume = this.capacity
|
||||
}
|
||||
|
||||
empty() {
|
||||
this.volume = 0
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.volume === 0
|
||||
}
|
||||
|
||||
isFull() {
|
||||
return this.volume == this.capacity
|
||||
}
|
||||
|
||||
pour(bucket) {
|
||||
const availableVolume = bucket.capacity - bucket.volume
|
||||
if (availableVolume > this.volume) {
|
||||
bucket.volume += this.volume
|
||||
this.volume = 0
|
||||
} else {
|
||||
this.volume -= availableVolume
|
||||
bucket.volume += availableVolume
|
||||
}
|
||||
}
|
||||
|
||||
string() {
|
||||
return `${this.volume}vol/${this.capacity}cap`
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Bucket;
|
@ -0,0 +1,134 @@
|
||||
const Bucket = require('./bucket');
|
||||
const { fill, empty, pour } = require('./action');
|
||||
|
||||
class Buckets {
|
||||
constructor(buckets = []) {
|
||||
this.buckets = buckets;
|
||||
}
|
||||
|
||||
hasTarget(target) {
|
||||
return !!this.buckets.find(bucket => bucket.volume === target)
|
||||
}
|
||||
|
||||
performAction(action) {
|
||||
if (action.isEmpty()) {
|
||||
this.buckets[action.target].empty()
|
||||
}
|
||||
if (action.isFill()) {
|
||||
this.buckets[action.target].fill()
|
||||
}
|
||||
if (action.isPour()) {
|
||||
this.buckets[action.src].pour(this.buckets[action.target])
|
||||
}
|
||||
}
|
||||
|
||||
possibleActions() {
|
||||
const ret = []
|
||||
this.buckets.forEach((bucket, index) => {
|
||||
if (!bucket.isFull()) {
|
||||
ret.push(fill(index));
|
||||
}
|
||||
if (!bucket.isEmpty()) {
|
||||
ret.push(empty(index));
|
||||
this.buckets.forEach((dest, j) => {
|
||||
if (index === j) {
|
||||
return
|
||||
}
|
||||
if (!dest.isFull()) {
|
||||
ret.push(pour(index, j));
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
string() {
|
||||
return this.buckets.map(bucket => bucket.string()).join(', ')
|
||||
}
|
||||
|
||||
findTarget(target) {
|
||||
const results = {
|
||||
mapping: { [this.string()]: [[]] },
|
||||
matching: new Set(),
|
||||
}
|
||||
this._findTarget(target, results, [], 0)
|
||||
|
||||
let best = null
|
||||
|
||||
results.matching.forEach(matchingKey => {
|
||||
const result = results.mapping[matchingKey]
|
||||
if (best === null) {
|
||||
best = result;
|
||||
return
|
||||
}
|
||||
if (best.length == result.length) {
|
||||
best.push(result);
|
||||
}
|
||||
if (best.length > result.length) {
|
||||
best = result;
|
||||
}
|
||||
})
|
||||
|
||||
return best || []
|
||||
}
|
||||
|
||||
_findTarget(target, state, actions = [], depth = 0) {
|
||||
if (this.hasTarget(target)) {
|
||||
return
|
||||
}
|
||||
const possibleActions = this.possibleActions();
|
||||
const results = possibleActions.map(action => {
|
||||
const copy = fromJSON(this);
|
||||
copy.performAction(action);
|
||||
return copy;
|
||||
});
|
||||
|
||||
const numberOfKeys = Object.keys(state.mapping).length
|
||||
|
||||
results.forEach(((result, i) => {
|
||||
const key = result.string();
|
||||
const existingActions = state.mapping[key];
|
||||
const action = possibleActions[i];
|
||||
|
||||
if (result.hasTarget(target)) {
|
||||
state.matching.add(result.string());
|
||||
}
|
||||
|
||||
if (!existingActions) {
|
||||
state.mapping[key] = [[...actions, action]]
|
||||
return
|
||||
}
|
||||
|
||||
if (existingActions[0].length == actions.length + 1) {
|
||||
state.mapping[key].push([...actions, possibleActions[i]])
|
||||
return
|
||||
}
|
||||
|
||||
if (existingActions[0].length > actions.length + 1) {
|
||||
state.mapping[key] = [[...actions, action]]
|
||||
}
|
||||
}));
|
||||
|
||||
if (Object.keys(state.mapping).length === numberOfKeys) {
|
||||
return
|
||||
}
|
||||
|
||||
results.forEach(((result, i) => {
|
||||
const action = possibleActions[i];
|
||||
result._findTarget(target, state, [...actions, action], depth + 1)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
function fromJSON(json) {
|
||||
const jsonStr = JSON.stringify(json)
|
||||
const newJSON = JSON.parse(jsonStr)
|
||||
const buckets = newJSON.buckets.map(val => new Bucket(val));
|
||||
return new Buckets(buckets)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fromJSON,
|
||||
Buckets,
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { fromJSON } = require('./buckets')
|
||||
|
||||
const usage = `main.js target bucket [bucket...]
|
||||
|
||||
target: volume of bucket to search for
|
||||
bucket: volume of bucket
|
||||
`
|
||||
|
||||
function bail() {
|
||||
console.log(usage);
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const args = [...process.argv].slice(2)
|
||||
|
||||
if (args.length < 2) {
|
||||
bail();
|
||||
}
|
||||
|
||||
const intArgs = args.map(arg => Number.parseInt(arg, 10))
|
||||
|
||||
const hasNaN = intArgs.find(arg => Number.isNaN(arg))
|
||||
if (hasNaN) {
|
||||
bail();
|
||||
}
|
||||
|
||||
const target = intArgs[0];
|
||||
|
||||
const bucketsJSON = intArgs.slice(1).map(capacity => ({ capacity }))
|
||||
|
||||
const buckets = fromJSON({ buckets: bucketsJSON })
|
||||
|
||||
const result = buckets.findTarget(target)
|
||||
|
||||
if (result.length === 0) {
|
||||
console.log('No results')
|
||||
}
|
||||
|
||||
if (result.length === 1) {
|
||||
console.log('1 solution')
|
||||
}
|
||||
|
||||
if (result.length > 1) {
|
||||
console.log(`${result.length} solutions`)
|
||||
}
|
||||
|
||||
result.forEach((actions, i) => {
|
||||
console.log(`solution ${i + 1}`)
|
||||
const copy = fromJSON(buckets);
|
||||
actions.forEach(action => {
|
||||
copy.performAction(action)
|
||||
console.log(`${action.string()} resulting in ${copy.string()}`)
|
||||
})
|
||||
});
|
Loading…
Reference in New Issue