MATLAB – Сравнение каждой строки двух матриц с использованием ismember без цикла

Скажем, у меня есть две матрицы:

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 тысяч строк, и мне бы хотелось сделать этот код более эффективным, убрав цикл.

Циклы в MATLAB не медленные. Они были 20 лет назад, но это было очень давно. Вы не хотите делать это без циклов, вы хотите ускорить свой код. Покажите нам свой код, возможно, мы сможем подсказать, как его ускорить.

Cris Luengo 22.08.2024 06:03

Спасибо, Крис! Я добавил свой код в вопрос. Надеюсь, это поможет

user26946861 22.08.2024 06:22

@CrisLuengo По моему опыту, хотя их скорость улучшилась, во многих ситуациях циклы по-прежнему работают медленнее, чем векторизованный код. Судя по времени Вулфи, похоже, это именно тот случай.

Luis Mendo 22.08.2024 12:45
За пределами сигналов Angular: Сигналы и пользовательские стратегии рендеринга
За пределами сигналов Angular: Сигналы и пользовательские стратегии рендеринга
TL;DR: Angular Signals может облегчить отслеживание всех выражений в представлении (Component или EmbeddedView) и планирование пользовательских...
Sniper-CSS, избегайте неиспользуемых стилей
Sniper-CSS, избегайте неиспользуемых стилей
Это краткое руководство, в котором я хочу поделиться тем, как я перешел от 212 кБ CSS к 32,1 кБ (сокращение кода на 84,91%), по-прежнему используя...
2
3
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Короткий ответ

Вы можете просто использовать ==, который будет использовать неявное расширение для сравнения каждого элемента A со всей строкой B и вывода логического массива совпадений, а затем суммирования по каждой строке для подсчета совпадений.

C = sum( A == B, 2 );

В зависимости от того, как генерируются A и B, часто полезно избегать прямых сравнений равенства чисел с плавающей запятой (см. здесь), поэтому может быть более надежным убедиться, что A и B находятся в пределах некоторого небольшого допуска, то есть

tol = 1e-8;
C = sum( abs(A-B) < tol, 2 );

При вычитании здесь будет использоваться неявное расширение, такое же, как == в первом примере, для распространения A по столбцам B.


Бенчмаркинг

Ради интереса я провел сравнительный анализ четырех случаев.

  1. Код точно такой же, как указано в вашем вопросе, который представляет собой цикл for без предварительного выделения выходного массива C, поэтому он растет вместе с циклом.
  2. Тот же код, но предварительное выделение C в виде массива NaN правильного размера. Обычно это хороший способ ускорить циклы. Ускорение против (1) ~18%.
  3. Тот же предварительно выделенный цикл, но с использованием == вместо ismember, поскольку вы сравниваете целые числа, я ожидаю, что == будет быстрее, чем ismember. Ускорение против (1) ~62%.
  4. Приведенный выше код вообще не использует циклы. Подходы == и 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

Именно то, что мне нужно! Огромное спасибо за помощь и очень подробный ответ, Вольфи! Ты лучший!

user26946861 22.08.2024 16:05

Другие вопросы по теме