Komputasi rekursif otomatis

1. Perkenalan


Dampak subrutin (subrutin bahasa Inggris) pada pemrograman tanpa berlebihan sangat besar. Diperkenalkan pada awal pemrograman, mereka tidak kehilangan relevansinya hingga hari ini. Tanpa mereka, pemrograman praktis tidak mungkin dibayangkan. Meskipun dari sudut pandang formal, mereka tidak begitu diperlukan, karena Teori murni lebih tertarik pada sifat-sifat algoritma daripada ukurannya.

Dalam teori automata, konsep nested automata, yang menjadi dasar praktik subprogram otomat (AMP), jarang dibahas. Suatu organisasi hierata automata automata yang serupa, jika dipertimbangkan, sangat dangkal. Salah satu alasan untuk hubungan ini mungkin karena kompleksitas penerapan hierarki bersarang di tingkat perangkat keras [1, 2].

Pemrograman lebih fleksibel dan menyediakan lebih banyak fitur daripada praktik merancang sirkuit digital. Kita harus memastikan ini, mempertimbangkan lebih lanjut implementasi perangkat lunak automata bersarang, dan kemudian konsep algoritma rekursif otomat.

Untuk semua masalah khusus dalam membentuk model robot bertingkat, definisi formalnya tidak menyebabkan masalah. Namun, di sisi lain, pilihan untuk membangun model hirarki tentu akan berdampak signifikan pada implementasi perangkat lunaknya.

2. Algoritma rekursif otomatis


Dalam artikel sebelumnya [3], hanya definisi formal dari model kontrol program otomatis (AP) yang diberikan tanpa mempertimbangkan penerapannya dan contoh penggunaan khusus. Kemungkinan menerapkan algoritma rekursif juga disebutkan. Selanjutnya, dengan menggunakan contoh perhitungan faktorial, pertama, kami akan mempertimbangkan implementasi perangkat lunak dari mekanisme untuk membuat rutinitas otomatis dalam kerangka paradigma objek automaton C ++, dan kedua, karena kami akan mempertimbangkan algoritma rekursif, kami pada dasarnya akan menetapkan prinsip umum untuk mengimplementasikan hal tersebut. algoritma dalam kerangka pemrograman otomatis.

Menggunakan fungsi rekursif dalam API bisa sangat sederhana. Ini menunjukkan kode untuk daftar 1 program mesin objek. Di sini kelas otomat FSimpleFactorial bukan proses, tetapi rutin otomat, karena berisi status akhir "00" (untuk detail lebih lanjut tentang subprogram lihat [3]). Pada tingkat perilaku, objek otomat yang dibuat sesuai dengan otomat dengan satu transisi tanpa syarat dari keadaan awal "f0" ke keadaan akhir "00" dan panggilan untuk transisi ini dalam kerangka kerja y1 dari fungsi faktorial faktorial biasa (faktorial (...)).

Daftar 1. Memanggil fungsi faktorial dari AMS
#include "lfsaappl.h"

class FSimpleFactorial : public LFsaAppl
{
public:
    double dF{1};		//	 
    FSimpleFactorial(int n);
    virtual ~FSimpleFactorial() {};
private:
    int nN{0};
    void y1();
    double factorial(int n);
};

#include "stdafx.h"
#include "FSimpleFactorial.h"

LArc TT_SimpleFactorial[] = {
    LArc("f0", "00", "--",	"y1"),		//
    LArc()
};
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
FSimpleFactorial::FSimpleFactorial(int n)
    :LFsaAppl(TT_SimpleFactorial, "FSimpleFactorial")
{
    nN = n;
}
//
void FSimpleFactorial::y1() { dF = factorial(nN); }

double FSimpleFactorial::factorial(int n)
{
    if (n == 0) return 1;
    else return n*factorial(n-1);
}


Karena objek FSimpleFactorial adalah automaton bersarang (subroutine), harus ada "pembungkus" untuk itu - proses yang memanggilnya. Contohnya adalah proses yang muncul dari objek bernama FTskSimple, yang kodenya ditunjukkan pada Listing 2.

Daftar 2. Proses untuk membuat otomat bersarang
#include "lfsaappl.h"

class FTskSimple : public LFsaAppl
{
public:
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FTskSimple(nameFsa); }
    bool FCreationOfLinksForVariables();
    FTskSimple(string strNam);
    virtual ~FTskSimple();

    CVar    *pVarF;
    CVar    *pVarN;
    CVar    *pVarStart;

    LFsaAppl *pFCall{nullptr};
protected:
    int x1();
    void y1(); void y2(); void y3();
};

