Office加载项:Excel深度优先搜索

Office加载项:Excel深度优先搜索,excel,typescript,office-addins,Excel,Typescript,Office Addins,tl;dr我在Excel工作表上执行递归函数时遇到问题。下面我将尽可能详细地介绍我的问题,但我希望能提供一个与我试图解决的特定域/问题无关的简单、有效的异步递归示例,因为这样可能更容易 问题背景 我正在尝试构建一个外接程序(任务窗格),以便在电子表格中线性化多变量分析(即假设分析)和约束求解。我的假设是,电子表格固有的二维网格不会随着变量数量的增加而扩展。我想尝试通过一种集成的查询语言来展示这个功能 因为Excel不允许我使用它的解释器,所以我需要将公式链复制到看不见的单元格中,在那里运行我的计

tl;dr我在Excel工作表上执行递归函数时遇到问题。下面我将尽可能详细地介绍我的问题,但我希望能提供一个与我试图解决的特定域/问题无关的简单、有效的异步递归示例,因为这样可能更容易

问题背景

我正在尝试构建一个外接程序(任务窗格),以便在电子表格中线性化多变量分析(即假设分析)和约束求解。我的假设是,电子表格固有的二维网格不会随着变量数量的增加而扩展。我想尝试通过一种集成的查询语言来展示这个功能

因为Excel不允许我使用它的解释器,所以我需要将公式链复制到看不见的单元格中,在那里运行我的计算,然后将结果复制到用户想要的地方

这需要我递归地探索有问题的公式,并复制可能受有问题的变量影响的所有嵌套公式

例如,如果我有以下结构

A7 -> IF(A5>0, A4, A3)
A5 -> A1 + A2
A4 -> A1 + 10
A3 -> A2 + 10
A1 -> 10 
A2 -> 20
我从目标公式开始,探索公式并将其复制到新位置。当我复制它们时,我构建了一个新的环境,告诉我应该去哪里看。i、 e

IF(A5>0, A4, A3) 
  -- A5
     -- A1 
     -- A2 
   copy(A5, copyTo = B5, env(A5 -> B5)) 
  -- A4 
     -- A1
   copy(A4, copyTo = B4, env(A5 -> B5, A4 -> B4))  
  -- A3 
     -- A2 
   copy(A3, copyTo = B3, env(A5-> B5, A4-> B4))
new_formula = replaceCells(A7, env) // this will return  IF(B5>0, B4, B3)
copy(A7, copyTo=B7, env)
So tl;dr是深度优先搜索+环境累积,这通常非常简单

实际问题

然而,我很难理解Officejs的并发模型。由于每个递归调用都是异步执行的,因此环境的变化就成了问题(虽然这里没有任何竞争条件,但在执行上述调用时,需要存在来自每个深度的绑定,以允许正确的重写)

不幸的是,当我第一次调用这个函数返回时,很多绑定都丢失了。我尝试用
async/await
Promise
API来解决这个问题,但都没有结果。正确的方法是什么?(我100%确信这是一个并发问题。这不仅有意义,而且这段代码在Google AppScript中也能工作,因为在Google AppScript中,并发模型的限制要大得多)

另外,您可能会问,“如果希望按顺序执行,为什么它是异步的?”->这是因为我需要在目标单元格中加载公式,这需要我同步上下文

附言2:我也读过,但似乎找不到解决的办法

async function exploreAndCopy(
    context: Excel.RequestContext, 
    currentWorksheet : Excel.Worksheet, 
    source: string,
    target : string,
    row : string,
    column : number,
    env: Map<string, string>) {

  let rx = currentWorksheet.getRange(target);
  rx.load()
  await context.sync();
  let cellFormulas = rx.formulas;

  if (cellFormulas.length > 0){
    var f : string = cellFormulas[0][0]
    var newFormula = f.replace(source, "A29")
    
    //Get the referenced cells 
    let cells : string[] = newFormula.split(/[^A-Za-z0-9]/)
    cells = cells.filter(s => s.match(/^(?=.*[a-zA-Z])(?=.*[0-9])/));

    //remove dublicates  
    cells = cells.filter((item, index) => cells.indexOf(item) === index);


    for(var c of cells){

        // ATTEMP #1
        await exploreAndCopy(context, currentWorksheet, source, c, row, column + 1 + cells.indexOf(c), env)

       // ATTEMPT #2 
       exploreAndCopy(context, currentWorksheet, source, c, row, column + 1 + cells.indexOf(c), env).then( p => 
        env.forEach((value: string, key: string) => {
          newFormula = newFormula.replace(key, value)
        })
      ); // will mutate the env


      // Also tried Promises.all 
      await context.sync();
    }
  }
  await context.sync();

  //replace everything in the map 
  var finalFormula = newFormula
  env.forEach((value: string, key: string) => {
      finalFormula = finalFormula.replace(key, value);

  });
  
  //set env
  
  env.set(target, row + column)
  //override    
  var targetCell = currentWorksheet.getRange(row + column)
  targetCell.formulas = [[finalFormula]]
} 
异步函数exploreAndCopy(
上下文:Excel.RequestContext,
当前工作表:Excel.Worksheet,
资料来源:string,
目标:字符串,
行:字符串,
列:编号,
环境:地图){
设rx=currentWorksheet.getRange(目标);
rx.load()
wait context.sync();
设cellFormulas=rx.formulas;
如果(cellFormulas.length>0){
变量f:string=cellFormulas[0][0]
var newFormula=f.replace(来源,“A29”)
//获取引用的单元格
let单元格:字符串[]=newFormula.split(/[^A-Za-z0-9]/)
cells=cells.filter(s=>s.match(/^(?=.[a-zA-Z])(?=.[0-9])/);
//删除多个字母
cells=cells.filter((项,索引)=>cells.indexOf(项)==索引);
for(单元格的var c){
//尝试#1
等待exploreAndCopy(上下文、当前工作表、源、c、行、列+1+单元格。索引(c)、环境)
//尝试#2
exploreAndCopy(上下文、当前工作表、源、c、行、列+1+单元格。索引(c)、环境)。然后(p=>
环境forEach((值:字符串,键:字符串)=>{
newFormula=newFormula.replace(键,值)
})
);//将使环境发生变异
//我也试过了
wait context.sync();
}
}
wait context.sync();
//替换地图上的所有内容
var最终公式=新公式
环境forEach((值:字符串,键:字符串)=>{
finalFormula=finalFormula.replace(键,值);
});
//设置环境
环境集合(目标,行+列)
//凌驾
var targetCell=currentWorksheet.getRange(行+列)
targetCell.formulas=[[finalFormula]]
} 

感谢您提供的任何建议

使用COM API可能更容易做到这一点,COM API具有用于执行公式字符串的求值方法,可以跟踪先例,并且是同步的。(我多年来一直要求在Office JS中进行求值)。您可以使用VBA(XLAM addin)或使用VSTO或XLDNA通过COM互操作使用.NET语言。使用VBA,您的UI将是一个用户窗体(VBA无法访问任务窗格)@charleswillams谢谢你的推荐。COM似乎需要安装特殊工作负载的visual studio发行版,我似乎找不到。你能给我指出正确的方向吗?谷歌“安装VSTO”或查看Excel DNA