Negating every second entree in a matrix column

My goal is pretty simple: I have an arbitrary sized matrix holding only positive real numbers and there will not be anymore than two nonzero elements per column at any time, for example:
b = [1 0 1; 0 1.2 2; 1 3 0];
or
b = [1 2 1 0; 0 1.2 0 0; 0 0 0 3; 1.6 0 4 2.2];
Now what I want is to negate every second nonzero entree of a column. The solution I came up with works, but is probably not the most elegant and/or efficient:
for n=1:numel(b(1,:))
isPositive = 0;
for m=1:numel(b(:,n))
if (isPositive == 1 && b(m,n) ~= 0)
b(m,n) = b(m,n)*-1;
break;
end
if (abs(b(m,n)) == b(m,n) && b(m,n) ~= 0)
isPositive = 1;
end
end
end
I am quite new to MATLAB, so if anybody knows a more elegant solution, perhaps not involving any for loops, please share.
Thanks in advance

댓글 수: 2

Jan
Jan 2011년 12월 13일
The BREAK wil stop the "for m" loop - is this intented?
Instead of "(abs(b(m,n)) == b(m,n) && b(m,n) ~= 0)" you could write "b(m,n) > 0", but if the matrix b has positive values only at first, the test "abs(b(m,n)) == b(m,n)" is useless.
Why do you check if "b(m,n)~=0" before multiplying with -1? In Matlab "-0" is the same as "0".
Yes, the BREAK is intended, since as soon as the second item has been negated in a column, I don't need to check that column any further (i.e. mission accomplished for this particular column). This should save unnecessary iterations over 0's in case of large matrices, where the two nonzero elements are near the beginning of the column. I see your point with writing b(m,n) > 0 instead. Bit stupid I haven't noticed that myself. The reason I do the whole b(m,n) > 0 check is to flag when I come across the first nonzero, positive element in a column. I then know that the following nonzero, positive element in the same column needs to be negated and once this is done, I can go on to repeat this procedure for the next column. My rationale behind checking if b(m,n) ~= 0 before multiplying with -1 is purely because this saves an unnecessary multiplication operation, since like you say yourself: -0 == 0

댓글을 달려면 로그인하십시오.

 채택된 답변

Andrei Bobrov
Andrei Bobrov 2011년 12월 13일

1 개 추천

idx = find(b)
b(idx(2:2:end)) = - b(idx(2:2:end))
more variant
[i1 j1] = find(b)
[m m] = unique(j1,'first')
k = sub2ind(size(b),i1(m+1) ,j1(m+1))
b(k) = -b(k)

댓글 수: 7

This looks a whole lot nicer for sure. Thanks!
Jan
Jan 2011년 12월 13일
This fails if the number of elements >0 is odd in a column. E.g.:
b = [1 2 1 0; 0 1.2 0 0; 0 0 0 3; 1.6 0 4 2.2; 1 1 1 1];
Jan
Jan 2011년 12월 13일
This function negates every second positive element in the matrix. As far as I understood at first, you want to negate every second positive element for each column. But your comment sounds, like you want to negate one element per column only.
It would have been useful, if you post a small example of the wanted output in addition.
Hi Jan!
idx = find(b)
b(idx(2:3:end)) = - b(idx(2:3:end))
Jan
Jan 2011년 12월 13일
@Andrei: No, this does not help in general. Try this:
b = [1 2 1 0; 0 1.2 0 0; 0 1 1 3; 1.6 0 4 2.2; 1 1 1 1];
The specification is "not more than 2 zeros per column". There can be 0, 1 or 2 zeros and the number of rows can be even or odd. In addition Michael wants to negate one element per column only - if I understand him now. Then "idx=find(b)" cannot work.
this doesn't return the correct result for
b = [1 2 1 0; 0 1.2 0 0; 0 0 0 3; 1.6 0 4 2.2; 1 1 1 1];
it returns
b =
1.0000 2.0000 -1.0000 0
0 1.2000 0 0
0 0 0 3.0000
-1.6000 0 4.0000 -2.2000
instead of
b =
1.0000 2.0000 -1.0000 0
0 -1.2000 0 0
0 0 0 3.0000
-1.6000 0 4.0000 -2.2000
Jan
Jan 2011년 12월 13일
@Michael: Yes, it does not create the correct results.
It is always a good idea to test the code before accepting an answer, even if the code looks nice and even if it comes from a high-skilled programmer like Andrei.