#include "stdafx.h"
#include "FTskSimple.h"
#include "FSimpleFactorial.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
LArc TT_Task[] = {
    LArc("st", "t1", "--",	"y1"),	//
    LArc("t1", "t2", "x1",	"y3"),	//
    LArc("t2", "t1", "--",	"y2"),	//
    };

FTskSimple::FTskSimple(string strNam)
    :LFsaAppl(TT_Task, strNam)
{
}

FTskSimple::~FTskSimple() { if (pFCall) delete pFCall; }

bool FTskSimple::FCreationOfLinksForVariables() {
    pVarF = CreateLocVar("dF", CLocVar::vtDouble, "factorial value");
    pVarN = CreateLocVar("nN", CLocVar::vtInteger, "input value");
    pVarStart = CreateLocVar("bStart", CLocVar::vtBool, "start?");
    return true;
}

int FTskSimple::x1() { return pVarStart->GetDataSrc(); }

void FTskSimple::y1()
{
    // reset flag start
    pVarStart->SetDataSrc(this, 0.0);
    // creating and calling a subroutine
    pFCall = new FSimpleFactorial(pVarN->GetDataSrc());
}
void FTskSimple::y2()
{
    // display factorial value
    pVarF->SetDataSrc(this, static_cast<FSimpleFactorial*>(pFCall)->dF);
}

void FTskSimple::y3()
{
    // reset flag start
    pVarStart->SetDataSrc(this, 0.0);
    static_cast<FSimpleFactorial*>(pFCall)->nN = pVarN->GetDataSrc();
    // creating and calling a subroutine
    pFCall->FCall(this);
}


Proses ini pada transisi dari keadaan awal "st" ke keadaan "t1" menciptakan objek bertipe FSimpleFactorial, yang akan menjadi dasar untuk membuat robot bersarang (dalam arti biasa, memanggil subprogram). Selanjutnya, berdasarkan pada keadaan saat ini dari variabel lokal dari tipe Boolean bStart (lihat juga kode predikat x1), itu menyebabkan transisi dari keadaan "t1" ke "t2". Pada transisi ini, tindakan y1, pertama, me-reset nilai variabel lokal ini (untuk mencegah restart salah) dan, kedua, memanggil prosedur FCall, yang menciptakan automaton bersarang.

Penerjemah automata dari lingkungan VKPA mengatur transisi ke keadaan awal "f0" dari automaton yang disematkan dan keadaan yang terakhir menjadi keadaan proses saat ini. Transisi automaton bersarang ke kondisi akhir β€œ00” mengembalikan perhitungan ke level sarang sebelumnya, dan dalam kasus kami, hingga penyelesaian transisi otomat tingkat atas ke kondisi t2. Kemudian proses, pada transisi tanpa syarat dari negara "t2" ke negara "t1", melakukan tindakan y2, yang menetapkan hasil perhitungan faktorial variabel lokal dari proses dF.

Catatan, dan sangat penting bahwa hasil dari automaton bersarang akan tersedia hanya setelah menyelesaikan pekerjaannya dan transisi ke keadaan berikutnya dari otomat tingkat atas. Dimungkinkan untuk mendapatkannya (hasil) melalui pointer ke objek automaton bersarang.

Dan semuanya akan baik-baik saja jika bukan karena runtime fungsi rekursif. Dibandingkan dengan subrutin biasa, karena bersarang, sebagai aturan, ini jauh lebih besar dan karenanya dapat melanggar atomisitas bersyarat dari tindakan proses otomatis, total waktu eksekusi yang harus sesuai dengan nilai siklus clock diskrit.

Dalam kasus kami, perhitungan faktorial cukup kecil dan hingga nilai maksimum hasil ganda untuk n = 170 cocok menjadi 1 mikrodetik. Untuk menghitung nilai besar, seseorang perlu beralih ke aritmatika panjang (lihat, misalnya, [4, 5]). Pada saat yang sama, waktu perhitungan faktorial akan meningkat bahkan lebih dan hampir dijamin akan melampaui siklus clock diskrit, yang mempengaruhi kecepatan mesin jaringan yang tersisa yang beroperasi dalam mode multitasking non-preemptive dan reaktivitas respon aplikasi secara keseluruhan, yang akan memanifestasikan dirinya dalam "rem".

Anda dapat tetap dalam siklus clock diskrit dan menyingkirkan cacat "rem" dengan memperluas perhitungan menjadi langkah-langkah. Untuk tujuan ini, akan lebih mudah untuk menerjemahkan algoritma faktorial yang biasa ke dalam bentuk otomatis. Benar, karena mode interpretasi automata, waktu perhitungan akan meningkat, tetapi sifat temporal aplikasi akan kembali normal.

