使用蹦床从嵌套数组创建树并将其隐式转换为字符串

如何解决使用蹦床从嵌套数组创建树并将其隐式转换为字符串

我想用蹦床用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.fromArraytrampoline(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中了解有关这种技术的更多信息。要查看以其他方式演示的LoopRecur,请参见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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-