IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 关于luckysheet公式 TRANSPOSE 的问题 -> 正文阅读

[游戏开发]关于luckysheet公式 TRANSPOSE 的问题

最近一个同学反馈,导入了一个带有公式的excel表以后,公式有部分不能生效,因为我们之前发现导入的Excel后,确实有这样子的问题,所以首先需要确认的是本身在luckysheet上做的公式是否是支持的。结果确认后发现本身也不行,所以问题已经确定是在luckysheet上,而不是excel导入的问题了。

分析

我们先来简化的看下,到底是什么样的公式是存在问题的,这里面其实涉及到的矩阵的转置。

在这里插入图片描述

首先我们看上图,上图的逻辑就是把横向的矩阵数据转置成了纵向,所以一旦源矩阵的数据如果发生变化的话,对应转置举证的数据也会发生变化。

下来我们增加新的引用关系, 如下图:

在这里插入图片描述

在某个单元格中去引用转置后的举证的其中一个单元格, 所以理论上我们希望会得到这样子的一个结果,就是一旦我修改了原单元格中的矩阵数据,相应转置的矩阵数据也会发生变化,同时引用了转置举证的单元格数据也要随着去更新。但是实际的结果是并没有。我们看下最后的结果现象

在这里插入图片描述

从上图就可以看出来更新了原矩阵的值后,下方选中的单元格数据并没有发生变化。遇到这样子的情况我们首先需要先确认一个事情即这个问题是因为举证转置导致的 还是本身引用的引用导致的这个问题。

所以我们重新做一个验证,如下图:

在这里插入图片描述

有如上的三者之前的关系,验证发现,通过修改原数据以后,选中的单元格数据其实是会发生更新的,所以我们大体可以确认一个问题即这个问题可能更多是跟矩阵转置有关系。

那现在我们就要排查下,问题出在哪个地方了。

首先我们得找到入口的地方,看了下代码逻辑后,终于发现了,在更新某个单元格后,如果该单元格只是纯文本的更新 没有其他公式引用的话,就会调用到 execFunctionGroup 方法。

在这里插入图片描述

所以我们需要看下 execFunctionGroup 他的具体逻辑是如何的。

这块的逻辑其实比较复杂 我们找到其中重点一个地方来看下

//形成一个公式之间引用的图结构
  Object.keys(formulaObjects).forEach((key) => {
      let formulaObject = formulaObjects[key];
      arrayMatch(formulaObject.formulaArray, formulaObjects, updateValueOjects, function (childKey) {
          if (childKey in formulaObjects) {
              let childFormulaObject = formulaObjects[childKey];
              formulaObject.chidren[childKey] = 1;
              childFormulaObject.parents[key] = 1;
          }
          // console.log(childKey,formulaObject.formulaArray);
          if (!isForce && childKey in updateValueOjects) {
              updateValueArray.push(formulaObject);
          }
      });

      if (isForce) {
          updateValueArray.push(formulaObject);
      }
  });

在这里插入图片描述

在这里插入图片描述

从这个地方的调试发现,经过公式引用图那块的逻辑处理后,我们的 I4 那块的方法就被过滤掉了。所以问题很大概率就出现在那块了。我们详细看下这块的逻辑吧。

我们发现那块的逻辑又调用到了 arrayMatch 这个方法。

let arrayMatch = function (formulaArray, formulaObjects, updateValueOjects, func) {
    for (let a = 0; a < formulaArray.length; a++) {
        let range = formulaArray[a];
        let cacheKey = "r" + range.row[0] + "" + range.row[1] + "c" + range.column[0] + "" + range.column[1] + "index" + range.sheetIndex;
        if (cacheKey in arrayMatchCache) {
            let amc = arrayMatchCache[cacheKey];
            // console.log(amc);
            amc.forEach((item) => {
                func(item.key, item.r, item.c, item.sheetIndex);
            });
        }
        else {
            let functionArr = [];
            for (let r = range.row[0]; r <= range.row[1]; r++) {
                for (let c = range.column[0]; c <= range.column[1]; c++) {
                    let key = "r" + r + "c" + c + "i" + range.sheetIndex;
                    func(key, r, c, range.sheetIndex);
                    if ((formulaObjects && key in formulaObjects) || (updateValueOjects && key in updateValueOjects)) {
                        functionArr.push({
                            key: key,
                            r: r,
                            c: c,
                            sheetIndex: range.sheetIndex
                        });
                    }
                }
            }

            if (formulaObjects || updateValueOjects) {
                arrayMatchCache[cacheKey] = functionArr;
            }
        }
    }
}

