没有For循环的Monotonify(list-munging)

时间:2010-07-22 01:29:31

标签: list functional-programming loops for-loop wolfram-mathematica

对于Mathematica代码中的For或While循环总是让我觉得有点脏,但是我很困惑自己试图做一些列表修改所有功能,并采取这个:

(* # Given a list of {x,y} pairs, transform the data as follows: every time 
   # there's a decrease in y-value from one datapoint to the next, say {x1,Y} 
   # followed by {x2,y}, add Y to the value of every datapoint on or after x2. *)
monotonify[data_] := Module[{data0, i, offset = 0},
  data0 = data;
  For[i = 2, i <= Length[data], i++,
    If[data[[i-1,2]] > data[[i,2]], offset += data[[i-1,2]]];
    data0[[i]] += {0,offset}];
  data0]

(将y值视为里程表读数,有时候里程表会被意外重置 - 显然是因为值减小,里程表不应该这样做。所以我们通过在每次重置之前添加最后的已知值来转换读数。所有未来的价值观。)

你会如何用一种很好的功能风格来写单调?

(事实上,我不认为上面的For循环完全没错可能是一种温和形式的强迫症。)

5 个答案:

答案 0 :(得分:4)

好的,现在我已经修复了我最初要求的输入方法。

从示例数据集开始:

dataset = {{a, 1}, {b, 2}, {c, 3}, {d, 4}, {e, 5}, {f, 0}, {g, 4}, 
{h,5}, {i, 6}, {j, 7}, {k, 4}, {l, 7}, {m, 8}, {n, 9}, {o, 0}, {p,2}, 
{q, 3}};

采取转置:

trDataset = Transpose[dataset];

下一个仅对Y值进行操作的函数:

trDataset[[2]] = FoldList[Plus, dataset[[1, 2]], Map[Max[#, 0] &, Differences[dataset[[All, 2]]]]]

撤消转置:

dataset = Transpose[trDataset]

,输出现在是

{{a, 1}, {b, 2}, {c, 3}, {d, 4}, {e, 5}, {f, 5}, {g, 9}, {h, 10}, {i, 
  11}, {j, 12}, {k, 12}, {l, 15}, {m, 16}, {n, 17}, {o, 17}, {p, 
  19}, {q, 20}}

我还没有测试过这个解决方案的性能。

编辑:好的,这是修复的基础,我将把剩下的工作留给你@dreeves。此版本的monotonify仅适用于数字列表,我没有将其整合到我之前的建议中以处理您的输入。

monotonify[series_] := 
 Split[series, Less] //. {a___, x_List, y_List, z___} /; 
     Last[x] > First[y] -> {a, x, y + Last[x], z} // Flatten

编辑2:另一个适用于数字列表的函数。这比我之前的尝试要快得多。

monotonify[series_] := 
Accumulate[Flatten[Map[Flatten[{#[[1]], Differences[#]}] &, 
       Split[series, Less]]]]

答案 1 :(得分:3)

答案 2 :(得分:2)

一旦手套失灵,我就不能尝试,但我认为For循环版本更直接:

mon00[{prev_,offset_}, next_] := {next, offset + If[prev > next, prev, 0]}
monotonify0[list_] := list + Rest[FoldList[mon00, {-Infinity,0}, list]][[All,2]]
monotonify[data_] := Transpose@{#1, monotonify0[#2]}& @@ Transpose@data

我们的想法是编写一个辅助函数,只为一个简单的y值列表执行它,然后使用双转置惯用法对数据的第二列进行操作。

双转置习语的方便参考

用于转换矩阵中的特定列,例如,使用transformElement [x]替换4列矩阵的第2列中的每个值x:

{#1, transformElement[#2], #3, #4}& @@@ matrix

如果您需要使用将整列作为列表的函数转换列,请使用以下习语:

Transpose @ {#1, transformList[#2], #3, #4}& @@ Tranpose@matrix

答案 3 :(得分:2)

我主要使用SplitFlattenAccumulate。我不确定最终结果比For循环更容易理解,但如果重要的话,它应该是好的和快速的。

monotonize[list_] := 
  With[{splits = Split[list, LessEqual]},
   With[{diffs = Most[Last /@ splits] - Rest[First /@ splits]},
    Flatten[
      MapThread[Plus, {Accumulate[Prepend[diffs, 0]], splits}],
     1]]];

monotonizeSecond[list_] :=
  With[{firsts = First /@ list, lasts = Last /@ list},
   Transpose[{firsts, monotonize@lasts}]];

我认为With的大量使用使得它比依赖匿名函数的解决方案更加清晰。此外,monotonize似乎对“未修饰”列表有用,所以我把它作为一个单独的函数分解。

答案 4 :(得分:1)

基本上,使这一挑战的是,Mathematica中的大多数功能操作符一次在列表的一个元素上运行。这不是唯一的选择,但是这些函数可以设置为一次取一个列表的两个相邻元素,这个假设函数会使获得所需结果变得微不足道。

我们可以使用Partition轻松转换数据,而不是转换函数。

Clear[monotonify];
monotonify[data_] := 
 Transpose[{data[[All, 1]], 
  Rest@FoldList[
    If[#2[[1]] < #2[[2]], #1 + #2[[2]] - #2[[1]], #1 + #2[[2]]] &, 0, 
    Partition[data[[All, 2]], 2, 1, {2, -1}, 0]]}]

这个版本我重构了一个辅助函数来清楚地说明函数折叠是如何工作的,但是mathematica也没有优化它。

Clear[monotonify, m00];
m00[acc_, {prev_, next_}] := 
 If[prev < next, acc + next - prev, acc + next]
monotonify[data_] := 
 Transpose[{data[[All, 1]], 
  Rest@FoldList[m00, 0, Partition[data[[All, 2]], 2, 1, {2, -1}, 0]]}]

编辑:忘了一些{}