Listing 3 menunjukkan perhitungan faktorial dalam bentuk yang biasa dan dalam bentuk yang disiapkan untuk konversi selanjutnya ke bentuk otomat. Yang terakhir secara eksplisit mewakili langkah-langkah atom yang disembunyikan dalam kode sumber program. Dalam hal ini, kita berbicara tentang operator dari bentuk y = f (...), di mana f adalah fungsi program biasa atau rekursif. Catatan semacam itu menutupi waktu operasi dari fungsi dan menciptakan kesan yang salah dari penugasan β€œsesaat” dari nilai variabel y. Pada kenyataannya, tidak ada yang satu atau yang lain.

Daftar 3. Fungsi perangkat lunak untuk menghitung faktorial
int factorial(int n)
{
    if (n == 0) return 1;
    else return n*factorial(n-1);
}

double Factorial(int n)
{
    double dF = 1;
    if (n == 0)
        return dF;
    else {
        double dPrev = Factorial(n-1);
        double dF = n*dPrev;
        return dF;
    }
}


Dalam kerangka konsep pemrograman otomatis, aksi atom (metode) objek otomatis perangkat lunak dianggap bersyarat seketika. Dan jika hanya waktu operasi total tindakan yang cocok dengan satu siklus waktu diskrit ruang otomat, maka implementasi perangkat lunak sesuai dengan model formalnya.

Keterangan 1. Total waktu nyata dari operasi tindakan pada satu atau lain siklus clock diskrit dapat sangat bervariasi. Dalam hal melebihi waktu siklus, lingkungan VKPA mencoba untuk mengkompensasinya pada siklus berikutnya dari operasi ruang otomat, mengurangi, jika mungkin, waktu diskrit dari siklus berikutnya.

Dalam gbr. Gambar 1 menunjukkan diagram blok yang mencakup markup yang mencerminkan keadaan mesin keadaan terbatas yang setara dan nama predikat dan tindakannya. Model otomat itu sendiri dalam bentuk grafik otomat ditunjukkan pada Gambar. 2.

gambar
Gambar. 1. Blok diagram dari Gambar faktorial algoritma rekursif

gambar
. 2. Model otomat dari suatu faktorial rekursif algoritma

Kode untuk rutin otomatis untuk menghitung faktorial ditunjukkan pada Listing 4. Komentar dalam tabel transisi (TP) mencerminkan waktu menghitung faktorial dalam ms dan jumlah langkah-langkah terpisah yang dihabiskan pada prosedur perhitungan untuk n = 170. Dan jika waktu perhitungan tergantung pada kecepatan platform komputasi dan / atau pada jenis proyek (debug / rilis), maka jumlah langkah diskrit (siklus jam) hanya ditentukan oleh sifat-sifat algoritma dan dapat berfungsi sebagai penilaian obyektif dari kecepatan algoritma, terlepas dari bentuk presentasi dan implementasinya.

Dalam kasus model otomat, jumlah kutu diskrit dapat dihitung dengan cukup sederhana dan, setelah mengumpulkan statistik tertentu, dapatkan rumus analitik untuk kecepatan algoritma. Sementara itu, perkiraan tersebut sangat perkiraan dan didasarkan pada kesimpulan, seringkali diperoleh sebagai hasil dari alasan yang agak rumit.

Daftar 4. Otomatis faktorial otomatis
#include "lfsaappl.h"

class FFactRec : public LFsaAppl
{
public:
    FFactRec(int n);
    virtual ~FFactRec() { if (pFFactRec) delete pFFactRec; };
    int nN;		//	n
    double dF;	//	n!
private:
	int x1();
    void y1(); void y2(); void y3();
protected:
    void CallFactorial();
    double  PrevFactorial();
    FFactRec *pFFactRec{nullptr};
};

#include "stdafx.h"
#include "FFactRec.h"

#define QT_NO_DEBUG_OUTPUT

extern LArc TT_FactRec[];
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
//	   
FFactRec::FFactRec(int n)
    :LFsaAppl(TT_FactRec, "FFactRec")
{
    nN=n; dF=1;
}

LArc TT_FactRec[] = {
//        Debug    Release     Steps
//msec: 120-129   98-101        340
    LArc("f1", "00", "x1",	"y3"),	//
    LArc("f1", "f2", "^x1",	"y1"),	//
    LArc("f2", "00", "--",	"y2"),	//
    LArc()
};

int FFactRec::x1() { return nN==0; }
//   
void FFactRec::y1() { CallFactorial(); }
//	1.      
//	2.    
void FFactRec::y2() { dF = PrevFactorial()*nN; }
// 0!
void FFactRec::y3() { dF = 1; }
//   (  )
void FFactRec::CallFactorial()	{
    //	     
    pFFactRec = new FFactRec(nN-1);
    pFFactRec->FCall(this);
};
//    
double  FFactRec::PrevFactorial()	{ return pFFactRec->dF; };


