【问题标题】:how to build a list on the fly with condition, the functional way如何在有条件的情况下即时构建列表,功能方式
【发布时间】:2011-07-24 10:11:43
【问题描述】:

我仍然不能很好地在 Mathematica 中以函数方式处理列表。这里有个小问题,想问问有什么好的函数式解决方法。

我已经说过以下由点组成的列表。因此每个元素都是一个点的坐标(x,y)。

a = {{1, 2}, {3, 4}, {5, 6}}

我想遍历这个列表,每次我找到一个 y 坐标 > 3.5 的点时,我都想生成它的复共轭点。最后,我想返回生成的点列表。因此,在上面的示例中,有 2 个点将满足此条件。因此,最终列表将有 5 个点,3 个原始点和 2 个复共轭点。

我试过了:

If[#[[2]] > 3.5, {#, {#[[1]], -#[[2]]}}, #] & /@ a

但我明白了

{{1, 2}, {{3, 4}, {3, -4}}, {{5, 6}, {5, -6}}}

您会在中间看到额外的 {},在我必须添加复共轭点的点周围。我希望结果是这样的:

{{1, 2}, {3, 4}, {3, -4}, {5, 6}, {5, -6}}

我尝试插入 Flatten,但没有奏效,因此,我发现自己有时会回到以前的程序方式,并像这样使用 Table 和 Do 循环之类的东西:

a = {{1, 2}, {3, 4}, {5, 6}}
result = {};
Do[

 If[a[[i, 2]] > 3.5, 
   {
    AppendTo[result, a[[i]]]; AppendTo[result, {a[[i, 1]], -a[[i, 2]]}]
   }, 
   AppendTo[result, a[[i]]]
 ],
 {i, 1, Length[a]}
 ]

这给了我我想要的,但不是功能性解决方案,我不喜欢它。

解决此类列表操作的最佳功能方法是什么?

更新 1

使用上面相同的数据,假设我想在遍历列表时对每个点进行计算,并在构建列表时使用此计算。假设我想找到点的范数(位置向量),并使用它来构建一个列表,其每个元素现在都是 {norm, point}。并遵循与上述相同的逻辑。因此,唯一的区别是我在每一步都进行了额外的计算。

这是我使用提供的解决方案所做的:

a = {{1, 2}, {3, 4}, {5, 6}}

If[#[[2]] > 3.5, 
   Unevaluated@Sequence[ {Norm[#], #}, {Norm[#], {#[[1]], -#[[2]]}}], 
   {Norm[#], #}
 ] & /@ a

这给了我想要的:

{    {Sqrt[5],{1,2}}, {5,{3,4}}, {5,{3,-4}}, {Sqrt[61],{5,6}}, {Sqrt[61],{5,-6}}   }

我遇到的唯一问题是,我在 3 个地方为同一点重复了对 Norm[#] 的调用。有没有办法在不重复计算的情况下做到这一点?

这就是我目前使用旧程序方式执行上述操作的方式:

a = {{1, 2}, {3, 4}, {5, 6}}
result = {};
Do[
 o = Norm[a[[i]]];
 If[a[[i, 2]] > 3.5, 
  {
   AppendTo[result, {o, a[[i]]}]; AppendTo[result, {o, {a[[i, 1]], -a[[i, 2]]}}]
  }, 
  AppendTo[result, {o, a[[i]]}]
 ],
 {i, 1, Length[a]}
]

我得到的结果与函数方式相同,但在上面,由于我使用了一个临时变量,所以我每点计算一次。

这是一个播种和收割之类的地方吗?我真的从来没有很好地理解这两个功能。如果没有,您将如何以功能方式执行此操作?

谢谢

【问题讨论】:

  • 感谢大家的cmets和答案。如果我能全部接受,我会的。我对编程和使用列表的“功能性”方式了解得越多,我就越觉得它更强大。我正在查看我现在正在编写的演示,并在我了解更多信息时将我的一些程序代码更改为更具“功能性”,并且我发现功能方式更短且更不容易出错。我只是认为,与过程式编程相比,函数式编程需要更多的技能和时间,但这似乎是值得的。

标签: wolfram-mathematica


【解决方案1】:

一种方法是使用Sequence

对您的解决方案稍作修改:

If[#1[[2]] > 3.5, Unevaluated@Sequence[#1, {#1[[1]], -#1[[2]]}], #1] & /@ a

不过,一个普通的ReplaceAll 可能更简单:

a /. {x_, y_} /; y > 3.5 :> Sequence[{x, y}, {x, -y}]

这种用法正是RuleRuleDelayed 具有SequenceHold 属性的确切原因。

回答更新1

我会分两步完成:

b = a /. {x_, y_} /; y > 3.5 :> Sequence[{x, y}, {x, -y}]
{Norm[#], #}& /@ b

在实际计算中,您可能希望单独使用规范,因此 Norm /@ b 可能会这样做

【讨论】:

  • +1,很酷。我实际上在那里尝试了 Sequence,但这样做是这样的: If[#[[2]] > 3.5, Sequence[{#, {#[[1]], #[[2]]}}], #] & / @a,它也没有工作。我不知道这样的 Unevaluated@Sequence 技巧。我也喜欢你的第二个解决方案。谢谢。
  • 我对同一个问题进行了跟进,我一直在努力以实用的方式来解决这个问题。请参阅更新 1。
【解决方案2】:

虽然 Mathematica 可以很好地模拟函数式编程范式,但您可以考虑使用 Mathematica 的原生范式——模式匹配:

a = {{1,2},{3,4},{5,6}}

b = a /. p:{x_, y_ /; y > 3.5} :> Sequence[p, {x, -y}]

然后您可以进一步转换结果以包含Norms:

c = Cases[b, p_ :> {Norm@p, p}]

毫无疑问,使用Sequence 生成一个非常大的列表不如预先分配一个正确大小的数组然后使用元素分配更新它那么有效。但是,我通常更喜欢表达清晰而不是这种微优化,除非所述优化被认为对我的应用程序至关重要。

【讨论】:

  • 在 Matlab 中通常使用预分配和赋值,但在 Mathematica 中,使用 SowReap 通常更容易且同样有效,如 Mark McClure 所示。尤其是当您事先不知道所需的大小时(因此需要通过将数组的大小加倍或类似的方法来有效地增长数组)。
  • @Szabolcs 我同意播种/收割通常是表现力和效率之间的最佳平衡。在 Mathematica 中,通常有许多可能的解决方案来解决只有秒表才能从性能角度区分的问题。自然地,命令式技术通常为优化提供最大的前景。 OP 正在寻找从命令式技术转向功能性技术的指导。我猜测动机是表达能力,所以我的回应主要关注的是建议超越功能性模式重写,主要的 Mathematica 范式。
【解决方案3】:

Flatten 采用第二个参数,指定要展平的深度。因此,您还可以执行以下操作。

a = {{1, 2}, {3, 4}, {5, 6}};
Flatten[If[#[[2]] > 3.5, {#, {#[[1]], -#[[2]]}}, {#}] & /@ a, 1]

Do 循环最严重的问题是AppendTo 的使用。如果result 变长,这将非常缓慢。处理由于此类过程而增长的列表的标准方法是使用ReapSow。在这个例子中,你可以这样做。

new = Reap[
  Do[If[el[[2]] > 3.5, Sow[{el[[1]], -el[[2]]}]],
  {el, a}]][[2, 1]];
Join[a, new]

【讨论】:

  • 我实际上尝试过 Flatten[..,1],但我落后了一个 {}。这是我尝试过的: If[#[[2]] > 3.5, {#, {#[[1]], #[[2]]}}, #] & /@ a;展平[%, 1];并没有工作。我需要在上面的最后一个 # 周围添加一个额外的 {}。没看到。这些 {{{}}} 会让人抓狂:)。感谢您的回答并展示了收割与播种。
【解决方案4】:

如果您要多次使用昂贵的东西,请使用With(或Module)回答您的编辑。

这是我在您的编辑中遇到的问题:

a = {{1, 2}, {3, 4}, {5, 6}};
Table[With[{n = Norm[x]}, 
  Unevaluated@Sequence[{n, x}, 
    If[x[[2]] > 3.5, {n, {1, -1} x}, Unevaluated@Sequence[]]]], 
 {x, a}]

上面的结构可以修改为在MapReplaceAll 版本中使用,但我认为Table 在这种情况下更清晰。未评估的序列有点烦人。您可以改为使用一些未定义的函数f,然后在最后用Sequence 替换f

【讨论】:

    【解决方案5】:

    Mark 的 Sow/Reap 代码未按请求的顺序返回元素。这样做:

    a = {{1, 2}, {3, 4}, {5, 6}};
    
    Reap[
      If[Sow[#][[2]] > 3.5, Sow[# {1, -1}]] & /@ a;
    ][[2, 1]]
    

    【讨论】:

    • @Verbeia 嗯...我想是的。我想我仍然对更改某人的帖子并不完全满意。如果您同意这是一个很好的更改,我将进行编辑并删除此答案。
    【解决方案6】:

    您可以将 join 与 Apply(@@) 一起使用:

    加入@@ ((如果[#[[2]] > 3.5, {#, {#[[1]], -#[[2]]}}, {#}]) & /@ a)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-03
      • 1970-01-01
      • 2015-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多