Math 获取一些数据,返回一个纯分段函数

Math 获取一些数据,返回一个纯分段函数,math,lambda,functional-programming,wolfram-mathematica,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},返回一个纯函数,如下所示: 我用函数和分段写了一些东西,我将把它们作为答案,但它似乎效率不高,尤其是对于大量的点。 [更新:我的答案现在可能还不错。如果没有人有更好的想法,我可能会同意。] 明确地说,我们正在寻找一个函

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

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

我用
函数
分段
写了一些东西,我将把它们作为答案,但它似乎效率不高,尤其是对于大量的点。 [更新:我的答案现在可能还不错。如果没有人有更好的想法,我可能会同意。]

明确地说,我们正在寻找一个函数,它接受一个参数——一组数字对——并返回一个纯函数。 该纯函数应该接受一个数字并返回一个数字。

以下操作有效:

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]]]

stp0[x_][{{x1,y1,y1},{x2,y2}]:={y1,x1您也可以使用
插值
(使用
InterpolationOrder->0
)执行此操作,但通过使用下一点的值而不是上一点的值进行插值。 但后来我意识到你可以用一个简单的双重否定技巧来扭转这种局面:

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

我以前的尝试没有正常工作(它们只适用于两个步骤)

我认为以下一种方法也同样有效:

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}]

手工编码的二进制搜索

如果您愿意牺牲简洁性来换取性能,那么命令式二进制搜索方法的性能会很好:

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[stepifyWithBinarySearch, 10]

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

test[stepify (*version 1*), 100000] 57.034 s test[stepify (*version 2*), 100000] 40.903 s test[stepifyWithBinarySearch, 100000] 2.902 s 这非常快,在我的机器上只需要0.016秒就可以执行
测试[stepifyWithInterpolation,100000]
编译花环确实会带来显著的加速。使用花环答案中定义的所有函数,但重新定义
测试
,以

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"]]
(第一列是编译的二进制搜索,第二列是插值,第三列是未编译的二进制搜索)

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


我认为这里的总体结论是,
插值
也只是在做一些固定时间的查找(大概是二进制搜索或类似的搜索),而手工编码的方式恰好稍微快一点,因为它不太通用。

这里有点片面的对话…:)pure是什么意思,这是Mathematica的概念吗?@starblue在这个上下文中(即Mathematica编程),它的意思是“无名”而不是“无副作用”。所以他想要一个返回无名函数的函数。没错。我一直使用“pure function”作为“lambda function”的同义词:看起来Mathematica文档也一样:@starblue在Mathematica中,“常规函数”可以有多个定义,其中一个是通过对调用参数进行模式匹配来选择使用的。“纯函数”只有一个定义并且绕过了模式匹配步骤。因此,它可以在许多情况下提供性能改进。很好!谢谢belisarius。
MapThread[List,…
与这里的转置不同吗?另外,我发现这不太符合规范;将其封装在
函数[x,…]中是否有效
?@dreeves我做了一些计时(没什么大不了的),但似乎你的第一个版本是最快的…谢谢你这么做!想清理你的答案并粘贴到你认为最快的我的版本中,然后我可以将其标记为已接受的答案吗?(我对公认答案的理论是,未来的搜索者不需要看任何其他答案,也就是说,这是一个完整的答案,也是最好的答案,提问者对此提供担保。哦,而且回答者应该自由地相互借鉴,试图构建一个确定的答案。)@dreeves你要求效率,我提供了优雅(充其量)。所以我认为你的答案应该是可以接受的,至少在有人想出一个更快的答案之前是这样。(我也分享了你关于借用内容的观点,当然有适当的归因)我认为你的版本不对。我只是用{{1,20},{2,10},{3,20}试过,结果不匹配。我想我提供了糟糕的数据。:)谢谢花环!这真让我惊讶。为什么分段函数或插值函数每次都不能比手工二进制搜索更好呢?我认为其他解决方案有很大的问题!就像它们每次调用纯函数或其他东西时都在重构片段一样。@dreeves您的直觉是正确的。请参阅我的更新ed回答关于避免重新计算插值函数的问题。我认为您应该将我的回答中插值函数的预计算合并,然后使您的回答成为可接受的答案。太好了!(我刚刚接受了您的答案。非常感谢您的帮助!)我认为这与数据数组不再以这种大小打包有关。请尝试使用[Developer`]DiscretePlot[PackedArrayQ[Table[{n,n^2},{n,1,count}]//Boole,{count,5000,100000,5000}]DiscretePlot[PackedArrayQ[Table[{n,n^2},{n,1,count}]//Boole,{count 100,500,20}]@Sjoerd-hmm,说得好。但即使我明确使用
topackedaray[data]
而不是
data
,我也看到了同样的减速。不过,这一定是因为
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}]]]
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"]]
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
*)