C++: newton raphson и функции за претоварване

Написах проста реализация на алгоритъма за намиране на корен на newton raphson, който приема първоначално предположение init, унарна функция f и толеранса tol като аргументи, както е показано по-долу:

bool newton_raphson(double& init,
                    double(*f)(double),
                    double tol){
    const int max_iter = 10000;
    double next_x, soln = init;
    int i = 0;

    while(++i < max_iter){
        next_x = soln - f(soln)/fp_x(f, soln);
        if(fabs(next_x - soln) < tol){
            init = next_x;
            return true;
        }
        soln = next_x;
    }
    return false;
}

double fp_x(double(*f)(double),
            double x){
    const double h = 0.000001;
    return (f(x + h) - f(x - h))/2.0/h;
}

Въпросът ми е: въпреки че това работи перфектно за унарни функции, бих искал да променя имплементацията, така че да работи за функции f, които имат повече от един параметър, но всички освен един параметър имат постоянни стойности. За пояснение: ако имам функция f(x) = 3x + 2, както е показано по-долу

double f(double x){
    return (3*x + 2);
}

Тогава моето внедряване работи. Бих искал обаче да работи за всякакви функции с произволен брой аргументи, но само първият аргумент е променлив. И така, ако имам функция f(x,y) = 3x + 2y

double f(double x, double y){
    return (3*x + 2*y);
}

Бих искал да намеря корена на f(x,2) или f(x,3), използвайки същата функция и така нататък за n аргумента, а не само за един или два (моля, игнорирайте идеята, че функциите, които показах в примерът са прости линейни функции, това е само пример). Има ли някакъв начин да внедря функцията за различен брой аргументи или трябва да напиша изпълнение за всеки случай?

Благодаря,

NAX

ЗАБЕЛЕЖКА

Както можете да разберете досега, този въпрос всъщност не е за newton-raphson, но го улеснява, ако го използвам като пример за действителния въпрос, който е единична реализация за функции с различен брой аргументи.

АКТУАЛИЗАЦИЯ

Няколко отговора по-долу използват std::bind и std::function за решаване на проблема, което всъщност по-добре отговаря на моя въпрос, отколкото избрания отговор; те обаче са класове/функции на библиотеки c++11 (което, не ме разбирайте погрешно, е нещо, което силно призовавам всеки програмист на c++ да продължи напред и да научи) и по време на това писане се сблъсках с някои проблеми използването им; Eclipse Juno, използващ g++ 4.7 (който е съвместим с c++11), все още по някакъв начин не успя да разпознае std::function и затова реших да се придържам към отметнатия отговор по-долу, който също работи добре.


person naxchange    schedule 18.03.2013    source източник
comment
ако намерите корените до n тогава не е ли честно да трябва да върнете всички тези корени през контейнер? или искате да изчислите 3-тите корени, например, при всяко повикване?   -  person Koushik Shetty    schedule 18.03.2013
comment
Мисля, че приехте отговор твърде бързо.   -  person Apprentice Queue    schedule 18.03.2013


Отговори (4)


Мисля, че питате за различни функции:

Променлива функция – функция, декларирана със списък с параметри, завършващ с многоточие (...) – може да приема различен брой аргументи от различни типове. Различните функции са гъвкави, но са и опасни. Компилаторът не може да провери дали дадено извикване на variadic функция предава подходящ брой аргументи или че тези аргументи имат подходящи типове. Следователно, извикване по време на изпълнение на variadic функция, която предава неподходящи аргументи, води до недефинирано поведение. Такова недефинирано поведение може да се използва за изпълнение на произволен код. Оттук:

https://www.securecoding.cert.org/confluence/display/cplusplus/DCL31-CPP.+Do+not+define+variadic+functions

Въпреки това, както е цитирано по-горе, има редица проблеми с тях.

Най-вече работи само за време за компилиране!

Ако обаче се интересувате от прилагането на такъв, ето статия с хубав пример:

http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=138

АКТУАЛИЗАЦИЯ:

IMO, мисля, че е по-добре да дефинирате функции, които приемат структурни или обектни аргументи (т.е. общ функционален обект), и да пишете функции, които работят с тези аргументи изрично.