3. Kesimpulan


Saya akui, karena spesifik tugas yang harus diselesaikan, saya sebenarnya tidak harus menggunakan, dan mengembangkan algoritma rekursif dalam proyek nyata, kecuali untuk tujuan pelatihan dan untuk menguji kernel untuk implementasi penyarangan. Rutinitas otomatis - hampir secara konstan dan dalam jumlah besar. Di antara mereka, subrutin yang disebut inersia sangat menarik (untuk lebih jelasnya, lihat [3]). Tapi mereka seharusnya mencurahkan artikel terpisah untuk pertimbangan mereka.

Algoritma rekursif adalah bagian penting dari teori program [6]. Tanpa implementasi rutinitas dan rekursi, sulit untuk membayangkan model komputasi yang efisien dan nyaman dari semua sudut pandang. Dan untuk mengimplementasikan rekursi, mengingat kemungkinan pemrograman objek (lihat contoh artikel), setelah membuat model subprogram, tidak begitu sulit. Anda bisa, tentu saja, menggunakan metode tertentu untuk mengubah rekursi menjadi bentuk reguler, melompati menggunakan siklus yang sama. Tetapi lebih disukai untuk tidak membuangnya dengan bodoh, tetapi memiliki mekanisme langsung untuk implementasinya. Tentu saja, akan lebih lambat, tentu saja, lebih mahal dalam hal sumber daya (biaya memori stack yang sama), tetapi dengan kemampuan perangkat keras yang ada ini tidak akan begitu kritis.

Tetapi algoritma rekursif juga menarik sebagai tes untuk mengevaluasi kemampuan, universalitas, dan efektivitas model algoritmik. Dalam hal ini, model automaton tidak hanya mengimplementasikan rekursi dengan cukup efektif, tetapi juga menambahkan β€œchip automaton” sebagai bonus - kemampuan untuk mengontrol keadaan internal dari algoritma rekursif, menghitung langkah-langkah untuk mengevaluasi kinerja, mengontrol waktu eksekusi dari algoritma, dll. dll.

Pertarungan rekursi tidak ada gunanya. Itu harus bisa digunakan. Saya akan mengutip yang sepenuhnya saya setujui: "Sekilas, rekursi mungkin tampak rumit. Tetapi dalam beberapa kasus, metode rekursif sangat efektif jika semuanya dilakukan dengan benar. Namun, terkadang lebih baik menggunakan loop. Memahami kedua metode dan kemampuan untuk menggunakannya secara efektif akan membantu Anda dalam pekerjaan Anda dan akan menjadi keuntungan dalam wawancara ”[7]. Saya hanya dapat menambahkan sendiri bahwa model otomatis dari algoritma rekursif memungkinkan, dengan menggunakan "properti otomatis" dari model komputasi, untuk memahami rekursi, men-debug dan memperbaiki algoritma seperti itu jauh lebih cepat.

Dan yang terakhir. Namun demikian, saya ingin mendapatkan jawaban atas pertanyaan - bagaimana keadaan dengan rekursi coroutine? Saya sudah menanyakannya, tetapi saya belum menerima jawaban ... Lagi pula, adalah satu hal untuk membuat sejuta coroutine (lihat [8] sebagai contoh) dan yang lainnya untuk mengimplementasikan algoritma rekursif yang memiliki tingkat bersarang yang tidak sama tetapi cukup tinggi. Dan, sepertinya, jawaban untuk pertanyaan ini menarik bukan hanya saya saja ... [9]

literatur
1. .., .. . I, . ., 1981, 2, 135-144. [ ], : mi.mathnet.ru/at5725 . . . ( 10.03.2020).
2. .., .. . II, . ., 1981, 3, 112-121. [ ], : mi.mathnet.ru/at5743 . . . ( 10.03.2020).
3. . [ ], : habr.com/ru/post/484588 . . . ( 10.03.2020).
4. . [ ], : habr.com/ru/post/255761 . . . ( 10.03.2020).
5. C++. [ ], : habr.com/ru/post/172285 . . . ( 10.03.2020).
6. . . : . . – .: , 1983. – 256 .
7. , ? Python. Ethan Jarrell. Recursion vs. Looping in Python [ ], : nuancesprog.ru/p/3325 . . . ( 15.03.2020).
8. Your first coroutine with Kotlin. [ ], : kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html . . . ( 18.03.2020).
9. . CyberForum.ru. [ ], : www.cyberforum.ru/unity/thread2479923.html . . . ( 18.03.2020).

All Articles