10
$\begingroup$

I have a list: {1,2,3,0,0,4,5,0,0,0}. I want to delete the 0's occurring after the last positive integer. In other words, I want Mathematica to return: {1,2,3,0,0,4,5}.

$\endgroup$
2
  • 2
    $\begingroup$ {1, 2, 3, 0, 0, 4, 5, 0, 0, 0} /. {a___, b_ /; b > 0, 0 ..} -> {a, b} $\endgroup$
    – Bill
    Mar 24, 2015 at 23:10
  • $\begingroup$ Somewhat related: (69613) $\endgroup$
    – Mr.Wizard
    Mar 25, 2015 at 11:21

11 Answers 11

14
$\begingroup$

The easiest way would be to use

{1, 2, 3, 0, 0, 4, 5, 0, 0, 0} /. {a___, 0 ...} :> {a}

/. is synonymous with ReplaceAll. It tries to replace values on the left hand side with the rules on the right hand side. In this case {a___, 0...} is the pattern; a___ matches zero or more elements, and 0... matches zero or more zeroes. :> {a} takes the a that corresponds to the matched expression and returns it.

$\endgroup$
2
  • $\begingroup$ Thanks for the explanation of the code. I was completely unaware of ... Repeated. $\endgroup$ Mar 24, 2015 at 23:30
  • 4
    $\begingroup$ Note that this only works because by default the first pattern is treated as Shortest. In reverse order this fails: {0, 0, 0, 1, 2, 3, 0, 0, 4, 5} /. {0 ..., a___} :> {a}. Adding a Shortest or Longest corrects the problem: {0, 0, 0, 1, 2, 3, 0, 0, 4, 5} /. {Longest[0 ...], a___} :> {a} $\endgroup$
    – Mr.Wizard
    Mar 25, 2015 at 9:11
7
$\begingroup$
Internal`DeleteTrailingZeros@{1, 2, 3, 0, 0, 4, 5, 0, 0, 0}
(* {1, 2, 3, 0, 0, 4, 5} *)

Timing comparison with @ciao's dropper and dropper2:

test = Join[RandomChoice[{1, 10} -> {1, 0}, 100000], ConstantArray[0, 100000]];

First@AbsoluteTiming[res0 = Internal`DeleteTrailingZeros@test;]
(* 0.005003 *)
First@AbsoluteTiming[res1 = dropper@test;]
(* 0.016011 *)
First@AbsoluteTiming[res2 = dropper2@test;]
(* 0.002004 *)
Equal @@ {res0, res1, res2}
(* True *)
$\endgroup$
6
$\begingroup$

The TakeWhile and replacement based answers will be very slow with large lists and/or large lists with many trailing zeroes.

Something like

