Скажем, у меня есть две матрицы:
A = [1;
2;
3;
4;
5]
и
B = [1 1 2 1;
2 3 3 4;
5 5 5 5;
1 4 4 4;
5 5 1 2]
Мне нужна результирующая матрица, показывающая, сколько раз элемент из A появлялся в соответствующей строке в B, например:
C = [3;
1;
0;
3;
2]
В настоящее время я делаю это, используя ismember внутри цикла for и добавляя результат для каждой строки. Но это занимает вечность из-за размера моих матриц. Есть ли способ сделать это без цикла?
Редактирование, чтобы показать мой текущий код:
for i=1:1:length(A)
C(i) = sum(ismember(B(i,:),A(i)),2);
end
Но на самом деле мои матрицы содержат более 500 тысяч строк, и мне бы хотелось сделать этот код более эффективным, убрав цикл.
Спасибо, Крис! Я добавил свой код в вопрос. Надеюсь, это поможет
@CrisLuengo По моему опыту, хотя их скорость улучшилась, во многих ситуациях циклы по-прежнему работают медленнее, чем векторизованный код. Судя по времени Вулфи, похоже, это именно тот случай.
Короткий ответ
Вы можете просто использовать ==
, который будет использовать неявное расширение для сравнения каждого элемента A
со всей строкой B
и вывода логического массива совпадений, а затем суммирования по каждой строке для подсчета совпадений.
C = sum( A == B, 2 );
В зависимости от того, как генерируются A
и B
, часто полезно избегать прямых сравнений равенства чисел с плавающей запятой (см. здесь), поэтому может быть более надежным убедиться, что A
и B
находятся в пределах некоторого небольшого допуска, то есть
tol = 1e-8;
C = sum( abs(A-B) < tol, 2 );
При вычитании здесь будет использоваться неявное расширение, такое же, как ==
в первом примере, для распространения A
по столбцам B
.
Бенчмаркинг
Ради интереса я провел сравнительный анализ четырех случаев.
C
, поэтому он растет вместе с циклом.C
в виде массива NaN
правильного размера. Обычно это хороший способ ускорить циклы. Ускорение против (1) ~18%.==
вместо ismember
, поскольку вы сравниваете целые числа, я ожидаю, что ==
будет быстрее, чем ismember
. Ускорение против (1) ~62%.==
и abs(..)<tol
одинаково быстры, поэтому я показал только последний. Ускорение против (1) ~94%.Результаты для тестового примера с length(A) = 1e6
и 100 столбцами в B
:
With loop and ismember: 0.65sec
With preallocated loop and ismember: 0.53sec
With preallocated loop and ==: 0.25sec
With no loop and ==: 0.04sec
Ваш результат может варьироваться в зависимости от этой экономии времени в зависимости от размеров входной матрицы, ожидаемого количества совпадений и т. д. Полный код ниже, чтобы вы могли протестировать другие случаи.
Код:
N = 1e6;
A = (1:N).';
B = randi([1,N],N,1e2);
t = [
timeit( @() withloop(A,B) )
timeit( @() withloop_prealloc(A,B) )
timeit( @() withloop_prealloceq(A,B) )
timeit( @() noloop(A,B) )
];
fprintf( 'With loop and ismember: %.2fsec\n', t(1) );
fprintf( 'With preallocated loop and ismember: %.2fsec\n', t(2) );
fprintf( 'With preallocated loop and ==: %.2fsec\n', t(3) );
fprintf( 'With no loop and ==: %.2fsec\n', t(4) );
function withloop(A,B)
for i=1:1:length(A)
C(i) = sum(ismember(B(i,:),A(i)),2);
end
end
function withloop_prealloc(A,B)
C = NaN(length(A),1);
for i=1:1:length(A)
C(i) = sum(ismember(B(i,:),A(i)),2);
end
end
function withloop_prealloceq(A,B)
C = NaN(length(A),1);
for i=1:1:length(A)
C(i) = sum(B(i,:) == A(i),2);
end
end
function noloop(A,B)
C = sum( abs(A-B)<1e-8, 2 );
end
Именно то, что мне нужно! Огромное спасибо за помощь и очень подробный ответ, Вольфи! Ты лучший!
Циклы в MATLAB не медленные. Они были 20 лет назад, но это было очень давно. Вы не хотите делать это без циклов, вы хотите ускорить свой код. Покажите нам свой код, возможно, мы сможем подсказать, как его ускорить.