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
Comments are closed.
7 CommentsOldest to Newest
>> 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 = -2However, I thought of another strange case. What should the output be for this?
c = [1 2 -4 3 0 0];
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
c = [1 2 -4 0 0]; % repeated 0's and negative surroundsI 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.
max(X(any([1 X(1:end-1) ; X(2:end) 1]==0)))Jos