Excel函数从没有VBA的值构造数组

Posted

技术标签:

【中文标题】Excel函数从没有VBA的值构造数组【英文标题】:Excel function to construct an array from values without VBA 【发布时间】:2021-11-18 00:16:02 【问题描述】:

NB 这个问题指的是一些特性,比如可选的 LAMBDA 参数和 ISOMITTED 函数,它们仅在 Beta 通道中可用(在撰写本文时)(更多信息here)


我试图在 Excel LAMBDA 函数中模仿 VBA 的参数数组,所以想要一个函数:

=ARRAY(arg_1, [arg_2], [arg_3], ...)

...返回数组arg_1, arg_2, arg_3, ...,根据传递的参数数量动态调整大小。

如果我知道 args 的数量,我可以像这样使用选择函数:

=CHOOSE(SEQUENCE(number_of_args), arg_1, arg_2, arg_3, ...))

但我不想将参数的数量作为参数传递,我希望它是动态的。一个想法是使用ISOMITTED(arg_n) 进行二进制搜索以找到第一个缺失的参数。但这仍然硬编码了我的 ARRAY 函数的 args 数量上限,更不用说创建硬编码的讨厌的二叉树了。


这当然很容易使用 vba:

Public Function ARRAYFROMARGS(ParamArray args()) As Variant
    ARRAYFROMARGS = args
End Function

虽然这只能接受可以强制转换为 Variants 的值,但不能接受 lambda 或链接数据类型。这使得非 VBA 版本更加灵活。

【问题讨论】:

输入是从哪里来的,它会是一个在字符串中包含参数的单元格吗?还是要对公式中的参数进行硬编码? @ScottCraner 没有硬编码,否则=1,2,3 有效。我想要=A1,A5,B4,"foo",Bar" 类型的东西。我希望避免手动解析字符串化版本,因为这需要转义所有参数,并且某些数据类型无法忠实地字符串化 @ScottCraner 哦,我想我误解了你的问题。我将像=PRINTF("Some text 1, more text 2", ARRAY("foo", A1)) 这样调用函数,其中ARRAY 接受可变数量的参数并将它们包装到一个数组中以传递给只需要两个参数的PRINTF;一个字符串掩码,以及一个动态的令牌数组,如this example function。这说明清楚了吗? 我明白了。我的研究表明 lambda 是不可能的。 Lambda 需要一定数量的输入。最后一个输入可以是一个数组,但它必须作为一个普通数组输入,这会带回创建数组的选择。我希望我是错的,并且会潜伏在这个问题上,看看是否有比我更了解的人。 @ScottCraner 好吧,我已经给出了蛮力方法,尽管它经过了一些优化以具有 O(log n) 而不是 O(n/2) 复杂性,因此它运行得非常快。现在你可以用任何东西做一个 ARRAY 了:) 但希望有人能在某处看到改进 【参考方案1】:

这不正是您要寻找的,但也许它会提供一些想法?如果我们可以一次取一个值而不是一个列表,那么我们可以使用转义字符 (\) 来让公式知道它不应该再期望值,而不是对参数进行限制,并使公式递归,并利用 MAKEARRAY 通过每个新条目扩展先前创建的数组。请注意,对于引用,第一个引用不能导致错误,因此必须是非空白的。

ARRAY
=LAMBDA(val_1,
    LAMBDA(val_2,
        IF(
            TYPE(val_2)=16,val_1,
            ARRAY(
                MAKEARRAY(ROWS(val_1)+1,COLUMNS(val_1),
                    LAMBDA(i,j,
                        IFERROR(INDEX(val_1,i,j),val_2)
                    )
                )
            )
        )
    )
)

【讨论】:

这很酷,部分答案/间接答案很好。所以如果 A7 是空白的,我们会得到一个错误,对吗?这是为什么?基本上这是什么意思:“注意,对于引用,第一个引用不能导致错误,所以需要是非空白的。” 为什么还要使用 TYPE() = 16 而不是 ISERROR? 抱歉,应该再测试几个案例,=ARRAY(3)(A7)(A8)(3)("foo")("bar")() 适用于 A7、A8空白,所以如果第一个条目是空白参考;我不太清楚为什么,因为 TYPE(blank ref)=1 而不是 16;一定是 Excel 计算引擎中发生的事情。而且 ISERROR 似乎工作得很好,只是个人对 TYPE 的偏好。 我已经对你的函数做了一个稍微修改的版本:=LAMBDA(accumulator,LAMBDA([next_val], IF(ISOMITTED(next_val), accumulator, ARRAYBUILDER(ARRAYJOIN(accumulator,next_val))))) 其中 ARRAYJOIN 被定义为 here 作为一个函数,它可以将其输入展平并将一个放在另一个的末尾,工作方式非常相似给你的。称为ARRAYBUILDER(2)(A1:A5)() 请注意末尾的() 以调用构建器 ...我想通过这种方法,您需要一个特殊的终止符值(对我来说是 IsOmitted,对你来说是 IsError)或一个特殊的延续值 - 例如ARRAYBUILDER(,1)(,2)(,3)(4) 错过第一个可选参数以继续。除了提供长度之外,我认为您无法通过其他方式获得,但是如果我们有,那么整个事情就没有实际意义了【参考方案2】:

