取一些数据,返回一个纯粹的分段函数

时间:2011-07-28 03:28:14

标签: math lambda functional-programming wolfram-mathematica

给定{x,y}数据点的列表,返回纯函数f(从实数到实数),使得数据中每{x,y}的f [x] == y。如果x不是x值之一,则返回前一点的y值(x值小于x的值)。如果函数的值小于数据中第一个x值的值 - 即没有前一个点 - 则返回0.

例如,给定数据{{1,20},{2,10}},返回一个如下所示的纯函数:

Graph of the function given {{1,20},{2,10}} http://yootles.com/outbox/so/piecewise.png

我使用FunctionPiecewise写了一些内容,我将其作为答案包含在内,但它似乎效率低下,特别是对于大量的点列表。 [更新:我的答案现在可能实际上很不错。如果没有人有更好的想法,我可能会选择它。]

要清楚,我们正在寻找带有单个参数的函数 - 一对数字对 - 并返回一个纯函数。 那个纯函数应该取一个数字并返回一个数字。

5 个答案:

答案 0 :(得分:5)

您也可以使用InterpolationInterpolationOrder->0)执行此操作,但是使用下一个点的值而不是之前的值进行插值。 但后来我意识到你可以用一个简单的双否定技巧来扭转这一点:

stepify[data_] := Function[x,
  Interpolation[{-1,1}*#& /@ Join[{{-9^99,0}}, data, {{9^99, data[[-1,2]]}}],
                InterpolationOrder->0][-x]]

答案 1 :(得分:5)

我以前的尝试没有正常工作(只有两个步骤才行。)

我认为下面的一行是相同的:

