设计方法:重载vs Switch?

tel*_*f14 14 wolfram-mathematica package

关于封装设计的性能和可扩展性,最好是:

  1. ...'重载'函数名称(让Mathematica根据模式/条件/测试以及系统命令定义的方式来排序使用哪个版本)?
  2. ...或者使用Switch [](或类似命令)构建单个函数来直接评估?

Mathematica的表现力经常让我感到困惑,就像这样的愚蠢(?)问题.

WRe*_*ach 11

这是一个广泛的问题,但我将借此机会给出一个广泛的答案......

我主张人们应该接受编程语言的主要范式,而不是试图对抗它或编写遵循另一种语言习语的代码.Mathematica是围绕模式匹配的概念构建的,因此,恕我直言,我们应该在尝试表达自己时始终首先考虑模式匹配.遵循这一原则,我赞成定义Switch.

关于性能问题,在比较Mathematica结构时越来越强调微基准测试,我越来越感到烦恼.虽然了解与构造相关的成本是有价值的,但我们应该注意Knuth(或者是Hoare?):"我们应该忘记效率低,大约97%的时间说:过早的优化是所有邪恶的根源"."邪恶"是程序中可读性的丧失,为了效率,它使用一些模糊或间接的方法来实现效果.这是我的表现清单:

  1. 性能是一个问题吗?如果没有,那么跳过清单的其余部分.

  2. 性能瓶颈在哪里?分析器在这里有所帮助,但通常可以通过检查或一些打印语句轻松找到瓶颈.然后...

  3. 算法效率低下吗?非常频繁:是否存在可以通过索引方案线性化或帮助的双嵌套循环?

  4. 好的,算法很好所以我想是时候进行microbenchmark了.

我不知道我对Mathematica的使用是否不够雄心勃勃,但大部分时间我都没有超越第一步.然后#3抓住了剩下的大部分.在Mathematica中,我发现我通常只是喜出望外,我可以用少量代码执行一些雄心勃勃的任务 - 整体表现通常不会进入画面.

哦 - 哦,我最好把肥皂盒拿走.抱歉,那个.

  • 关于使用自然范式的一个很好的观点.关于你对mma的使用可能不够雄心勃勃,你使用它的可能性太大了.至少在我的情况下,我主要使用mma来做一些事情:构造非常大的稀疏矩阵,获得特征值和向量,用它们做一些简单的事情.通常,最耗时的部分是构建矩阵,而不是解决特征系统.在那种情况下,microbenchmarking(或者只是`Compile` to C)是C之前的最后一个希望.对于更复杂的使用,我完全同意你的看法. (2认同)

Bre*_*ion 9

除了非常简单的情况,我更喜欢使用具有多个定义的函数而不是Switch.原因有三:

  1. 只要函数命名良好,我就会发现读代码更容易.
  2. 通过/错误情况更容易设置适当的默认值并再次调用该函数.
  3. 如果使用函数,则可以在计算结果时使用命名模式.

编辑

这是一个作为Sjoerd的#2示例创建的示例:

createNColors[fn_, Automatic, n_] := Table[Hue[i/n], {i, n}]

createNColors[fn_, colors_List, n_] := PadRight[colors, n, colors]

createNColors[fn_, color:(Hue | RGBColor | CMYKColor | GrayLevel)[__], n_] := 
    Table[color, {n}]

createNColors[fn_, color_, n_] := (
    Message[fn::"color", HoldForm[color]]; 
    createNColors[fn, Automatic, n]
    )  
Run Code Online (Sandbox Code Playgroud)

它可用于为某些选项生成一组n种颜色.


Tim*_*imo 9

要回答问题的性能部分,请考虑以下两个重载和使用示例 Switch[]

switchFunc[a_] :=  Switch[a, _String, 5, _Integer, var, _Symbol, "string"] 

overloadFunc[a_String]  := 5;
overloadFunc[a_Integer] := var;
overloadFunc[a_Symbol]  := "string";
Run Code Online (Sandbox Code Playgroud)

这是非常简化的,但足以证明性能的差异

In[1]  := Timing@Nest[switchFunc, x, 1000000]
Out[1] := {3.435, "string"}

