Steve on Image Processing and MATLAB

Concepts, algorithms & MATLAB

Learning Lessons from a One-Liner 7

Posted by Steve Eddins,

My colleague Greg Wilson, creator of the course Software Carpentry, proposed a couple of programming problems as prerequisites for the course. The idea is that if you can solve the problems using any language at all, then you're ready for the course.

Here's Problem 1:

"Write a function that takes a list or array of numbers as input and return the largest number that is adjacent to a zero. For example, if the input is:

   [1, 5, 3, 0, 2, 7, 0, 8, 9, 1 0]

the output is 8."

When I saw this problem I immediately fired off an e-mail to Greg with a MATLAB one-liner:

   max(a(imdilate(a == 0, [1 1 1])))
a = [1, 5, 3, 0, 2, 7, 0, 8, 9, 1 0];
max(a(imdilate(a == 0, [1 1 1])))
ans =

     8

Of course, one common problem with one-liners tossed off by "experts" is that they are so often wrong. And my answer above is no exception! If all the nonzero elements adjacent to 0s in the input vector are negative, the above solution would incorrectly return 0.

b = [5 4 -1 0 -2 0 -5 8];
max(b(imdilate(b == 0, [1 1 1])))
ans =

     0

Greg raised two questions in response to my e-mail:

  • How many MATLAB users would be able to understand that solution?
  • How many MATLAB users would be able to come up with it on their own?

Well, I hope by writing this blog to increase the number of MATLAB and Image Processing Toolbox users who could come up with it on their own. But let me tackle Greg's first question - how many users would understand the solution?

I think the answer is "not very many." That's because my code does not clearly express my intent. This is a common problem with one-liners.

I suspect that most MATLAB programmers go through a phase of being overly enamored of the one-liner. (Some never leave that phase!) Now that I've been in the software development business for quite a while, I've (mostly) lost my fondness for the one-liner. I'll still write code that way if I'm experimenting at the MATLAB command prompt, but in code that's going to ship I'm much more likely to break that expression into parts and give each part a meaningful name. I might write it like this (including the bug fix):

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

     0     0     0     1     0     0     1     0     0     0     1

adjacent_to_zero_mask = imdilate(zero_mask, [1 0 1])
adjacent_to_zero_mask =

     0     0     1     0     1     1     0     1     0     1     0

max_value_adjacent_to_zero = max(a(adjacent_to_zero_mask))
max_value_adjacent_to_zero =

     8

Is this solution understandable? Well, it's more understandable than the first version, but it does assume that the code reader is familiar with a few key concepts:

  • MATLAB logical indexing. If you do image processing in MATLAB and you're not familiar with logical indexing, stop right now and go read my short tutorial on the subject. We'll wait here until you get back.
  • The use of the term "mask" to mean a logical matrix indicating which elements in another matrix satisfy some property.
  • The use of binary image dilation (or binary image erosion) to find pixels that are adjacent to a specified set of pixels.

And maybe you need to be comfortable with the idea of applying image processing operators to things that don't appear to be images, such 1-by-11 vectors. :-)

I have used logical indexing and binary image dilation and erosion in countless ways to do useful things in MATLAB, sometimes with images and sometimes not. I strongly recommend that you add these ideas to your bag of tricks.


Get the MATLAB code

Published with MATLAB® 7.8

Note

Comments are closed.

7 CommentsOldest to Newest

Ken Eaton replied on : 1 of 7
Not sure how much easier it is to understand than yours, but this is the first answer I came up with:
>> a = [1 5 3 0 2 7 0 8 9 1 0];
>> max(a(find([diff(a) 0] & (a==0))+1))

ans =

     8

>> b = [5 4 -1 0 -2 0 -5 8];
>> max(b(find([diff(b) 0] & (b==0))+1))

ans =

    -2

However, I thought of another strange case. What should the output be for this?
c = [1 2 -4 3 0 0];
Ken Eaton replied on : 2 of 7
Oops! I misunderstood... for some reason I read it as "the largest value AFTER a 0", but the values before the 0 count too. My bad!
Doug Schwarz replied on : 4 of 7
Steve, Anyone who has ever been in a position to support code written by others will soon learn to hate one-liners and I applaud your efforts to discourage this practice. However, in the spirit of the blog, here's an idiom that can be used to detect entries adjacent to a 0 and does not require the IPT:
isnan(conv(1./a,[0 1 0],'same'))
It's then a simple matter to use this as a mask and then apply max:
max(a(isnan(conv(1./a,[0 1 0],'same'))))
Doug
Ken Eaton replied on : 6 of 7
It never ceases to amaze me how engrossing a simple one-line challenge can be. It certainly highlights how ugly and confusing a one-line solution often gets when you try to account for every possible case. For example, in trying to extend Steve's original solution to handle the additional test (vector "b") that he added, as well as the additional test below:
c = [1 2 -4 0 0];  % repeated 0's and negative surrounds
I ended up with the horrendous one-liner:
max([c(imdilate(c == 0,[1 1 1]) & c) c(imerode(c == 0,[1 1 1]))])
This works for all 3 cases (a, b, and c), but is largely incomprehensible. Kudos to Doug for coming up with a solution that already satisfied my additional case.