如何解决使用蹦床从嵌套数组创建树并将其隐式转换为字符串
我想用蹦床用JavaScript重写Lisp中的所有递归函数。我有两个我不知道如何重写的示例:
Pair.fromArray = trampoline(function fromArray(array) {
if (array.length === 0) {
return nil;
} else {
return new Thunk(() => {
var car;
if (array[0] instanceof Array) {
car = Pair.fromArray(array[0]);
} else {
car = array[0];
}
if (typeof car === 'string') {
car = LString(car);
}
if (array.length === 1) {
return new Pair(car,nil);
} else {
// this overload the stack
return new Pair(car,Pair.fromArray(array.slice(1)));
}
});
}
});
// ----------------------------------------------------------------------
var pair_to_string = trampoline(function pair_to_string(pair,rest) {
var arr = [];
if (!rest) {
arr.push('(');
}
var value = toString(pair.car,true);
if (value !== undefined) {
arr.push(value);
}
return new Thunk(() => {
if (pair.cdr instanceof Pair) {
arr.push(' ');
const cdr = pair_to_string(pair.cdr,true);
arr.push(cdr);
} else if (pair.cdr !== nil) {
arr = arr.concat([' . ',toString(pair.cdr,true)]);
}
if (!rest) {
arr.push(')');
}
return arr.join('');
});
});
// ----------------------------------------------------------------------
Pair.prototype.toString = function(quote,rest) {
return pair_to_string(this,quote,rest);
};
这是蹦床代码:
function Thunk(fn) {
this.fn = fn;
}
// ----------------------------------------------------------------------
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) {
result = result.fn();
}
return result;
}
问题在于它不起作用,如果将trampoliline调用返回的值称为trampolined函数,它将使堆栈超载,而当我调用命名函数表达式时,得到:(1 #<Thunk>)
。我尝试放入unwind(pair_to_string(pair.cdr,true))
,但这也会使堆栈超载。
此函数可以用蹦床编写,以便将lisp列表转换为字符串吗?
这是具有这两个功能的堆栈片段。我知道我需要编写看起来像尾部递归但返回Thunk的函数,但是如果我在创建表达式的过程中该怎么做。
// ----------------------------------------------------------------------
function Thunk(fn) {
this.fn = fn;
}
// ----------------------------------------------------------------------
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) {
result = result.fn();
}
return result;
}
// ----------------------------------------------------------------------
function toString(obj,...arg) {
if (obj instanceof Pair) {
return obj.toString(...arg);
}
return obj.toString();
}
// ----------------------------------------------------------------------
function Pair(car,cdr) {
this.cdr = cdr;
this.car = car;
}
// ----------------------------------------------------------------------
var nil = new function Nil() {};
// ----------------------------------------------------------------------
Pair.fromArray = trampoline(function fromArray(array) {
if (array.length === 0) {
return nil;
} else {
var car;
if (array[0] instanceof Array) {
car = Pair.fromArray(array[0]);
} else {
car = array[0];
}
if (array.length === 1) {
return new Pair(car,nil);
} else {
return new Pair(car,Pair.fromArray(array.slice(1)));
}
}
});
// ----------------------------------------------------------------------
var pair_to_string = function pair_to_string(pair,true);
if (value !== undefined) {
arr.push(value);
}
if (pair.cdr instanceof Pair) {
arr.push(' ');
const cdr = pair_to_string(pair.cdr,true);
arr.push(cdr);
} else if (pair.cdr !== nil) {
arr = arr.concat([' . ',true)]);
}
if (!rest) {
arr.push(')');
}
return arr.join('');
};
// ----------------------------------------------------------------------
Pair.prototype.toString = function(rest) {
return pair_to_string(this,rest);
};
// ----------------------------------------------------------------------
function range(n) {
const range = new Array(n).fill(0).map((_,i) => i);
return Pair.fromArray(range);
}
// ----------------------------------------------------------------------
console.log(range(40).toString());
var l = range(8000);
console.log(l.toString());
注意::以上代码重构了原始功能,没有任何蹦床(包括但不使用蹦床代码)。
PS:我对其他解决方案也很满意,该解决方案允许遍历二叉树而无需使用递归并消耗堆栈。如果您不能使用蹦床遍历二叉树,我也很好地解释了为什么不可能做到这一点。但更喜欢实际的解决方案。
解决方法
嵌套蹦床
您已突出显示该问题-
Pair.fromArray = trampoline(function(array) {
if ...
return ...
else
return new Thunk(() => {
if ...
...
else
return new Pair(car,Pair.fromArray(array.slice(1))) // !
})
}
});
Pair.fromArray
是trampoline(function() { ... })
,对Pair.fromArray
的递归调用创建了一个嵌套进程-
Pair.fromArray([1,2,3])
unwind(fn.apply(this,[1,3]))
unwind(new Thunk(() => ...))
unwind(new Pair(1,Pair.fromArray([2,3])))
unwind(new Pair(1,unwind(fn.apply(this,[2,3]))))
unwind(new Pair(1,unwind(new Thunk(() => ...))))
unwind(new Pair(1,unwind(new Pair(2,Pair.fromArray([3])))))
unwind(new Pair(1,[3]))))))
unwind(new Pair(1,unwind(new Thunk(() => ...))))))
unwind(new Pair(1,unwind(new Pair(3,nil))))))
unwind(new Pair(1,new Pair(3,nil)))))
unwind(new Pair(1,new Pair(2,nil))))
new Pair(1,nil)))
如您所见,每个元素嵌套unwind
的另一帧,直到遇到输入数组的末尾,该帧才能关闭。
可能的实施方式
让我们概述一个小程序,以创建一个Lisp样式的100个元素列表-
import { fromArray,toString } from "./List"
console.log(toString(fromArray([[1,3],4,[],5]])))
console.log(toString(fromArray(Array.from(Array(100),(_,v) => v))))
预期输出-
((1 (2 3) 4 () 5))
(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100)
我们可以用任何方式写List
。这是一种方法-
// List.js
import { Pair } from "./Pair"
import { Loop,Recur } from "./TailRec"
function push(r,v)
{ r.push(v)
return r
}
const Nil =
Symbol()
const fromArray = (a = []) =>
Loop(function(r = Nil,i = 0) {
if (i >= a.length)
return reverse(r)
else if (Array.isArray(a[i]))
return Recur(Pair(fromArray(a[i]),r),i + 1)
else
return Recur(Pair(a[i],i + 1)
})
const toString = (t = Nil) =>
Loop(function(r = [],m = t) {
if (m === Nil)
return "(" + r.join(" ") + ")"
else if (m.car === Nil || m.car instanceof Pair)
return Recur(push(r,toString(m.car)),m.cdr)
else
return Recur(push(r,m.car),m.cdr)
})
const reverse = (t = Nil) =>
Loop(function(r = Nil,m = t) {
if (m === Nil)
return r
else
return Recur(Pair(m.car,m.cdr)
})
export { fromArray,reverse,toString }
这是Pair
模块-
// Pair.js
function Pair (car,cdr)
{ return Object.create
( Pair.prototype,{ constructor: { value: Pair },car: { value: car },cdr: { value: cdr }
}
)
}
export { Pair }
还有TailRec
模块-
// TailRec.js
function Loop (f,...init)
{ let r = f(...init)
while (r instanceof Recur)
r = f(...r)
return r
}
function Recur (...v)
{ return Object.create
( Recur.prototype,{ constructor: { value: Recur },[Symbol.iterator]: { value: _ => v.values() }
}
)
}
export { Loop,Recur }
运行下面的代码片段,以构建包含 100,000 元素的Lisp样式列表-
function push(r,v)
{ r.push(v)
return r
}
function Pair (car,cdr: { value: cdr }
}
)
}
function Loop (f,[Symbol.iterator]: { value: _ => v.values() }
}
)
}
const Nil =
Symbol()
const fromArray = (a = []) =>
Loop(function(r = Nil,m.cdr)
})
console.log(toString(fromArray([[1,[]]])))
console.log(toString(fromArray(Array.from(Array(100000),v) => v))))
继续阅读...
如您所见,这适用于巨大的列表,但是如果您需要创建一个非常深的嵌套列表,仍然有可能使堆栈崩溃。您可以使任何递归程序堆栈安全的 都不需要更改您的想法。在this Q&A中了解有关这种技术的更多信息。要查看以其他方式演示的Loop
和Recur
,请参见these Q&As。
健壮,如Lisp
如前所述,在深层嵌套的数组上调用fromArray
时,以上代码将使堆栈溢出。如果toString
尝试将深度嵌套的列表转换为字符串,则同样如此。 Lisp列表没有这些限制,所以让我们进行一些改进-
// Main.js
import { fromArray,toString } from "./List"
// create a deeply nested list!
let r = ["hi"]
for (let i = 0; i < 20000; i++)
r = [r]
console.log(toString(fromArray(r)))
我们希望看到(...)
嵌套了20,000个级别-
((((((((...((((((((hi))))))))...))))))))
这次,我们将使用更复杂的List
模块来实现TailRec
模块-
// List.js
import { loop,recur,call } from "./TailRec"
import { pair,isPair } from "./Pair"
const nil =
Symbol("nil")
const isNil = t =>
t === nil
const isList = t =>
isNil(t) || isPair(t)
const push = (r,v) =>
(r.push(v),r)
const fromArray = (a = []) =>
loop
( ( m = a,i = 0
) =>
i >= m.length
? nil
: call
( pair,Array.isArray(m[i])
? recur(m[i],0)
: m[i],recur(m,i + 1)
)
)
const toString = (t = nil) =>
loop
( ( m = t,r = []
) =>
isNil(m)
? "(" + r.join(" ") + ")"
: recur
( m.cdr,call
( push,r,isList(m.car)
? recur(m.car,[])
: String(m.car)
)
)
)
export { fromArray,toString }
采用this Q&A中介绍的技术,我们实现了TailRec
模块。请务必注意,这可以解决您的特定问题,而无需更改原始模块-
// TailRec.js
const call = (f,...values) => ...
const recur = (...values) => ...
const loop = f => ...
const run = r => ...
export { loop,call }
最后是用于构建列表的Pair
模块-
// Pair.js
class Pair
{ constructor (car,cdr)
{ this.car = car
; this.cdr = cdr
}
}
const pair = (car,cdr) =>
new Pair(car,cdr)
const isPair = t =>
t instanceof Pair
const toString = t =>
`(${t.car} . ${t.cdr})`
export { pair,isPair,toString }
展开下面的代码片段,以在浏览器中验证结果-
const call = (f,...values) =>
({ type: call,f,values })
const recur = (...values) =>
({ type: recur,values })
const identity = x =>
x
const loop = f =>
{ const aux1 = (expr = {},k = identity) =>
expr.type === recur
? call (aux,expr.values,values => call (aux1,f (...values),k))
: expr.type === call
? call (aux,expr.f (...values),k))
: call (k,expr)
const aux = (exprs = [],k) =>
call
( exprs.reduce
( (mr,e) =>
k => call (mr,r => call (aux1,e,x => call (k,[ ...r,x ]))),k => call (k,[])
),k
)
return run (aux1 (f ()))
}
const run = r =>
{ while (r && r.type === call)
r = r.f (...r.values)
return r
}
class Pair
{ constructor (car,cdr)
const isPair = t =>
t instanceof Pair
const nil =
Symbol("nil")
const isNil = t =>
t === nil
const isList = t =>
isNil(t) || isPair(t)
const push = (r,[])
: String(m.car)
)
)
)
let a = ["hi"]
for (let i = 0; i < 20000; i++)
a = [a]
document.body.textContent = toString(fromArray(a))
// ((((((((...((((((((hi))))))))...))))))))
body { overflow-wrap: anywhere; }
,
这就是我要做的。
// range :: Number -> List Number
const range = n => enumFromTo(0,n - 1);
// enumFromTo :: (Number,Number) -> List Number
const enumFromTo = (m,n) => m > n ? null : {
head: m,get tail() {
return enumFromTo(m + 1,n);
}
};
// showList :: List Number -> String
const showList = xs => xs === null ? "()" :
`(${showListElements(xs.head,xs.tail)})`;
// (Number,List Number) -> String
const showListElements = (x,xs) => {
let string = `${x}`;
let variant = xs;
while (variant !== null) {
const { head,tail } = variant;
string = `${string} ${head}`;
variant = tail;
}
return string;
};
console.log(showList(range(40)));
console.log(showList(range(8000)));
如您所见,您根本不需要蹦床。您唯一需要做的就是懒惰。
您可能还会发现以下堆栈溢出线程很有趣。
How to encode corecursion/codata in a strictly evaluated setting?
,我能够使用递归为递归函数创建解决方案,这些函数需要在蹦床中递归后执行一些操作。如果在Thunk中没有继续,则无法以)作为列表的结尾。蹦床结束时需要调用该函数,但返回Thunk时需要指定代码。
当列表的嵌套非常复杂时,这可能会引发堆栈溢出错误,但是我认为对于我而言,这很好。它将与range(8000)一起使用,并适用于嵌套列表,所以很好。
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;
}
// ----------------------------------------------------------------------
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,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,{
nested: true,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,{result});
return result.join('');
};
// ----------------------------------------------------------------------
function range(n) {
return new Array(n).fill(0).map((_,i) => i);
}
// ----------------------------------------------------------------------
console.log(Pair.fromArray([[[range(8000),range(10)]]]).toString());
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。