10

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

Kuba
  • 136,707
  • 13
  • 279
  • 740
Geoffrey Critzer
  • 1,661
  • 1
  • 11
  • 14

11 Answers11

14

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.

C. E.
  • 70,533
  • 6
  • 140
  • 264
  • Thanks for the explanation of the code. I was completely unaware of ... Repeated. – Geoffrey Critzer Mar 24 '15 at 23:30
  • 4
    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} – Mr.Wizard Mar 25 '15 at 09:11
7
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 *)
kglr
  • 394,356
  • 18
  • 477
  • 896
6

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

ciao
  • 25,774
  • 2
  • 58
  • 139
  • SparseArray[#, Automatic, 0] is somewhat verbose; Automatic and 0 are the defaults so you need only SparseArray[#]. – Mr.Wizard Mar 25 '15 at 06:55
5

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}
David G. Stork
  • 41,180
  • 3
  • 34
  • 96
  • 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.) – C. E. Mar 24 '15 at 23:17
  • 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. – David G. Stork Mar 24 '15 at 23:25
  • 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! – Geoffrey Critzer Mar 24 '15 at 23:35
  • 1
    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.) – David G. Stork Mar 24 '15 at 23:36
5
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}

Chris Degnen
  • 30,927
  • 2
  • 54
  • 108
4

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

Kuba
  • 136,707
  • 13
  • 279
  • 740
  • 3
    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 :^) – Mr.Wizard Mar 25 '15 at 09:20
2

Using ReplaceRepeated (//.):

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

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

corey979
  • 23,947
  • 7
  • 58
  • 101
2
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}

eldo
  • 67,911
  • 5
  • 60
  • 168
2

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}

Syed
  • 52,495
  • 4
  • 30
  • 85
2

Using FirstPosition

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

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

E. Chan-López
  • 23,117
  • 3
  • 21
  • 44