Векторизация функции, включающей цикл while или условие if в цикле (Matlab) - PullRequest
3 голосов
/ 05 марта 2012

Допустим, у меня есть функция, которая может вычислять один выход из одного входа, например,

function y = sqrt_newton(x)
    y = x ./ 2;
    yo = y;
    y = 0.5.*(y + x ./ y);
    while abs(y - yo) > eps * abs(y)
        yo = y;
        y = 0.5.*(y + x ./ y);
    end
end

Я хотел бы иметь возможность применить эту функцию к векторному входу, скажем sqrt_newton(2:9), как со встроеннымв функции.Каков наилучший способ добиться этого с помощью условия в начале цикла или некоторого предложения if внутри?Я хотел бы избежать написания дополнительной функции в качестве обертки, чтобы просто перебрать входной вектор, если это вообще возможно.

Мое текущее громоздкое решение

Что ясделать до сих пор:

  • Я должен сначала расширить входные данные до того же размера (используя finargsz из набора инструментов финансы, но если вы знаете другую основную функцию, которая делает то же самое,это было бы здорово)

  • запишите форму, используя size

  • deal входные данные

  • цикл через все входные элементы

  • reshape выход

Кажется, что функция numel устраняет необходимостьвсе это тяжелое занятие, но дополнительные комментарии будут приветствоваться.

Ответы [ 3 ]

2 голосов
/ 05 марта 2012

Ну, вы могли бы векторизовать его следующим образом (с any):

function y = sqrt_newton(x)
    y = x / 2;
    yo = y;
    y = 0.5*(y + x ./ y);
    while any(abs(y - yo) > eps * abs(y))
        yo = y;
        y = 0.5*(y + x ./ y);
    end
end

Тогда вы получите:

>> sqrt_newton(2:9)
ans =
    1.4142    1.7321    2.0000    2.2361    2.4495    2.6458    2.8284    3.0000

>> ans.^2-(2:9)
ans =
   1.0e-14 *
   -0.0444   -0.0444         0    0.0888   -0.0888    0.0888   -0.1776         0

как и ожидалось. Тем не менее, я бы не рекомендовал это делать, поскольку вы выполняете ненужные операции над уже сросшимися элементами. Я бы просто использовал for цикл над x в начале функции:

function yall = sqrt_newton(xall)
yall = zeros(size(xall));
for xn=1:numel(xall)
    x = xall(xn);
    y = x / 2;
    yo = y;
    y = 0.5*(y + x ./ y);
    while abs(y - yo) > eps * abs(y)
        yo = y;
        y = 0.5*(y + x ./ y);
    end
    yall(xn)=y;
end
end

Установите размер yall в начале, чтобы избежать его увеличения по всему циклу.

2 голосов
/ 05 марта 2012

Всегда есть arrayfun.Вы можете сохранить имеющийся у вас код, поместив его во внутреннюю функцию.

function y = sqrt_newton(z)

    y = arrayfun(@inner, z);

    function y= inner(x)
        y = x ./ 2;
        yo = y;
        y = 0.5.*(y + x ./ y);
        while abs(y - yo) > eps * abs(y)
            yo = y;
            y = 0.5.*(y + x ./ y);
        end
    end
end

Редактировать: Преимущество приведенного выше решения состоит в том, что его можно реализовать тривиально после того, как он работает с1x1 вход, но циклы в других ответах намного быстрее для больших входов.Например, на моем компьютере код

tic; sqrt_newton(rand(500)); toc

работает с ~1.24 seconds с моим кодом, 0.06 seconds с кодом @ Рамашаланки и 0.28 seconds с кодом @ GuntherStruyf.

2 голосов
/ 05 марта 2012

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

Для вашего примера я бы использовал следующее:

function y = sqrt_newton(x)
    y = x ./ 2;
    yo = y;
    y = 0.5.*(y + x ./ y);
    for i=1:numel(x)
        while abs(y(i) - yo(i)) > eps * abs(y(i))
            yo(i) = y(i);
            y(i) = 0.5*(y(i) + x(i) / y(i));
        end
    end
end

Я использую число вместо размера, чтобы он мог обрабатывать любой массив, который я ему выброшу

...