Loren on the Art of MATLAB

July 1st, 2009

Find Largest Positive Value Next to Zero

Recently, Steve wrote a blog discussing code clarity/obscurity in the context of one-line code solutions. Simply stated, the problem he solved is this. Find the largest value in an array adjacent to a zero value. Must be zillions of ways, right?

Contents

An Example

Let me start with an example. If the array in question is A,

A =  [1, 5, 3, 0, 2, 7, 0, 8, 9, 1 0];

the correct answer is 8.

Steve's First Solution

Steve, being an image processing guru, immediately arrived at a solution using image processing techniques. For me, not so obvious what is going on! If I inherited this code, I'd have some work cut out for me to understand it.

fs = @(A) max(A(imdilate(A == 0, [1 1 1])));
fs(A)
ans =
     8

Good for Only Positive Values

Steve then noticed his solution was only good for positive values.

B = [5 4 -1 0 -2 0 -5 8];
fs(B)
ans =
     0

Steve's Second Answer

So Steve tried again. Again, though I understand what a mask is, I don't fully comprehend the solution, and would probably resort to reading the imdilate documentation from Image Processing Toolbox.

zero_mask = (B == 0);
adjacent_to_zero_mask = imdilate(zero_mask, [1 0 1]);
max_value_adjacent_to_zero = max(B(adjacent_to_zero_mask))
max_value_adjacent_to_zero =
    -1

Doug's answer

Here we see Doug's answer, written without dependence on anything but MATLAB. Very nice solution, but I have to think about it a bit. Again, I know what a mask is, I see we are looking for NaN values after division by 0, so we'll find the zeros, convolve to get the neighbors of the zeros, and find the maximum of those. Whew! I'm working hard now but I get it.

mask = isnan(conv(1./B,[0 1 0],'same'));
fd = @(B) max(B(isnan(conv(1./B,[0 1 0],'same'))));
fd(B)
ans =
    -1

Jos' Solution Next

The next suggestion that appeared is from Jos. It's dense, but ultimately comprehensible to me. First, create a 2 row vector with the data padded at each end with the value 1, and aligned so the first value is above the 3rd, the 2nd above the 4th, etc. (i.e., shifted over by 2 elements). Check each column to see if any of the values are 0. Select the data from the columns with a zero value, and find the maximum of those values.

fj = @(X) max(X(any([1 X(1:end-1) ; X(2:end) 1]==0)))
fj(A)
fj(B)
fj = 
    @(X)max(X(any([1,X(1:end-1);X(2:end),1]==0)))
ans =
     8
ans =
    -1

Start Over

My turn to start over and think about the problem. First, I want to find the indices of zeros in the array. From there, I construct an array of the neighbors.

zind = find(B==0)
maxcandidates = [zind-1 zind+1]
zind =
     4     6
maxcandidates =
     3     5     5     7

Next, I weed out values outside the range (possibly the first and last).

maxcandidates = maxcandidates((maxcandidates > 0) & (maxcandidates <= length(B)))
maxcandidates =
     3     5     5     7

Now I index into my candidates and get the maximum value!

max(B(maxcandidates))
ans =
    -1

As Chef Tell would say, "Very simple, very easy."

I could write this algorithm in one statement, but I fear it would immediately lose clarity.

Repeated Zeros - Clarity of Problem Statement

I got to wondering what result the originator of the question had in mind if there were repeated zeros. Why? Because I could possibly get a zero result? Was that intended? All the solutions here allow that, so mine does too and I don't need to do anything more. Let's check what happens.

C = [1 2 -4 0 0];
fj(C)
ans =
     0

Your Thoughts?

It can definitely be fun and challenging to write nifty, compact, and very dense one-liners in MATLAB. But the horror of going back to that code later, deciphering it so it can be updated or whatever, is not for me typically. I like to write code where the comments don't have to be a lot longer than the code! What are your thoughts? Post them here.


Get the MATLAB code

Published with MATLAB® 7.8

