基于 Trampoline 的链表(lisp 树)与循环字符串
Trampoline based linked list (lisp tree) to string with cycles
我对 lisp 列表进行字符串化的基于蹦床的函数有问题。这是代码:
function Pair(car, cdr) {
this.car = car;
this.cdr = cdr;
}
const nil = new function Nil() {};
// ----------------------------------------------------------------------
Pair.fromArray = function(array) {
var result = nil;
var i = array.length;
while (i--) {
let car = array[i];
if (car instanceof Array) {
car = Pair.fromArray(car);
}
result = new Pair(car, result);
}
return result;
};
// ----------------------------------------------------------------------
function Thunk(fn, cont = () => {}) {
this.fn = fn;
this.cont = cont;
}
// ----------------------------------------------------------------------
Thunk.prototype.toString = function() {
return '#<Thunk>';
};
// ----------------------------------------------------------------------
function trampoline(fn) {
return function(...args) {
return unwind(fn.apply(this, args));
};
}
// ----------------------------------------------------------------------
function unwind(result) {
while (result instanceof Thunk) {
const thunk = result;
result = result.fn();
if (!(result instanceof Thunk)) {
thunk.cont();
}
}
return result;
}
// ----------------------------------------------------------------------
// original function have different data types here
// with simplified version this is fine
function toString(x) {
return x.toString();
}
// ----------------------------------------------------------------------
const pair_to_string = (function() {
const prefix = (pair, rest) => {
var result = [];
if (pair.ref) {
result.push(pair.ref + '(');
} else if (!rest) {
result.push('(');
}
return result;
};
const postfix = (pair, rest) => {
if (!rest || pair.ref) {
return [')'];
}
return [];
};
return trampoline(function pairToString(pair, quote, extra = {}) {
const {
nested,
result = [],
cont = () => {
result.push(...postfix(pair, nested));
}
} = extra;
result.push(...prefix(pair, nested));
let car;
if (pair.cycles && pair.cycles.car) {
car = pair.cycles.car;
} else {
car = toString(pair.car, quote, true, { nested: false, result, cont });
}
if (car !== undefined) {
result.push(car);
}
return new Thunk(() => {
if (pair.cdr instanceof Pair) {
if (pair.cycles && pair.cycles.cdr) {
result.push(' . ');
result.push(pair.cycles.cdr);
} else {
if (pair.cdr.ref) {
result.push(' . ');
} else {
result.push(' ');
}
return pairToString(pair.cdr, quote, {
nested: true,
result,
cont
});
}
} else if (pair.cdr !== nil) {
result.push(' . ');
result.push(toString(pair.cdr, quote));
}
}, cont);
});
})();
// ----------------------------------------------------------------------
Pair.prototype.toString = function(quote) {
var result = [];
pair_to_string(this, quote, {result});
return result.join('');
};
// ----------------------------------------------------------------------
function range(n) {
return new Array(n).fill(0).map((_, i) => i);
}
// ----------------------------------------------------------------------------
function markCycles(pair) {
var seen_pairs = [];
var cycles = [];
var refs = [];
function visit(pair) {
if (!seen_pairs.includes(pair)) {
seen_pairs.push(pair);
}
}
function set(node, type, child, parents) {
if (child instanceof Pair) {
if (parents.includes(child)) {
if (!refs.includes(child)) {
refs.push(child);
}
if (!node.cycles) {
node.cycles = {};
}
node.cycles[type] = child;
if (!cycles.includes(node)) {
cycles.push(node);
}
return true;
}
}
}
const detect = trampoline(function detect_thunk(pair, parents) {
if (pair instanceof Pair) {
delete pair.ref;
delete pair.cycles;
visit(pair);
parents.push(pair);
var car = set(pair, 'car', pair.car, parents);
var cdr = set(pair, 'cdr', pair.cdr, parents);
var thunks = [];
if (!car) {
detect(pair.car, parents.slice());
}
if (!cdr) {
const cdr_args = [pair.cdr, parents.slice()];
return new Thunk(() => {
return detect_thunk(...cdr_args);
});
}
}
});
function mark_node(node, type) {
if (node.cycles[type] instanceof Pair) {
const count = ref_nodes.indexOf(node.cycles[type]);
node.cycles[type] = `#${count}#`;
}
}
detect(pair, []);
var ref_nodes = seen_pairs.filter(node => refs.includes(node));
ref_nodes.forEach((node, i) => {
node.ref = `#${i}=`;
});
cycles.forEach(node => {
mark_node(node, 'car');
mark_node(node, 'cdr');
});
}
// ----------------------------------------------------------------------
// this works fine
//console.log(Pair.fromArray([[[range(8000), range(10)]]]).toString());
var data = new Pair(1, new Pair(new Pair(2, nil), new Pair(3, nil)));
data.cdr.car.cdr = data.cdr;
data.cdr.cdr.cdr = data;
markCycles(data)
console.log(data.toString());
console.log("#0=(1 . #1=((2 . #1#) 3 . #0#)) - valid");
问题是最后一个括号丢失了,我不确定我应该如何在 trampoline 中使用 continuation 来解决这个问题。
这是在没有蹦床的情况下工作的代码:
// ----------------------------------------------------------------------
Pair.prototype.toString = function(quote, rest) {
var arr = [];
if (this.ref) {
arr.push(this.ref + '(');
} else if (!rest) {
arr.push('(');
}
var value;
if (this.cycles && this.cycles.car) {
value = this.cycles.car;
} else {
value = toString(this.car, quote, true);
}
if (value !== undefined) {
arr.push(value);
}
if (this.cdr instanceof Pair) {
if (this.cycles && this.cycles.cdr) {
arr.push(' . ');
arr.push(this.cycles.cdr);
} else {
if (this.cdr.ref) {
arr.push(' . ');
} else {
arr.push(' ');
}
const cdr = this.cdr.toString(quote, true);
arr.push(cdr);
}
} else if (this.cdr !== nil) {
arr = arr.concat([' . ', toString(this.cdr, quote, true)]);
}
if (!rest || this.ref) {
arr.push(')');
}
return arr.join('');
};
我有两种情况,第一种是大列表(8000 个元素),另一种是小循环。使用堆栈片段中的代码,它可以处理长列表,但不能处理没有蹦床的循环,它会溢出大列表上的堆栈。它也是 lisp,所以它需要处理任何树,而不仅仅是链表。
编辑:如果您尝试回答,请至少不要更改数据结构。它需要与 car 和 cdr 配对 class,并且在将它们转换为字符串之前需要计算周期。所以它与多个函数一起工作,检查内存中的数据是否是循环的。
这就是我要做的。
const pure = value => ({ constructor: pure, value });
const bind = (monad, arrow) => ({ constructor: bind, monad, arrow });
const thunk = eval => ({ constructor: thunk, eval });
function evaluate(expression) {
let expr = expression;
let stack = null;
while (true) {
switch (expr.constructor) {
case pure:
if (stack === null) return expr.value;
expr = stack.arrow(expr.value);
stack = stack.stack;
break;
case bind:
stack = { arrow: expr.arrow, stack };
expr = expr.monad;
break;
case thunk:
expr = expr.eval();
}
}
}
const monadic = func => thunk(() => {
const gen = func();
function next(data) {
const { value, done } = gen.next(data);
return done ? value : bind(value, next);
}
return next(undefined);
});
class Pair {
constructor(car, cdr) {
this.car = car;
this.cdr = cdr;
}
static fromArray(array) {
const loop = (array, index) => monadic(function* () {
if (index === array.length) return pure(null);
const item = array[index];
const car = Array.isArray(item) ? yield loop(item, 0) : item;
const cdr = yield loop(array, index + 1);
return pure(new Pair(car, cdr));
});
return evaluate(loop(array, 0));
}
duplicates() {
const visited = new WeakSet();
const result = new WeakSet();
const loop = pair => monadic(function* () {
if (visited.has(pair)) {
result.add(pair);
} else {
visited.add(pair);
const { car, cdr } = pair;
if (car instanceof Pair) yield loop(car);
if (cdr instanceof Pair) yield loop(cdr);
}
return pure(result);
});
return evaluate(loop(this));
}
toString() {
let result = "";
const duplicates = this.duplicates();
const visited = [];
const loop = (pair, end) => monadic(function* () {
const index = visited.indexOf(pair);
if (index < 0) {
const duplicate = duplicates.has(pair);
if (duplicate) {
const last = visited.push(pair) - 1;
result += end ? ` . #${last}=(` : `#${last}=(`;
} else result += end ? " " : "(";
const { car, cdr } = pair;
if (car instanceof Pair) yield loop(car, false);
else result += JSON.stringify(car);
if (cdr instanceof Pair) yield loop(cdr, true);
else if (cdr === null) result += ")";
else result += ` . ${JSON.stringify(cdr)})`;
if (duplicate && end) result += ")";
} else {
result += end ? ` . #${index}#)` : `#${index}#`;
}
return pure(result);
});
return evaluate(loop(this, false));
}
}
const data = new Pair(1, new Pair(new Pair(2, null), new Pair(3, null)));
data.cdr.car.cdr = data.cdr;
data.cdr.cdr.cdr = data;
console.log(data.toString());
const range = length => Array.from({ length }, (x, i) => i);
console.log(Pair.fromArray([[[range(8000), range(10)]]]).toString());
希望对您有所帮助。
我对 lisp 列表进行字符串化的基于蹦床的函数有问题。这是代码:
function Pair(car, cdr) {
this.car = car;
this.cdr = cdr;
}
const nil = new function Nil() {};
// ----------------------------------------------------------------------
Pair.fromArray = function(array) {
var result = nil;
var i = array.length;
while (i--) {
let car = array[i];
if (car instanceof Array) {
car = Pair.fromArray(car);
}
result = new Pair(car, result);
}
return result;
};
// ----------------------------------------------------------------------
function Thunk(fn, cont = () => {}) {
this.fn = fn;
this.cont = cont;
}
// ----------------------------------------------------------------------
Thunk.prototype.toString = function() {
return '#<Thunk>';
};
// ----------------------------------------------------------------------
function trampoline(fn) {
return function(...args) {
return unwind(fn.apply(this, args));
};
}
// ----------------------------------------------------------------------
function unwind(result) {
while (result instanceof Thunk) {
const thunk = result;
result = result.fn();
if (!(result instanceof Thunk)) {
thunk.cont();
}
}
return result;
}
// ----------------------------------------------------------------------
// original function have different data types here
// with simplified version this is fine
function toString(x) {
return x.toString();
}
// ----------------------------------------------------------------------
const pair_to_string = (function() {
const prefix = (pair, rest) => {
var result = [];
if (pair.ref) {
result.push(pair.ref + '(');
} else if (!rest) {
result.push('(');
}
return result;
};
const postfix = (pair, rest) => {
if (!rest || pair.ref) {
return [')'];
}
return [];
};
return trampoline(function pairToString(pair, quote, extra = {}) {
const {
nested,
result = [],
cont = () => {
result.push(...postfix(pair, nested));
}
} = extra;
result.push(...prefix(pair, nested));
let car;
if (pair.cycles && pair.cycles.car) {
car = pair.cycles.car;
} else {
car = toString(pair.car, quote, true, { nested: false, result, cont });
}
if (car !== undefined) {
result.push(car);
}
return new Thunk(() => {
if (pair.cdr instanceof Pair) {
if (pair.cycles && pair.cycles.cdr) {
result.push(' . ');
result.push(pair.cycles.cdr);
} else {
if (pair.cdr.ref) {
result.push(' . ');
} else {
result.push(' ');
}
return pairToString(pair.cdr, quote, {
nested: true,
result,
cont
});
}
} else if (pair.cdr !== nil) {
result.push(' . ');
result.push(toString(pair.cdr, quote));
}
}, cont);
});
})();
// ----------------------------------------------------------------------
Pair.prototype.toString = function(quote) {
var result = [];
pair_to_string(this, quote, {result});
return result.join('');
};
// ----------------------------------------------------------------------
function range(n) {
return new Array(n).fill(0).map((_, i) => i);
}
// ----------------------------------------------------------------------------
function markCycles(pair) {
var seen_pairs = [];
var cycles = [];
var refs = [];
function visit(pair) {
if (!seen_pairs.includes(pair)) {
seen_pairs.push(pair);
}
}
function set(node, type, child, parents) {
if (child instanceof Pair) {
if (parents.includes(child)) {
if (!refs.includes(child)) {
refs.push(child);
}
if (!node.cycles) {
node.cycles = {};
}
node.cycles[type] = child;
if (!cycles.includes(node)) {
cycles.push(node);
}
return true;
}
}
}
const detect = trampoline(function detect_thunk(pair, parents) {
if (pair instanceof Pair) {
delete pair.ref;
delete pair.cycles;
visit(pair);
parents.push(pair);
var car = set(pair, 'car', pair.car, parents);
var cdr = set(pair, 'cdr', pair.cdr, parents);
var thunks = [];
if (!car) {
detect(pair.car, parents.slice());
}
if (!cdr) {
const cdr_args = [pair.cdr, parents.slice()];
return new Thunk(() => {
return detect_thunk(...cdr_args);
});
}
}
});
function mark_node(node, type) {
if (node.cycles[type] instanceof Pair) {
const count = ref_nodes.indexOf(node.cycles[type]);
node.cycles[type] = `#${count}#`;
}
}
detect(pair, []);
var ref_nodes = seen_pairs.filter(node => refs.includes(node));
ref_nodes.forEach((node, i) => {
node.ref = `#${i}=`;
});
cycles.forEach(node => {
mark_node(node, 'car');
mark_node(node, 'cdr');
});
}
// ----------------------------------------------------------------------
// this works fine
//console.log(Pair.fromArray([[[range(8000), range(10)]]]).toString());
var data = new Pair(1, new Pair(new Pair(2, nil), new Pair(3, nil)));
data.cdr.car.cdr = data.cdr;
data.cdr.cdr.cdr = data;
markCycles(data)
console.log(data.toString());
console.log("#0=(1 . #1=((2 . #1#) 3 . #0#)) - valid");
问题是最后一个括号丢失了,我不确定我应该如何在 trampoline 中使用 continuation 来解决这个问题。
这是在没有蹦床的情况下工作的代码:
// ----------------------------------------------------------------------
Pair.prototype.toString = function(quote, rest) {
var arr = [];
if (this.ref) {
arr.push(this.ref + '(');
} else if (!rest) {
arr.push('(');
}
var value;
if (this.cycles && this.cycles.car) {
value = this.cycles.car;
} else {
value = toString(this.car, quote, true);
}
if (value !== undefined) {
arr.push(value);
}
if (this.cdr instanceof Pair) {
if (this.cycles && this.cycles.cdr) {
arr.push(' . ');
arr.push(this.cycles.cdr);
} else {
if (this.cdr.ref) {
arr.push(' . ');
} else {
arr.push(' ');
}
const cdr = this.cdr.toString(quote, true);
arr.push(cdr);
}
} else if (this.cdr !== nil) {
arr = arr.concat([' . ', toString(this.cdr, quote, true)]);
}
if (!rest || this.ref) {
arr.push(')');
}
return arr.join('');
};
我有两种情况,第一种是大列表(8000 个元素),另一种是小循环。使用堆栈片段中的代码,它可以处理长列表,但不能处理没有蹦床的循环,它会溢出大列表上的堆栈。它也是 lisp,所以它需要处理任何树,而不仅仅是链表。
编辑:如果您尝试回答,请至少不要更改数据结构。它需要与 car 和 cdr 配对 class,并且在将它们转换为字符串之前需要计算周期。所以它与多个函数一起工作,检查内存中的数据是否是循环的。
这就是我要做的。
const pure = value => ({ constructor: pure, value });
const bind = (monad, arrow) => ({ constructor: bind, monad, arrow });
const thunk = eval => ({ constructor: thunk, eval });
function evaluate(expression) {
let expr = expression;
let stack = null;
while (true) {
switch (expr.constructor) {
case pure:
if (stack === null) return expr.value;
expr = stack.arrow(expr.value);
stack = stack.stack;
break;
case bind:
stack = { arrow: expr.arrow, stack };
expr = expr.monad;
break;
case thunk:
expr = expr.eval();
}
}
}
const monadic = func => thunk(() => {
const gen = func();
function next(data) {
const { value, done } = gen.next(data);
return done ? value : bind(value, next);
}
return next(undefined);
});
class Pair {
constructor(car, cdr) {
this.car = car;
this.cdr = cdr;
}
static fromArray(array) {
const loop = (array, index) => monadic(function* () {
if (index === array.length) return pure(null);
const item = array[index];
const car = Array.isArray(item) ? yield loop(item, 0) : item;
const cdr = yield loop(array, index + 1);
return pure(new Pair(car, cdr));
});
return evaluate(loop(array, 0));
}
duplicates() {
const visited = new WeakSet();
const result = new WeakSet();
const loop = pair => monadic(function* () {
if (visited.has(pair)) {
result.add(pair);
} else {
visited.add(pair);
const { car, cdr } = pair;
if (car instanceof Pair) yield loop(car);
if (cdr instanceof Pair) yield loop(cdr);
}
return pure(result);
});
return evaluate(loop(this));
}
toString() {
let result = "";
const duplicates = this.duplicates();
const visited = [];
const loop = (pair, end) => monadic(function* () {
const index = visited.indexOf(pair);
if (index < 0) {
const duplicate = duplicates.has(pair);
if (duplicate) {
const last = visited.push(pair) - 1;
result += end ? ` . #${last}=(` : `#${last}=(`;
} else result += end ? " " : "(";
const { car, cdr } = pair;
if (car instanceof Pair) yield loop(car, false);
else result += JSON.stringify(car);
if (cdr instanceof Pair) yield loop(cdr, true);
else if (cdr === null) result += ")";
else result += ` . ${JSON.stringify(cdr)})`;
if (duplicate && end) result += ")";
} else {
result += end ? ` . #${index}#)` : `#${index}#`;
}
return pure(result);
});
return evaluate(loop(this, false));
}
}
const data = new Pair(1, new Pair(new Pair(2, null), new Pair(3, null)));
data.cdr.car.cdr = data.cdr;
data.cdr.cdr.cdr = data;
console.log(data.toString());
const range = length => Array.from({ length }, (x, i) => i);
console.log(Pair.fromArray([[[range(8000), range(10)]]]).toString());
希望对您有所帮助。