9

一个简单的问题,但我只是为了确保我没有忽略一个明显的解决方案,它可以更有效。

如果一个人有很大的数据缓冲区,比如说非常大的列表,需要更新,并且想将它传递给一个函数以在函数内部进行更新,如

a = Table[0,{10}]
a = update[a]

并且由于我不能使用按引用传递(在 CDF 中,不能将函数的属性更改为任何东西,例如 HoldFirst),所以我被迫在函数本身内按顺序制作列表的副本更新它,并返回副本。

我的问题是,除了使用不好的“全局变量”之外,还有更有效的方法吗?

附言。大约一年前,我通过引用询问了副本,这是 我的 Mathgroup 问题的链接。(顺便说一句,感谢 Leonid 的回答,这是有用的答案)。

但是我在这里的问题有点不同,因为现在我不能使用 HoldFirst,是否还有其他我没有看到的替代方法来避免这种额外的数据复制,当大小变得太大时,它似乎会减慢程序的速度大的。

(不能使用 SetAttributes 及其朋友,CDF 中不允许)。

我将首先展示基本示例,然后展示如果我可以使用 HoldFirst,我会怎么做。

例子

update[a_List] := Module[{copyOfa = a}, copyOfa[[1]] = 5; copyOfa]
a = Table[0, {10}];
a = update[a]

----> {5, 0, 0, 0, 0, 0, 0, 0, 0, 0}

如果我可以使用 HoldFirst,我会写

update[a_] := Module[{}, a[[1]] = 5; a]
Attributes[update] = {HoldFirst};

a = Table[0, {10}];
a = update[a]

----> {5, 0, 0, 0, 0, 0, 0, 0, 0, 0}

效率更高,因为没有复制。通过参考。

我可以使用全局变量,如

a = Table[0, {10}];
updateMya[] := Module[{}, a[[1]] = 5]
updateMya[];
a
----> {5, 0, 0, 0, 0, 0, 0, 0, 0, 0}

但这当然是糟糕的编程,即使它非常快。

由于我有大型数据缓冲区,并且我想模块化我的 Mathematica 代码,我需要创建将大数据传递给它以进行处理的函数,但同时又想保持它“高效”。

可以看到任何其他选项来做到这一点?

抱歉,如果之前在这里问过这个问题,很难搜索到。

谢谢,

加法1

使用 Unevaluated 很容易使用,但我不再能够使用我必须确保列表正在传递的类型检查。例如

update[a_List] := Module[{}, a[[1]] = 5; a]
a = Table[0, {10}];
a = update[Unevaluated[a]]

现在调用不会“绑定”到定义,因为“a”现在没有标题列表。

因此,我失去了代码中的一些稳健性。但是在 CDF 中使用 Unevaluated 确实有效,并且更改代码以使用它很容易。我只需要删除我在那里的那些额外的“类型检查”以使其工作。

4

2 回答 2

16

该功能Unevaluated与(临时)设置属性的效果几乎相同,HoldFirst因此您可以执行类似的操作

update[a_] := Module[{}, a[[1]] = 5; a]
a = Table[0, {10}];
a = update[Unevaluated[a]]

编辑

关于加法1:您可以通过执行类似的操作来添加类型检查

Clear[update];
update[a_] := Module[{}, a[[1]] = 5; a] /; Head[a] == List

然后

a = Table[0, {10}];
update[Unevaluated[a]]

像以前一样工作,但是

b = f[1,2,3];
update[Unevaluated[b]]

仅以未评估的形式返回最后一条语句。

于 2011-09-11T08:38:16.683 回答
12

或者,如果 CDF 允许,您可以使用具有 Hold* 属性的纯函数,如下所示:

update = Function[a, a[[1]] = 5; a, HoldFirst]

然后,您照常使用它:

In[1408]:= 
a=Table[0,{10}];
update[a];
a

Out[1410]= {5,0,0,0,0,0,0,0,0,0}

编辑

为了完整起见,这是另一种不太优雅的方式,但我发现自己不时使用它,尤其是当您有多个参数并且想要保存多个参数时(但是这样HoldFirstHoldRest不够好,例如例如,第一个和第三个):只需将参数包装在 中Hold,并将其记录在函数的签名中,如下所示:

updateHeld[Hold[sym_], value_] := (sym[[1]] = value; sym)

您将其用作:

In[1420]:= a=Table[0,{10}];
updateHeld[Hold[a],10];
a

Out[1422]= {10,0,0,0,0,0,0,0,0,0}

编辑 2

如果您主要关心的是封装,您还可以使用Module创建持久性局部变量和方法来访问和修改它,如下所示:

Module[{a},
   updateA[partIndices__, value_] := a[[partIndices]] = value;
   setA[value_] := a = value;
   getA[] := a
]

从结构的角度来看,它仍然(几乎)是一个全局变量,但没有与其他变量发生名称冲突的危险,并且更容易跟踪它的更改位置,因为您只能通过使用 mutator 方法来做到这一点上面(但不是直接)。您将其用作:

In[1444]:= 
setA[Table[0,{10}]];
updateA[1,5];
getA[]

Out[1446]= {5,0,0,0,0,0,0,0,0,0}

这就像在 Java 中制作一个简单的 JavaBean——一个可变数据的容器(一种封装状态的方法)。由于额外的方法调用(wrt Hold-attribute 或Unevaluated基于方法),您将有一点开销,并且在许多情况下您不需要它,但在某些情况下您可能希望像这样封装状态 - 它可能会使你的(有状态的)代码更容易测试。就个人而言,我已经为 UI 编程和与数据库接口相关的代码做了几次。

本着同样的精神,你还可以在函数之间共享一些变量,在作用域内定义这些函数Module——在这种情况下,你可能不需要 getter 和 setter 方法,而这种具有共享状态的全局函数是closures. 您可以在我在此MathGroup 线程的第三篇文章中找到更详细的讨论。

于 2011-09-11T09:41:55.280 回答