19 Responses to “Find Largest Positive Value Next to Zero”

  1. Urs (us) Schwarz replied on :

    loren, don’t take the fun out of CSSM…

    we know: whilst everybody is bragging with undecipherable golfers in the NG - when back in their own kitchen, they prepare the meal in edible pieces…

    as ever with a :-)
    urs

  2. Rafael Hernandez-Walls replied on :

    Hi,

    For code for: Find Largest Positive Value Next to Zero

    with: Repeated Zeros

    I think that next code resolve that question!

    C = [1 2 -4 0 0 -5 4 -2 0 0 -6];
    zind = find(C==0)
    maxcandidates = [zind-1 zind+1]
    maxcandidates = maxcandidates((maxcandidates > 0) & (maxcandidates <= length(C)))
    nz=find(C(maxcandidates)~=0)
    max(C(maxcandidates(nz)))
    

    Sorry but I just learn to write in english

    R.

  3. George Kapodistrias replied on :

    Hi Loren,

    Thank you for your post. I absolutely agree with you! Condensed code can be useful for prototyping which is essentially throw-away code. In a decent SW implementation, such compact code is counterproductive because it is usually unreadable, not always backwards compatible, prone to errors, and makes debugging frustrating and almost always results is extensive recode. I prefer to stick with established and recognizable programming constructs along with meaningful comments. I write SW that are going to be used and possibly augmented by other people. Such work should not become a nightmare!

    gk.

  4. Thomas replied on :

    What’s wrong about this simple code line?

    B = [5 4 -1 0 -2 0 -5 8];
    min(B(B>0))
    
  5. Loren replied on :

    Thomas-

    The problem with your solution is that it gives the wrong answer, if I’m not mistaken:

    >> B = [5 4 -1 0 -2 0 -5 8];
    min(B(B>0))
    ans =
         4
    

    4 isn’t next to a zero, for starters.

    –Loren

  6. Loren replied on :

    Rafael-

    Your solution seems fine except it doesn’t get the answer 0 when there are duplicate 0 neighbors and 0 is the largest neighbor to a 0.

    –Loren

  7. Mike Koets replied on :

    How about this:

    max(c(intersect(find(c),[find(c==0)+1 find(c==0)-1])))
    

    The “intersect(find(c), …” rules out runs of zeros as well as out of bounds indexes. The max handles positive and negative values. I don’t like calling “find(c==0)” twice, but there’s probably a way around that. Could always make it two lines of code.

  8. Loren replied on :

    Mike-

    I think it doesn’t give the right answer for this case.

    >> C = [1 2 -4 0 0];
    >> max(c(intersect(find(c),[find(c==0)+1 find(c==0)-1])))
    ans =
        -4
    

    but the answer should be 0.

    –Loren

  9. matt fig replied on :

    Here’s my crack at it. I don’t think it is too abstruce.

     
    
    zr = findstr(A,0);
    mx = max(A(zr(zr>1)-1));  % Max on left.
    mx = max([mx,A(zr(zr<length(A))+1)]);  
    
     
  10. Loren replied on :

    Matt-

    Your solution is certainly viable as well!

    –Loren

  11. Mike Koets replied on :

    If we do want to allow answers of zero (I assumed we did not want that), then we can use this code

    max(c(intersect(1:length(c),[find(c==0)+1 find(c==0)-1])))

    Now the intersect just removes out of bounds results.

  12. Pär Lansåker replied on :

    It is often better to use an obvious path for understanding. I would use a combinationd of diff(A) (find the largest, not forgetting the negative left hand side) and A==0 (adjecent to 0).

  13. Ben replied on :

    I think Mike’s answer is probably the most intuitive, but I would have answered it like Jos only using “|” instead of “any”.

    fb = @(X)max(X([1,X(1:end-1)]==0 | [X(2:end),1]==0))
    

    An even shorter code is

    fb2 = @(X)max(X([1,X(1:end-1)].*[X(2:end),1]==0))
    
  14. Jos replied on :

    Again an interesting blog! However, the title of this blog is somewhat misleading. Shouldn’t the word “positive” be removed?

    - jos

  15. Loren replied on :

    Jos-

    You’re right about the title!

    Ben-

    Thanks for another solution.

    –Loren

  16. Thijs replied on :

    My option would be the following:

    max(A([false A(1:end-1)==0]|[A(2:end)==0 false]))
    

    or the identical, but slightly more compact:

    max(A([0 A(1:end-1)==0]|[A(2:end)==0 0]))
    

    pretty similar to what already has been posted though.

  17. Brett Shoelson replied on :

    Hi Loren,
    Late to the game–have been on vacation–but wanted to throw my solution into the mix. Cheers!
    Brett

    First, pad B, then use |BSXFUN|:

    C = [-Inf B -Inf];
    max(max(C(bsxfun(@plus,find(~C),[-1,1]'))))
    
  18. Akiel replied on :

    How about this:
    max(A(logical(conv(~A+0, [1 0 1], ’same’))))
    I haven’t looked at any of the other solutions, so maybe someone’s already done this.

  19. OysterEngineer replied on :

    Loren,

    Your solution is clear to follow. i.e., the code demonstrates what it is doing.

    The solutions that are shown from Steve, Doug & Jos are not clear. Certainly it can be fun to study them. But, I would never use them in real code.

    I really think that Djykstra was correct in saying that the code should demonstrate that it is correct.

Leave a Reply

Wrap code fragments inside <pre> tags, like this:

<pre class="code">
a = magic(3);
sum(a)
</pre>

If you have a "<" character in your code, either follow it with a space or replace it with "&lt;" (including the semicolon).


Loren Shure works on design of the MATLAB language at The MathWorks. She writes here about once a week on MATLAB programming and related topics.

  • Jun: I totally can not believe it, Loren. You are really helpful. Thank you so much, MATLAB master!
  • Loren: Wow folks- Always lots of interest when there’s a quickie to try out! I will only make 2 general...
  • Loren: Jun- ismember is your friend here: >> [aa,ind] = ismember(Array2,Arra y1) aa = 1 1 1 1 1 1 1 ind = 1 2 1 4 4 3...
  • Dan: I like the first way better than the second way. Combining the arrays into one and running any is nice, although...
  • James Myatt: How about I = (a == 0 | b == 0); a(I) = []; b(I) = [];
  • Tunc: Hello Loren, love your blog because of such inspiring and challenging comments to such ’small’...
  • Pekka Kumpulainen: Here is my tradeoff. I usually want to keep the original variables as they are most probably...
  • Iain: Followup: Of course, to allow NaNs (counting them as non-zero): mask = (a~=0) & (b~=0); The mask says “a...
  • Matt Fig: I would usually go with something like this: y = a&b; x = a(y); y = b(y); But I was surprised to find...
  • kk: c=all([a;b]) a(c) a(b)

These postings are the author's and don't necessarily represent the opinions of The MathWorks.