正如我在问题中提到的那样,我找到了一种使用二进制搜索来评估缺少多少参数的方法。不过,它确实涉及很多复制粘贴。因此,这里的代码通过查找缺少的第一个来评估传递的 args 的数量:

Tag Value
Name ARGSCOUNT
Scope Workbook
Comment Use a hardcoded binary search to find the first omitted argument, in chunks of up to 63
Refers To =LAMBDA([p_1],[p_2],[p_3],[p_4],[p_5],[p_6],[p_7],[p_8],[p_9],[p_10],[p_11],[p_12],[p_13],[p_14],[p_15],[p_16],[p_17],[p_18],[p_19],[p_20],[p_21],[p_22],[p_23],[p_24],[p_25],[p_26],[p_27],[p_28],[p_29],[p_30],[p_31],[p_32],[p_33],[p_34],[p_35],[p_36],[p_37],[p_38],[p_39],[p_40],[p_41],[p_42],[p_43],[p_44],[p_45],[p_46],[p_47],[p_48],[p_49],[p_50],[p_51],[p_52],[p_53],[p_54],[p_55],[p_56],[p_57],[p_58],[p_59],[p_60],[p_61],[p_62],[p_63],IF(ISOMITTED(p_32),IF(ISOMITTED(p_16),IF(ISOMITTED(p_8),IF(ISOMITTED(p_4),IF(ISOMITTED(p_2),IF(ISOMITTED(p_1),0,1),IF(ISOMITTED(p_3),2,3)),IF(ISOMITTED(p_6),IF(ISOMITTED(p_5),4,5),IF(ISOMITTED(p_7),6,7))),IF(ISOMITTED(p_12),IF(ISOMITTED(p_10),IF(ISOMITTED(p_9),8,9),IF(ISOMITTED(p_11),10,11)),IF(ISOMITTED(p_14),IF(ISOMITTED(p_13),12,13),IF(ISOMITTED(p_15),14,15)))),IF(ISOMITTED(p_24),IF(ISOMITTED(p_20),IF(ISOMITTED(p_18),IF(ISOMITTED(p_17),16,17),IF(ISOMITTED(p_19),18,19)),IF(ISOMITTED(p_22),IF(ISOMITTED(p_21),20,21),IF(ISOMITTED(p_23),22,23))),IF(ISOMITTED(p_28),IF(ISOMITTED(p_26),IF(ISOMITTED(p_25),24,25),IF(ISOMITTED(p_27),26,27)),IF(ISOMITTED(p_30),IF(ISOMITTED(p_29),28,29),IF(ISOMITTED(p_31),30,31))))),IF(ISOMITTED(p_48),IF(ISOMITTED(p_40),IF(ISOMITTED(p_36),IF(ISOMITTED(p_34),IF(ISOMITTED(p_33),32,33),IF(ISOMITTED(p_35),34,35)),IF(ISOMITTED(p_38),IF(ISOMITTED(p_37),36,37),IF(ISOMITTED(p_39),38,39))),IF(ISOMITTED(p_44),IF(ISOMITTED(p_42),IF(ISOMITTED(p_41),40,41),IF(ISOMITTED(p_43),42,43)),IF(ISOMITTED(p_46),IF(ISOMITTED(p_45),44,45),IF(ISOMITTED(p_47),46,47)))),IF(ISOMITTED(p_56),IF(ISOMITTED(p_52),IF(ISOMITTED(p_50),IF(ISOMITTED(p_49),48,49),IF(ISOMITTED(p_51),50,51)),IF(ISOMITTED(p_54),IF(ISOMITTED(p_53),52,53),IF(ISOMITTED(p_55),54,55))),IF(ISOMITTED(p_60),IF(ISOMITTED(p_58),IF(ISOMITTED(p_57),56,57),IF(ISOMITTED(p_59),58,59)),IF(ISOMITTED(p_62),IF(ISOMITTED(p_61),60,61),IF(ISOMITTED(p_63),62,63)))))))

