17
$\begingroup$

This question is closely related to the problem of zero crossings.

Suppose I have a list l = {-1, -1, 0, 1, 0, 0, -1, -1, 1, 1, 1, 0, 0, 0, -1}

and I want to replace the zeros with the previous nonzero value, like this:

{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}

A rather clunky way to do this is with:

  f = TimeSeries[l /. 0 -> Missing[], 
MissingDataMethod -> {"Interpolation", InterpolationOrder -> 0}]

Then indeed f[-1 + Range@Length@l]gives the required result.

However, there must be a more elegant way to achieve same, with patterns.

Can anyone supply a suggestion?

$\endgroup$
1
  • 4
    $\begingroup$ What should happen when the first element is a zero? $\endgroup$ Sep 25, 2019 at 14:53

8 Answers 8

18
$\begingroup$

SequenceReplace

SequenceReplace[ p:{a_, 0..} :> Sequence @@ (p /. 0 -> a)] @ l

{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}

or

FixedPoint[SequenceReplace[{a_, 0} :> Sequence[a, a]], l]

{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}

ReplaceRepeated

An alternative way to use ReplaceRepeated with a single replacement rule:

l //. {a___, b_, 0, c___} :> {a, b, b, c}

{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}

Memoization

Memoize the last non-zero value (inspired by @WReach's answer):

f[x_] := (f[0] = x; x) 
f /@ l

{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}

Split + ReplaceAll

Split[l, #2 == 0 &] /. p : {a_, 0 ..} :> (p /. 0 -> a) // Flatten

{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}

TimeSeries + MissingDataMethod

Using the "Values" property with OP's f:

f["Values"]

{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}

$\endgroup$
2
  • 1
    $\begingroup$ One of the reasons I asked this question even though I had a working (although inelegant) solution was to see all the creative ways in which MMA experts are able to use the Wolfram language to solve a problem. Outstanding! $\endgroup$ Sep 25, 2019 at 22:41
  • 1
    $\begingroup$ What sort of (Mathematica) black magic is this!? My +1 sir. $\endgroup$
    – Hans Olo
    Sep 27, 2019 at 13:54
17
$\begingroup$

Define a simple function, using FoldList

op = FoldList[If[#2 == 0, #1, #2] &];

l = {-1, -1, 0, 1, 0, 0, -1, -1, 1, 1, 1, 0, 0, 0, -1};

op@l
(* {-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1} *)
$\endgroup$
1
  • $\begingroup$ This must be the fastest (and simplest) of the lot. The one I knew existed, but never reached. $\endgroup$ Sep 26, 2019 at 22:19
8
$\begingroup$

Perhaps something like this will appeal

l //. {{a___, 1, 0, b___} -> {a, 1, 1, b}, {a___, -1, 0, b___} -> {a, -1, -1, b}}
$\endgroup$
1
  • $\begingroup$ This is the kind of thing I was aiming for, but I couldn't find the right formulation. I find patterns such a challenge (although I do use them extensively). Anyway that you for a great solution. $\endgroup$ Sep 25, 2019 at 22:35
6
$\begingroup$

Given:

list = {-1, -1, 0, 1, 0, 0, -1, -1, 1, 1, 1, 0, 0, 0, -1}

Then:

Module[{prev = 0}, Replace[list, {0 :> prev, x_ :> (prev = x)}, {1}]]

(* {-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1} *)

The initial value assigned by prev = 0 is only used for lists that start with a zero -- choose another value if desired.

$\endgroup$
5
$\begingroup$

A few more...

l = {-1, -1, 0, 1, 0, 0, -1, -1, 1, 1, 1, 0, 0, 0, -1};

Do[If[l[[i]] == 0, l[[i]] = l[[i - 1]]], {i, 2, Length[l]}]

l

{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}

fn[l_] := Block[{l1 = l},
  Set[Part[l1, #[[2]]], Part[l1, #[[1]]]] & /@ SequencePosition[l, {_, 0}];
  l1
]

l = {-1, -1, 0, 1, 0, 0, -1, -1, 1, 1, 1, 0, 0, 0, -1};

FixedPoint[fn, l]

{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}

l = {-1, -1, 0, 1, 0, 0, -1, -1, 1, 1, 1, 0, 0, 0, -1};

Fold[{#1, If[#2 == 0, Last[#1], #2]} &, l[[1 ;; 1]], l[[2 ;; -1]]] //Flatten

{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}

$\endgroup$
3
$\begingroup$
list = {-1, -1, 0, 1, 0, 0, -1, -1, 1, 1, 1, 0, 0, 0, -1};

Using SequenceSplit (new in 11.3)

Flatten @ SequenceSplit[list, {a_, b : 0 ..} :> {a, Table[a, Length @ {b}]}]

{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}

$\endgroup$
2
$\begingroup$
list = {-1, -1, 0, 1, 0, 0, -1, -1, 1, 1, 1, 0, 0, 0, -1};

Using SplitBy, SequenceCases and a suggestion from Syed:

patts = {{0 ..}, {a : Except[0], b : 0 ..} :> Array[a &, Length@{b}]};

sp = SplitBy[#, {0 ..}] &@list;

Catenate[sp /. Rule @@@ Thread[SequenceCases[list, #] & /@ patts]]

(*{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}*)

Or in a simpler way using SequenceReplace, as @Syed suggests:

patt = {a : Except[0], b : 0 ..} :> Splice@Array[a &, Length@{b} + 1];

SequenceReplace[list, patt]

(*{-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1}*)
$\endgroup$
2
  • 1
    $\begingroup$ SequenceReplace[list, {a : Except[0], b : 0 ..} :> Sequence @@ ConstantArray[a, Length@{b} + 1]]. ReplacePart functionality is built into Sequence* functions. Flatten is not a good idea. Consider replacing the -1 in the second location with {a} and rerun the solutions. $\endgroup$
    – Syed
    Feb 7 at 3:42
  • $\begingroup$ Thanks for your excellent observation, Syed! I think I'll pay more attention to SequenceReplace because I haven't used it much. See the update, please. :-) $\endgroup$ Feb 7 at 4:39
0
$\begingroup$

Looking for a mostly arithmetic solution, I found a function that replaces only the first zero:

f[list_] := list + Prepend[ Rest[1 - Unitize[list]] Most[list], 0]

Finding the number of repetitions requires patterns:

list = {-1, -1, 0, 1, 0, 0, -1, -1, 1, 1, 1, 0, 0, 0, -1};
numberIterations =  Max[ SequenceCases[list, {p : Repeated[0]} :> Length[{p}]]]
Nest[f, list, numberIterations]
(* {-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1} *)

Works, but it didn't correspond to what I was looking for.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.