In[2]  := Timing@Nest[overloadFunc, x, 1000000]
Out[2] := {0.754, "string"}
Run Code Online (Sandbox Code Playgroud)

但是,如果您打算基于条件测试重载您的函数,则性能会比Switch[]以下情况更糟:

switchFunc2[a_] := Switch[a < 5, True, 6, False, 4];

overloadFunc2[a_ /; a < 5] := 6;
overloadFunc2[a_ /; a > 5] := 4;
overloadFunc2[a_] := a;

In[3]  := Timing@Nest[switchFunc2, 4, 1000000]
Out[3] := {2.63146, 4}

In[4]  := Timing@Nest[overloadFunc2, 6, 1000000]
Out[4] := {4.349, 6}
Run Code Online (Sandbox Code Playgroud)

编辑:这个答案的时间是在OS X 10.7.2上使用Mathematica 8.0.1完成的.有关上述顺序相反的其他结果,请参阅Mr.Wizard的答案.尽管如此,我认为对函数参数进行逻辑模式检查是明智的.

从设计的角度来看,我的个人经历就是这样,Switch[]而且它很糟糕,因为它们难以阅读和缓慢.但是,我也认为根据参数的类型使相同的函数执行不同通常是糟糕的设计,并使得更难以遵循您的代码(即使它可能更容易阅读).


Mr.*_*ard 6

你写的问题很模糊,对"重载"有不同的解释会改变我的答案.但是,如果您正在讨论关于不同类型(头部)和参数模式的重载您自己的函数,那么无论如何都要利用Mathematica紧密集成的模式匹配.


为了提供一个实际的例子,我将使用我的这个解决方案.以供参考:

f[k_, {}, c__] := If[Plus[c] == k, {{c}}, {}]

f[k_, {x_, r___}, c___] := Join @@ (f[k, {r}, c, #] & /@ Range[0, Min[x, k - Plus[c]]])
Run Code Online (Sandbox Code Playgroud)

如果我在f没有模式匹配的情况下重写并调用它g:

g = Function[{k, L, c},
      If[L === {},
         If[Tr@c == k, {c}, {}],
         Join @@ (g[k, Rest@L, Append[c, #]] & /@ Range[0, Min[First@L, k - Tr@c]])
      ]
    ];
Run Code Online (Sandbox Code Playgroud)

我觉得这不太清楚,写作肯定不太方便.我不得不使用显式RestFirst函数,我不得不介绍,Append因为我无法容纳可变数量的参数.这也需要使用虚拟的第三个参数:{}.

Timings表明原始形式也快得多:

f[12, {1, 5, 8, 10, 9, 9, 4, 10, 8}]; // Timing
g[12, {1, 5, 8, 10, 9, 9, 4, 10, 8}, {}]; // Timing
Run Code Online (Sandbox Code Playgroud)
{0.951, Null}
{1.576, Null}

为了回应蒂莫的回答,我认为分享我的时间结果是有价值的,因为它们与他的不同.(我在Windows 7上使用的是Mathematica 7.)此外,我认为他将DownValues版本复杂化,超出了Switch版本的功能.

首先,我的函数编写时间,但使用一系列值:

Array[switchFunc2, 1*^6]; // Timing
Array[overloadFunc2, 1*^6]; // Timing
Run Code Online (Sandbox Code Playgroud)
{1.014, Null}
{0.749, Null}

所以即使写完,DownValues函数对我来说也更快.但不需要第二个条件:

ClearAll[overloadFunc2]

overloadFunc2[a_ /; a < 5] := 6;
overloadFunc2[a_] := 4;

Array[overloadFunc2, 1*^6]; // Timing
Run Code Online (Sandbox Code Playgroud)
{0.546, Null}

当然,在这种简单功能的情况下,也可以使用If:

ifFunc[a_] := If[a < 5, 6, 4]

Array[ifFunc, 1*^6]; // Timing
Run Code Online (Sandbox Code Playgroud)
{0.593, Null}

如果这是作为Mathematica在Array中编译的纯函数编写的:

ClearAll[ifFunc]
ifFunc = If[# < 5, 6, 4] &;

Array[ifFunc, 1*^6]; // Timing
Run Code Online (Sandbox Code Playgroud)
{0.031, Null}