开发者

How do I replace each value in run with the integer preceding the run

Using Mathematica, I have a list:

l={0,0,0,1,2,0,0,0,1,0,0,0,2,0,0,0}

I want to apply a function to the above list to obtain the following:

{0,0,0,1,2,2,2,2,1,1,1,1,2,2,2,2}

Essentially I want to replace the runs of 0 values with runs of the same length, but using the value of the positive integer just preceding each run of 0s.

I thought I could do this easily with FoldList, but I can't see my way through to a solution.

Many开发者_StackOverflow thanks.


Here is your test list:

tst = {0, 0, 0, 1, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0}

The following solution will be reasonably efficient:

In[31]:= Module[{n = 0}, Replace[tst, {0 :> n, x_ :> (n = x)}, {1}]]

Out[31]= {0, 0, 0, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}

The way it works is the following: we use the fact that only the first matching rule is applied. The variable n stores the last non-zero value encountered by the pattern-matcher during its run through the list. Initially it is set to zero. The first rule replaces 0 with the current value of n. If it matches, replacement is made and the pattern-matcher goes on. If it does not match, then we have a non-zero value and the second rule applies, updating the value of n. Since the Set assignment returns back the value, the non-zero element is simply placed back. The solution should have a linear complexity in the length of the list, and is IMO a good example of the occasional utility of side effects mixed with rules.

EDIT

Here is a functional version:

In[56]:= Module[{n = 0}, Map[If[# != 0, n = #, n] &, tst]]

Out[56]= {0, 0, 0, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}

One can check that the rule - based version is about 4 times faster for really large lists. However, the advantage of this form is that it can easily be Compile-d, providing extreme performance:

nzrunsC = 
 Compile[{{l, _Integer, 1}}, 
   Module[{n = 0}, Map[If[# != 0, n = #, n] &, l]], 
   CompilationTarget -> "C"]

In[68]:= tstLarge = RandomInteger[{0,2},{10000000}];

In[69]:= nzrunsC[tstLarge];//Timing
Out[69]= {0.047,Null}

In[70]:= Module[{n = 0},Map[If[#!=0,n = #,n]&,tstLarge]];//Timing
Out[70]= {18.203,Null}

The difference is several hundred times here, and about a hundred times faster than the rule-based solution. OTOH, rule-based solution will work also with symbolic lists, not necessarily integer lists.


ReplaceRepeated seems to work fine for this:

l //. {f__, x_ /; x != 0, 0, e___} :> {f, x, x, e}

(*   {0, 0, 0, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}  *)


Your original idea of using FoldList results in the following elegant solution:

In[1]:= tst = {0, 0, 0, 1, 2, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0};

In[2]:= FoldList[If[#2 != 0, #2, #1] &, 0, tst] // Rest                 

Out[2]= {0, 0, 0, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}

This solution is functionally purer because it does not require a helper variable to be set as a side effect, as the rule-based or map-based versions do. It's also faster:

In[3]:= tstLarge = RandomInteger[{0, 2}, {10000000}];

In[4]:= Module[{n = 0}, Replace[tstLarge, {0 :> n, x_ :> (n = x)}, {1}]]; // Timing

Out[4]= {5.704, Null}

In[5]:= Module[{n = 0}, Map[If[# != 0, n = #, n] &, tstLarge]]; // Timing

Out[5]= {16.5619, Null}

In[6]:= FoldList[If[#2 != 0, #2, #1] &, 0, tstLarge] // Rest; // Timing

Out[6]= {1.25148, Null}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