dropper=With[{s = Split[#]}, If[s[[-1, 1]] == 0, Join @@ (Most@s), #]] &;

will be ~2000X faster on a list of 50K length vs a replacement solution, advantage growing with size. There are other even faster methods for really huge flat numeric lists... e.g.:

dropper2 = 
 With[{s = SparseArray[#, Automatic, 0]["NonzeroPositions"]}, 
       If[s === {}, {}, #[[;; s[[-1, 1]]]]]] &;

Using:

test = Join[RandomChoice[{1, 10} -> {1, 0}, 100000], ConstantArray[0, 100000]];

The SparseArray is ~10X faster than dropper, which is itself ~15X faster than the TakeWhile, which is itself ~365X faster than the replace-based solution, making the SparseArray ~45,000X faster for this test...

$\endgroup$
1
  • $\begingroup$ SparseArray[#, Automatic, 0] is somewhat verbose; Automatic and 0 are the defaults so you need only SparseArray[#]. $\endgroup$
    – Mr.Wizard
    Mar 25, 2015 at 6:55
5
$\begingroup$

Technically, @Bill's and @Pickett's clean answers do not quite delete the 0s after "the last positive integer." A teeny alteration fixes Bill's answer:

{1, 2, 3, 0, 0, 4, 4.2, 0, 0, 0} /. {a___, b_Integer /; b > 0, 0 ..} -> {a, b}
$\endgroup$
4
  • $\begingroup$ What if there are negative values interspersed to the right of the last positive integer? (My answer made some assumptions about the data that I think are reasonable, so I did not address this myself.) $\endgroup$
    – C. E.
    Mar 24, 2015 at 23:17
  • $\begingroup$ I read the original question to mean: "take the last positive integer in a list, and if all the remaining elements of the list are all $0$, delete them." Thus {0, -4, 0, 0} would be unchanged. I think that's what the poser meant. $\endgroup$ Mar 24, 2015 at 23:25
  • $\begingroup$ For what its worth, my list only contains positive integers. My question probably should have been stated: I want to delete all the 0's after the last non-zero entry. Thank you very much! $\endgroup$ Mar 24, 2015 at 23:35
  • 1
    $\begingroup$ Oh! Then that is a very simple problem and several solutions exist, such as the ones provided by @Bill and Pickett. (Try to be extremely clear and precise in your question posing from now on.) $\endgroup$ Mar 24, 2015 at 23:36
5
$\begingroup$
a = {1, 2, 3, 0, 0, 4, 5, 0, 0, 0};

f = Drop[#, -Length[TakeWhile[Reverse[#], Function[b, b == 0]]]] &;

f[a]

{1, 2, 3, 0, 0, 4, 5}

$\endgroup$
1
  • 1
    $\begingroup$ +1, LengthWhile can be used here as well. $\endgroup$
    – C. E.
    Mar 25, 2015 at 0:23
4
$\begingroup$

Too late for the party so fun solution:

ToString @ Row @ list ~ StringTrim ~ ("0" ..) // ToExpression // IntegerDigits
{1, 2, 3, 0, 0, 4, 5} 

I feel like it is a duplicate but I can't find it...

$\endgroup$
1
  • 3
    $\begingroup$ This kills leading zero as well, which it should not, and it doesn't work with integers larger than 9. This solves both problems and it's shorter: StringTrim[ToString[list], ", 0" ... ~~ "}"] <> "}" // ToExpression :^) $\endgroup$
    – Mr.Wizard
    Mar 25, 2015 at 9:20
2
$\begingroup$

Using ReplaceRepeated (//.):

{1, 2, 3, 0, 0, 4, 5, 0, 0, 0} //. {a___, 0} :> {a}

{1, 2, 3, 0, 0, 4, 5}

$\endgroup$
2
$\begingroup$
list = {1, 2, 3, 0, 0, 4, 5, 0, 0, 0};

Using SequencePosition (new in 10.1)

list[[;; SequencePosition[list, {a_?Positive, 0}][[-1, 1]]]]

{1, 2, 3, 0, 0, 4, 5}

Using ReplaceAt (new in 13.1)

Flatten @ ReplaceAt[{0 ...} :> Nothing, -1] @ Split[list]

{1, 2, 3, 0, 0, 4, 5}

$\endgroup$
2
$\begingroup$

Using IntegerExponent:

From the documentation: IntegerExponent[n,b] gives the number of trailing zeros in the digits of n in base b.

list = {1, 2, 3, 0, 0, 4, 5, 0, 0, 0};
Drop[list, -IntegerExponent[FromDigits[list]]] 

{1, 2, 3, 0, 0, 4, 5}

$\endgroup$
1
  • 1
    $\begingroup$ Very nice and a well deserved (+1). Very interesting approach $\endgroup$
    – bmf
    Dec 26, 2023 at 3:09
2
$\begingroup$

Using FirstPosition

list = {1,2,3,0,0,4,5,0,0,0};
Drop[list,-First@FirstPosition[Reverse[list],_Integer?(#>0&)]+1]
$\endgroup$
1
$\begingroup$

Using ReplaceList:

list = {1, 2, 3, 0, 0, 4, 5, 0, 0, 0};

ReplaceList[#, {a__, b_ /; b > 0, 0 ..} :> Splice@{a, b}] &@list

(*{1, 2, 3, 0, 0, 4, 5}*)

Or using SequenceCases:

SequenceCases[list, {a__, b_ /; b > 0, 0 ..} :> Splice@{a, b}]

(*{1, 2, 3, 0, 0, 4, 5}*)

Or using SequenceSplit:

First@SequenceSplit[list, s : {a__, b_} /; b > 0 :> s]

(*{1, 2, 3, 0, 0, 4, 5}*)

Or using FirstCase and Span:

#[[Span[1, #[[1]]] & @@ Position[#, FirstCase[Reverse@#, a_ /; a > 0]]]] &@list

(*{1, 2, 3, 0, 0, 4, 5}*)
$\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.