g[l_] := Function[x, 
  Total[#[[2]] UnitStep[x - #[[1]]] & /@ 
    Transpose@({First@#, Differences[Join[{0}, Last@#]]} &@ Transpose@l)]]

Plot[g[{{1, 20}, {2, 10}, {3, 20}}][x], {x, 0, 6}]

enter image description here

答案 2 :(得分:5)

手动编码二进制搜索

如果一个人愿意牺牲性能的简洁性,那么必要的二元搜索方法表现良好:

stepifyWithBinarySearch[data_] :=
  With[{sortedData = SortBy[data, First], len = Length @ data}
  , Module[{min = 1, max = len, i, x, list = sortedData}
    , While[min <= max
      , i = Floor[(min + max) / 2]
      ; x = list[[i, 1]]
      ; Which[
          x == #, min = max = i; Break[]
        , x < #, min = i + 1
        , True, max = i - 1
        ]
      ]
    ; If[0 == max, 0, list[[max, 2]]]
    ]&
  ]

配备一些测试脚手架......

test[s_, count_] :=
  Module[{data, f}
  , data = Table[{n, n^2}, {n, count}]
  ; f = s[data]
  ; Timing[Plot[f[x], {x, -5, count + 5}]]
]

......我们可以测试和计算各种解决方案:

test[stepifyWithBinarySearch, 10]

step plot

在我的机器上,获得以下时间:

test[stepify (*version 1*), 100000]      57.034 s
test[stepify (*version 2*), 100000]      40.903 s
test[stepifyWithBinarySearch, 100000]     2.902 s

我希望通过编写各种功能可以获得进一步的性能提升,但我会将其作为读者的练习。

更好:预先计算插值 (响应dreeves的评论)

令人费解的是,手动编码的未编译二进制搜索将击败Mathematica内置函数。对于Piecewise来说可能并不那么令人惊讶,因为除非进行优化,否则它实际上只是一个美化的IF-THEN-ELSEIF链测试任意复杂度的表达式。但是,人们会期望Interpolation更好,因为它基本上是专门为此任务而构建的。

好消息是Interpolation 确实提供了一个非常快速的解决方案,前提是只安排计算插值一次:

stepifyWithInterpolation[data_] :=
  With[{f=Interpolation[
            {-1,1}*#& /@ Join[{{-9^99,0}}, data, {{9^99, data[[-1,2]]}}]
            , InterpolationOrder->0 ]}
    , f[-#]&
  ]

速度非常快,我的机器只需要0.016秒即可执行test[stepifyWithInterpolation, 100000]

答案 3 :(得分:3)

编译WReach的answer确实会带来显着的加速。使用WReach答案中定义的所有函数,但重新定义test

test[s_,count_]:=Module[{data,f},
    data=Table[{n,n^2},
        {n,count}];
        f=s[ToPackedArray[N@data]];
        Timing[Plot[f[x],{x,-5,count+5}]]]

(这是强制生成数组的必要条件;感谢Sjoerd de Vries指出这一点),并定义

ClearAll[stepifyWRCompiled];
stepifyWRCompiled[data_]:=With[{len=Length@data,sortedData=SortBy[data,First]},
Compile[{{arg,_Real}},Module[{min=1,max=len,i,x,list=sortedData},
            While[
                min<=max,
                i=Floor[(min+max)/2];
                    x=list[[i,1]];
                    Which[
                        x\[Equal]arg,min=max=i;Break[],
                        x<arg,min=i+1,True,max=i-1
                    ]
            ];
            If[0==max,0,list[[max,2]]]
        ],CompilationTarget->"WVM",RuntimeOptions\[Rule]"Speed"]]

With块是将sortedData显式插入要编译的代码块所必需的。我们得到的结果甚至比使用Interpolation的解决方案更快,尽管只是稍微如此:

Monitor[
tbl = Table[
    {test[stepifyWRCompiled, l][[1]],
        test[stepifyWithInterpolation, l][[1]],
        test[stepifyWithBinarySearch, l][[1]]},
        {l, 15000, 110000, 5000}], l]
tbl//TableForm
(*
0.002785    0.003154    0.029324
0.002575    0.003219    0.031453
0.0028      0.003175    0.034886
0.002694    0.003066    0.034896
0.002648    0.003002    0.037036
0.00272     0.003019    0.038524
0.00255     0.00325     0.041071
0.002675    0.003146    0.041931
0.002702    0.003044    0.045077
0.002571    0.003052    0.046614
0.002611    0.003129    0.047474
0.002604    0.00313     0.047816
0.002668    0.003207    0.051982
0.002674    0.00309     0.054308
0.002643    0.003137    0.05605
0.002725    0.00323     0.06603
0.002656    0.003258    0.059417
0.00264     0.003029    0.05813
0.00274     0.003142    0.0635
0.002661    0.003023    0.065713
*)

(第一列是二进制搜索,二次插值,第三列,未编译的二进制搜索)。

另请注意,我使用的是CompilationTarget->"WVM",而不是CompilationTarget->"C";这是因为该函数是用“内置”的大量数据点编译的,如果我使用100000个数据点编译C,我可以看到gcc持续很长时间并占用大量内存(我想最终的C文件很大,但我没有检查)。所以我只是将编译用于“WVM”。

我认为这里的总体结论只是Interpolation也正在进行一些恒定时间查找(二进制搜索或类似的东西,大概是这样),并且手动编码的方式恰好稍微快一点,因为它更少一般

答案 4 :(得分:2)

以下作品:

stp0[x_][{{x1_,y1_}, {x2_,y2_}}] := {y1, x1 <= x < x2}
stepify[{}] := (0&)
stepify[data_] := With[{x0 = data[[1,1]], yz = data[[-1,2]]},
  Function[x, Piecewise[Join[{{0, x<x0}}, stp0[x] /@ Partition[data, 2,1]], yz]]]

请注意,如果没有With,它会在返回的函数中留下{{1,10},{2,20}}[[1,1]]之类的内容,这看起来有点浪费。

顺便说一句,我决定将此调用,因为它将点列表转换为步进函数。