댓글을 달려면 로그인하십시오.

추가 답변 (2개)

Jan
Jan 2011년 12월 13일

0 개 추천

This negates every second element per column:
b = [1 2 1 0; 0 1.2 0 0; 0 0 0 3; 1.6 0 4 2.2];
gt0 = (b > 0);
even = mod(cumsum(gt0, 1) - 1, 2);
idx = and(gt0, even);
b(idx) = -b(idx);
[EDITED]: After reading your comment, I understand, that you want to negate one element per column only:
b = [1 2 1 0; 0 1.2 0 0; 0 0 0 3; 1.6 0 4 2.2];
gt0 = (b > 0);
idx = and(gt0, cumsum(gt0, 1) == 2);
b(idx) = -b(idx);
Or with a cleaner loop:
[nx, ny] = size(b);
for iy = 1:ny
isPositive = false;
for ix = 1:nx
if b(ix, iy) > 0
if isPositive
b(ix, iy) = -b(ix, iy);
break;
else
isPositive = true;
end
end
end
end

댓글 수: 1

I'll go with the cleaner version of my original loop then. Thanks for all your help with this guys, really appreciated and once again apologies for the confusion. So this really should be the accepted answer instead the one marked previously.

댓글을 달려면 로그인하십시오.

Daniel Shub
Daniel Shub 2011년 12월 13일

0 개 추천

While there are probably more efficient, and some would argue elegant, solutions, the real goal should be to do what you think makes the most sense. It is generally a bad idea to spend time trying to speed up sections of code until you know it is a bottle neck.
I like Andrei's solution, but it might be more confusing 6 months later to figure out what it is doing. I think loops often allow for easier documentation. I think a loop like yours with lots of useful comments is a very elegant solution.
My answer:
for column = 1:size(b, 2)
row = find(b(:, column), 1, 'last');
b(row, column) = -b(row, column);
end

댓글 수: 4

Jan
Jan 2011년 12월 13일
The question is not very clear. But as far I understand, Michael wants teh 2nd positive element in each column to be neagted. Then you need:
for column = 1:size(b, 2)
row = find(b(:, column), 2, 'first');
b(row(2), column) = -b(row(2), column);
end
Daniel Shub
Daniel Shub 2011년 12월 13일
@Jan, but according to the question: "there will not be anymore than two nonzero elements per column at any time." As long as there are always exactly 2 non-zero elements the second non-zero element is the last non-zero element. I assumed that there would always be exactly 2 non-zero elements. If there are not, my answer may not give the correct result. The code you put in the comment crashes if there is only one non-zero element, which might be better.
Jan
Jan 2011년 12월 13일
@Daniel: Aaaaarrrgh. "every second" and "not be anymore than 2 nonzero". I still do not get the question completely.
I'm taking a break now and have a cup of coffee. Please fix all problems here. Thanks!
Yes, I wasn't completely clear in my original question, my apologies, so once more: I have a matrix of arbitrary dimensions filled with positive numbers or zeros. Every column of the matrix contains EXACTLY 2 nonzero positive entrees (no more, no less), each at an arbitrary row index. Now what I want is to negate the second of these nonzero positive entrees in every column.

댓글을 달려면 로그인하십시오.

카테고리

도움말 센터File Exchange에서 Matrix Indexing에 대해 자세히 알아보기

제품

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by