在这里插入图片描述

我们发现这里会将每个公式的formulaArray 的区域都取出来,然后判断是否该区域内的节点是否在公式列表中,如果在的话,说明其实是存在有引用的引用的关系的。这个时候其实会在对应节点的parents中将key做赋值的操作的, 但是现在的问题就出现 我们上面截图的地方,r3c8i1 并没有出现在formulaObjects 中。因为他只有我们刚才截图中的r2c7i1 以及 r7c7i1 这两个,所以这里也就是为什么我们新的一个单元格的数据没有被更新的原因了,因为它根本就没有被计算进来。

解决

其实我们希望我们的 r3c8i1 能够被算到 r2c7i1 中因为它本身就是属于这个矩阵转置中的一部分。

我们又观察了下转置的矩阵的单元格,发现我选中其中一个单元格的时候其实他是有方法能够识别出来整个矩阵的区间的,如果是这样子的话 我是不是可以通过我这个节点,找到他真正的具有公式的单元格,再来就能够满足前面分析说的 r3c8i1 能够被算到 r2c7i1 这样子的情况了。

认真又看了下代码,还真的找到了这样子的一个逻辑,即

dynamicArray.js

//点击表格区域是否属于动态数组区域
function dynamicArrayHightShow(r, c) {
    let dynamicArray = Store.luckysheetfile[getSheetIndex(Store.currentSheetIndex)]["dynamicArray"] == null ? [] : Store.luckysheetfile[getSheetIndex(Store.currentSheetIndex)]["dynamicArray"];
    let dynamicArray_compute = dynamicArrayCompute(dynamicArray);
    ....

其实我们要的就是这个 dynamicArray_compute 这个值是什么,我们来放进去看下

在这里插入图片描述

我们得到了这样子的一个结果,这就有点思路了,因为我们刚才说过 我们没办法讲 r3c8i1 映射到 r2c7i1 现在通过这个对象就可以计算到了呀, 因为key中的r以及c实际上就是真正带有公式的单元格。

所以我们需要改造下前面 execFunctionGroup 说到的那块逻辑

//形成一个公式之间引用的图结构
Object.keys(formulaObjects).forEach((key) => {
    let formulaObject = formulaObjects[key];
    arrayMatch(formulaObject.formulaArray, formulaObjects, updateValueOjects, function (childKey, r, c, sheetIndex) {
        // 判断这个节点是否是在动态的数组计算区域,如果是的话,就将childkey修改为真正的动态数组的key
        if (childKey in formulaObjects || ((r + "_" + c) in dynamicArray_compute && ("r" + dynamicArray_compute[(r + "_" + c)].r + "c" + dynamicArray_compute[(r + "_" + c)].c + "i" + sheetIndex) in formulaObjects)) {
            if ((r + "_" + c) in dynamicArray_compute) {
                childKey = "r" + dynamicArray_compute[(r + "_" + c)].r + "c" + dynamicArray_compute[(r + "_" + c)].c + "i" + sheetIndex;
            }
            let childFormulaObject = formulaObjects[childKey];
            formulaObject.chidren[childKey] = 1;
            childFormulaObject.parents[key] = 1;
        }
        // console.log(childKey,formulaObject.formulaArray);
        if (!isForce && childKey in updateValueOjects) {
            updateValueArray.push(formulaObject);
        }
    });

    if (isForce) {
        updateValueArray.push(formulaObject);
    }
});

如下我们再来验证下

在这里插入图片描述

现在数据是会发生变化了,但是更新竟然总是会延迟上一个数据。这个地方就很奇怪了。

所以我们只能重新断点跟下去看下什么情况了。最后发现最后的逻辑都进入到了

[execfunction](https://github.com/mengshukeji/Luckysheet/blob/master/src/global/formula.js#L5768) 这个方法中

execfunction: function (txt, r, c, index, isrefresh, notInsertFunc) {
        let _this = this;

        let _locale = locale();
        let locale_formulaMore = _locale.formulaMore;
        // console.log(txt,r,c)
        if (txt.indexOf(_this.error.r) > -1) {
            return [false, _this.error.r, txt];
        }

        if (!_this.checkBracketNum(txt)) {
            txt += ")";
        }

        if (index == null) {
            index = Store.currentSheetIndex;
        }

        Store.calculateSheetIndex = index;

        let fp = $.trim(_this.functionParserExe(txt));
        //console.log(fp)
        if ((fp.substr(0, 20) == "luckysheet_function." || fp.substr(0, 22) == "luckysheet_compareWith")) {
            _this.functionHTMLIndex = 0;
        }

        if (!_this.testFunction(txt, fp) || fp == "") {
            tooltip.info("", locale_formulaMore.execfunctionError);
            return [false, _this.error.n, txt];
        }

        let result = null;
        window.luckysheetCurrentRow = r;
        window.luckysheetCurrentColumn = c;
        window.luckysheetCurrentIndex = index;
        window.luckysheetCurrentFunction = txt;

        let sparklines = null;

        try {
						// 关键是这里的new Function的逻辑 
            ....
            result = new Function("return " + fp)();
            if (typeof (result) == "string") {//把之前的非打印控制字符DEL替换回一个双引号。
                result = result.replace(/\x7F/g, '"');
            }

            //加入sparklines的参数项目
            if (fp.indexOf("SPLINES") > -1) {
                sparklines = result;
                result = "";
            }
        }
        catch (e) {
            let err = e;
            //err错误提示处理
            console.log(e, fp);
            err = _this.errorInfo(err);
            result = [_this.error.n, err];
        }

        ....
        return [true, result, txt];
    }

我们看到前面的代码逻辑,关键的地方其实就是 result = new Function("return " + fp)(); 所以我们需要断点到这个看下I4的获取结果是什么。

在这里插入图片描述

确实这里的data 中的v 就是要更新的值,但是这个值其实是未更新前的值 并不是最新的值。 所以可能是矩阵转置结果更新慢导致了这样子的问题。

我们又得最后看下数据的更新逻辑是啥

let v = _this.execfunction(calc_funcStr, formulaCell.r, formulaCell.c, formulaCell.index);

_this.groupValuesRefreshData.push({
    "r": formulaCell.r,
    "c": formulaCell.c,
    "v": v[1],
    "f": v[2],
    "spe": v[3],
    "index": formulaCell.index
});

从上面我们发现这个结果的数据更新其实不是立即更新的 是每个单元格都计算完结果以后,丢到一个groupValuesRefreshData 数组中,最后统一去触发更新了,这里就不难理解 可能就会有我们刚才说的那个问题了,所以我们可以尝试去这样处理,一旦方法执行完以后,就立即去更新单元格的数据是不是更好点,所以我们尝试这么去改。

直接增加如下的逻辑,在execfunction

let v = _this.execfunction(calc_funcStr, formulaCell.r, formulaCell.c, formulaCell.index);

let item = {
    "r": formulaCell.r,
    "c": formulaCell.c,
    "v": v[1],
    "f": v[2],
    "spe": v[3],
    "index": formulaCell.index
}

_this.groupValuesRefreshData.push(item);
// _this.execFunctionGroupData[u.r][u.c] = value;
_this.execFunctionGlobalData[formulaCell.r + "_" + formulaCell.c + "_" + formulaCell.index] = {
    v: v[1],
    f: v[2]
};
console.log(_this.groupValuesRefreshData)

let luckysheetfile = getluckysheetfile();
let file = luckysheetfile[getSheetIndex(item.index)];
let data = file.data;
if (data == null) {
    continue;
}

let updateValue = {};
if (item.spe != null) {
    if (item.spe.type == "sparklines") {
        updateValue.spl = item.spe.data;
    }
    else if (item.spe.type == "dynamicArrayItem") {
        file.dynamicArray = _this.insertUpdateDynamicArray(item.spe.data);
    }
}
updateValue.v = item.v;
updateValue.f = item.f;
setcellvalue(item.r, item.c, data, updateValue);

我们看下最后的结果

在这里插入图片描述

如上就做到实时同步了,但是这个其实有个弊端的,因为本身作者统一更新的设计肯定是有一定道理的,也许是为了性能更优吧,所以这块的处理其实并不是那么的优雅,所以只是想到暂时这么解决这个问题。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-03-21 21:25:48  更:2022-03-21 21:27:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/16 18:52:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码