Другата опция е да направите малко отражение по време на компилиране - което би било полезно, но е твърде много трудности за правене в такъв пример като този. Освен това „отражението“ в C++ не е „истинско“ отражение, а по-скоро лошо и непълно негово изпълнение.

person jrd1    schedule 18.03.2013

За това, което се опитвате да направите тук, това, което търсите, е std::bind (или, ако имате работа с C++03 компилатор, std::bind1st и std::bnd2nd).

Те ще ви позволят да "свържете" стойности с другите параметри, оставяйки ви с функция (технически функционален обект), която изисква само един параметър.

Това, което бихте искали в идеалния случай, би било нещо подобно:

double f(double x, double y) { 
     return 3*x + 2*y;
}

double init = 1.0;

newton_raphson(init, std::bind2nd(f, 3), 1e-4);

За съжаление, в реална употреба не е толкова просто -- за да работите с std::bind2nd, не можете да използвате действителна функция; вместо това трябва да използвате функционален обект и той трябва да произлиза от std::binary_function.

std::bind е доста по-гъвкав, така че почти със сигурност искате да използвате вместо него (ако изобщо е възможно).

person Jerry Coffin    schedule 18.03.2013

Използвах вашия въпрос като начин да се принудя да науча променлив шаблон на C++11, ето един работещ пример.

template< typename... Ts >
double f( Ts... Vs ) {
    double array[] = { Vs... };
    int numArg = sizeof...( Vs );
    switch (numArg) {
    case 1:
        return 3 * array[0] + 2;
    case 2:
        return 3 * array[0] + 2 * array[1];
    case 3:
        return 3 * array[0] + 2 * array[1] + 1 * array[3];
    ....
    default:
        return 0.0;
    }
}

template< typename... Ts >
double newton_raphson( double &init, double tol,
                       double (*func) ( Ts... Vs ), Ts... Vs ) {
    return func( Vs... );
}

можете да го наречете така

newton_raphson( &init, 1.0, f, 1.0, 2.0, 3.0, 4.0, 5.0 );
person yngccc    schedule 18.03.2013

Можете да използвате std::bind и std::function. Типът std::function<double(double)> представлява функционалност, която приема двойно и връща двойно. По подобен начин std::function<double(int,int)> е за функционал, който взема 2 int и връща двойно.

#include <functional>

bool newton_raphson(double& init,
                    std::function<double(double)>& f,
                    double tol){
    const int max_iter = 10000;
    double next_x, soln = init;
    int i = 0;

    while(++i < max_iter){
        next_x = soln - f(soln)/fp_x(f, soln);
        if(fabs(next_x - soln) < tol){
            init = next_x;
            return true;
        }
        soln = next_x;
    }
    return false;
}

double myfunction(double x, double y){
    return (3*x + 2*y);
}
double fp_x(std::function<double(double)> f, double x) {
    ...
}
...
double d = 1.0;
// Here we set y=2.5 and we tell bind that 1st parameter is unbounded
// If we wanted to switch and set x=2.5 and let y be unbounded, then
// we would use  (&myfunction, 2.5, std::placeholders::_1)
newton_raphson(d, std::bind(&myfunction, std::placeholders::_1, 2.5) , 1e-6);
...
person Apprentice Queue    schedule 18.03.2013
comment
Благодаря за този отговор, всъщност предпочитам този, защото изглежда, че адресира въпроса ми директно, но имам няколко въпроса: първо, трябва ли шаблонната променлива function да има идентификатор (например в декларацията си вътре в fp_x )? И ако не, какъв би бил неговият синтаксис? Освен това опитвам това с помощта на g++ и използването на function<double(double)> ми дава грешката: Symbol 'function' could not be resolved. Знаеш ли защо? - person naxchange; 18.03.2013
comment
Опитах също това, използвайки g++ 4.7 на Ubuntu 12.10, използвайки -std=c++11 и все още не е решено. Използвам eclipse juno - person naxchange; 18.03.2013
comment
@naxchange, да, за съжаление това е C++11. Предполагам, че сте включили ‹функционален›? - person Apprentice Queue; 18.03.2013