更容易复制版本here

它在封闭的 LAMBDA 中被称为 =ARGSCOUNT(arg_1, arg_2, ..., arg_63)。请注意,它最多需要 63 个可选参数,因为我的二叉树是对称的,因此必须是 2 的幂(对于 0 参数的情况,减 1)并且命名引用的字符限制在 2000 左右。但是您可以多次调用它来自父函数并对结果求和,例如ARGSCOUNT(arg_1, ..., arg_63) + ARGSCOUNT(arg_64, ..., arg_126)

然后可以在第二个 LAMBDA 函数中使用该计数来构建数组:

Tag Value
Name ARRAY
Scope Workbook
Comment Create an array from comma separated arguments, up to 130
Refers To =LAMBDA(_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],[_101],[_102],[_103],[_104],[_105],[_106],[_107],[_108],[_109],[_110],[_111],[_112],[_113],[_114],[_115],[_116],[_117],[_118],[_119],[_120],[_121],[_122],[_123],[_124],[_125],[_126],[_127],[_128],[_129],CHOOSE(SEQUENCE(ARGSCOUNT(_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)+ARGSCOUNT(_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,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,_117,_118,_119,_120,_121,_122,_123,_124,_125,_126)+ARGSCOUNT(_127,_128,_129)+1),_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,_101,_102,_103,_104,_105,_106,_107,_108,_109,_110,_111,_112,_113,_114,_115,_116,_117,_118,_119,_120,_121,_122,_123,_124,_125,_126,_127,_128,_129))

我知道这很糟糕,但这让您可以访问一个非常简单的功能:

=ARRAY(A1, A2, 3, "foo", "bar") // array of anything, dynamically sized


奖金:

你甚至可以创建一个 LAMBDAS 数组来传递给 map 之类的东西:

=MAP(ARRAY(LAMBDA(x, x^2), LAMBDA(y, y+1)),LAMBDA(f, f(3)) // -> 9,4 i.e. 3^2, 3+1

...并且 vba ARRAYFROMARGS 函数不能将 LAMBDAS 作为参数。

【讨论】:

【参考方案3】:

如果Array1 定义为:

=CHOOSE(SEQUENCE(number_of_args), arg_1, arg_2, arg_3, ..., arg_n))

如果number_of_args 等于number_of_args >= n,那么您想要的结果是通过以下方式得出的:

=INDEX(Array1,SEQUENCE(SUM(1-ISERR(Arry1))))

【讨论】:

抱歉,我不确定这如何解决问题。我正在寻找一个 create 数组的函数,而不是从现有数组中获取第一个非错误元素的函数。你能解释一下你的答案吗? 抱歉,如果我误解了,但您说“如果我知道 args 的数量,我可以使用这样的选择函数 =CHOOSE(SEQUENCE(number_of_args), arg_1, arg_2, arg_3, .. .))”,所以我创建了一个适用于未知数量 args 的版本。 哦,我想我知道这是怎么回事了。你的意思是我应该有一个函数来加载可选参数,将它们存储在一个超大的数组中,然后使用 isomitted 函数/iser 过滤该数组以返回一个有效值数组,是这样的想法吗?如果是这样,过滤比二分搜索稍微慢一些,因为它必须查询我认为的每个元素,但在语法上要好得多,可能是单线 别担心!是的,那么在给定辅助函数的情况下,最终的 ARRAY() 函数会是什么样子? 是的,就是这样。当然,效率不高,但也许您可以对 number_of_args 设置一个合理的上限,这样就不会导致设置效率太低。不幸的是,我没有 LAMBDA - 我认为您可以根据需要调整我提供的结构。也许你更喜欢看完整版:=INDEX(CHOOSE(SEQUENCE(number_of_args), arg_1, arg_2, arg_3, ..., arg_n)),SEQUENCE(SUM(1-ISERR(CHOOSE(SEQUENCE(number_of_args), arg_1, arg_2, arg_3, ..., arg_n))))))

以上是关于Excel函数从没有VBA的值构造数组的主要内容,如果未能解决你的问题,请参考以下文章

excel vba 数组中第1位字符为0,赋给单元格时如何将0保留?

excel函数 value是啥意思

从 Excel 调用的访问 vba 函数导致返回不同的值

用vba给Excel单元格赋值

Excel VBA:将计算结果数组作为参数传递给函数

使用公式将数组从 VBA 函数输出到 Excel 工作表