Showing posts with label ֆունկցիա. Show all posts
Showing posts with label ֆունկցիա. Show all posts

Sunday, July 12, 2020

Haskell: Օր առաջին. Ֆակտորիալ

Այս կիրակի ես վերջապես որոշեցի սկսել ծանոթությունը Haskell լեզվի հետ։ Haskel-ը ֆունկցիանալ լեզու է. երբեմն ասում են, որ այն ֆունկցիոնալների մեջ ամենաֆունկցիոնալն է։ Ես, ինչ-որ տարրական պատկերացում ունենալով ֆունկցիոնալ ծրագրավորման մասին, ինձ համար սահմանեցի հետևյալ առաջին խնդիրը.

Ֆակտորիալ։ Գրել ծրագիր, որ հրամանային տողից ստանում է որևէ դրական ամբողջ թիվ, ապա հաշվարկում և արտածում է այդ թվի ֆակտորիալը։

Բայց, մինչև խնդրի լուծմանն անցնելը, ես պիտի պատրաստեմ Haskell լեզվի միջավայրը, որում աշխատեցնելու եմ իմ գրած ծրագրերը։ Կարելի է, իհարկե, օգտագործել որևէ առցանց ծառայություն, ինչպիսիք են, օրինակ, www.tutorialspoint.com-ը կամ https://repl.it-ը, բայց ես նախընտրում եմ ամեն ինչ ունենալ ձեռքի տակ՝ իմ մեքենայի վրա։

Իսկ իմ մեքենան Raspberry Pi է` Debian-ի հիման վրա կառուցված օպերացիոն համակարգով։ Haskell Platform-ի էջից գտա, թե ինչպես է պետք տեղադրել Haskell-ի կոմպիլյատորն ու ինտերպրետատորը.

$ sudo apt-get install haskell-platform

Haskel Platform-ի կոմպիլյատորի և ինտերպրետատորի հաջող տեղադրված լինելը ստուգելու համար նախ հրամանային տողից աշխատեցեմ ghci ինտերպրետատորը.

$ ghci
GHCi, version 8.4.4: http://www.haskell.org/ghc/  :? for help
Prelude>

Հրավերքի տողում Prelude ցույց է տալիս, որ ինտերպրետատորը գործարկվել է և ակտիվ է Prelude փաթեթը։ Խնդրեմ Հասկելին ցույց տալ π թվի արժեքը.

Prelude> pi
3.141592653589793

Կարծես թե աշխատում է։ Փորձեմ հենց այստեղ սահմանել ֆակտորիալը հաշվող ֆունկցիան՝ ամենապարզ մոտեցմամբ.

Prelude> factorial n = if n == 1 then 1 else n * factorial (n - 1)

Մի քանի օրինակներով համոզվեմ, որ սահմանած ֆունկցիան աշխատում է.

Prelude> factorial 1
1
Prelude> factorial 5
120
Prelude> factorial 10
3628800
Prelude> factorial 100
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

Հիմա այս ֆունկցիան գրեմ մի ֆայլի մեջ, օրինակ, ex0.hs անունով, ու փորձեմ այդ ֆայլը թարգմանել Հասկելի կոմպիլյատորով։

-- Իմ առաջին ծրագիրը

factorial :: Integer -> Integer
factorial n = if n == 1 then 1 else n * factorial (n - 1)

Այստեղ ֆունկցիայի սահմանումից առաջ ավելացրել եմ նաև դրա վերնագիրը (կամ նկարագրությունը)։ Այդ նկարագրությամբ տրվում է ֆունկցիայի տիպը. :: սիմվոլից ձախ գրված է ֆունկցիայի անունը՝ factorial, իսկ աջ կողմում՝ արգումենտի ու վերադարձվող արժեքի տիպերը։ Այսինքն՝ ֆունկցիան ստանում է Integer տիպի արգումենտ և վերադարձնում է Integer տիպի արժեք։ Երկրորդ տողում հենց ֆունկցիայի սահմանումն է. = սիմվոլից ձախ ֆունկցիայի անունն ու արգումենտն է, իսկ աջ կողմում՝ մարմինը, որը տվյալ դեպքում պարզ ճյուղավորման արտահայտություն է։

Haskell Platform-ում կոմպիլյատորը ghc—ն է։ Աշխատեցնում եմ՝ մուտքին տալով ex0.hs ֆայլը.

$ ghc ex0.hs
[1 of 1] Compiling Main             ( ex0.hs, ex0.o )

ex0.hs:1:1: error:
    The IO action ‘main’ is not defined in module ‘Main’
  |
1 |
  | ^

Սխալի հաղորդագրությունն ասում է, որ Main մոդուլում սահմանված չէ main գործողությունը։ Բանից պարզվում է, որ Հասկելի կոմպիլյատորը նույնպես (ինչպես, օրինակ, Սի լեզվի կոմպիլյատորը) որպես մուտքի կետ է համարում main գործողությունը։ Հիմա ex0.hs ֆայլում ավելացնում եմ main գործողությունն այնպես, որ այն արտածի 12-ի ֆակտորիալը.

-- Իմ առաջին ծրագիրը
factorial n = if n == 1 then 1 else n * factorial (n - 1)

-- Մուտքի կետը
main =
    print (factorial 12)

Նորից փորձեմ թարգմանել։ Ի դեպ, Հասկել լզվում -- սիմվոլով սկսվում են մեկնաբանությունները։

$ ghc ex0.hs
[1 of 1] Compiling Main             ( ex0.hs, ex0.o )
Linking ex0 ...

Արդեն ամեն ինչ լավ է։ Իմ գրած ծրագիրը թարգմանվեց (compile), կապակցվեց (link), և հիմա կարող եմ աշխատեցնել ու տեսնել արդյունքը.

$ ./ex0
479001600

Բայց այս ծրագիրը կարողանում է հաշվել ու տպել միայն 12-ի ֆակտորիալը։ Իսկ ես ուզում եմ, որ այն կարողանա հաշվել հրամանային տողում տրված թվի ֆակտորիալը։ ՄԻ քիչ քչփորելուց հետո պարզեցի, որ Հասկել ծրագրում գրամանային տողի պարամետրերը կարելի է վերցնել System.Environment մոդուլի getArgs գործողությամբ։ Օրինակ, հետևյալ ծրագիրը (գրառված ex1.hs ֆայլում) արտածում է հրամանային տողում տրված պարամետրերի ցուցակը.

-- Հրամանային տողի պարամետրերի ցուցադրություն

import System.Environment

main = do
    args <- getArgs
    print args

Ահա թարգմանության ու կատարման մի քանի օրինակ.

$ ./ex1
[]
$ ./ex1 a
["a"]
$ ./ex1 a bb
["a","bb"]
$ ./ex1 a bb ccc
["a","bb","ccc"]
$ ./ex1 1 22 333
["1","22","333"]

Այստեղից երևում է, որ հրամանային տողի պարամետրերը ծրագրում երևում են տեքստային արժեքների ցուցակի տեսքով։ Ես պետք է թվի տեքստային ներկայացումից ստանամ դրա թվային արժեքը, ապա այդ արժեքի նկատմամբ կիրառեմ factorial ֆունկցիան:

Հասկելի read ֆուկցիան տեքսից «կարդում» է որևէ տիպի արժեք։ Այդ տիպը տրվում է ֆունկցիայի կանչի հետ՝ :: սիմվոլոլից հետո։ Օրինակ, «read "12" :: Int» արտահայտությունը "12" տողից կարդում է Int տիպի 12 արժեքը։ «read "12" :: Float» արտահայտությունը նույն տողից կարդում է 12.0 արժեքը՝ Float տիպի։

Այսպիսով, ես պետք է վերցնեմ հրամանային տողի պարամետրերի ցուցակի առաջին տարրը (head ֆունկցիայիով), դրա նկատմամբ կիրառեմ read ֆունկցիան՝ Integer տիպի համար, ստացված արժեքի նկատմամբ կիրառեմ factorial-ը ու տպեմ ստացված արժեքը։ Ահա այսպիսի մի արտահայտություն main ֆունկցիայում.

print (factorial (read (head args) :: Integer))

Ձևափոխված ex0.hs ծրագիրը կունենա հետևյալ վերջնական տեսքը.

import System.Environment

-- Ֆակտորիալի հաշվարկը
factorial :: Integer -> Integer
factorial n = 
    if n == 1 
    then 1
    else n * factorial (n - 1)

-- Մուտքի կետ
main :: IO ()
main = do
    args <- getArgs
    print (factorial (read (head args) :: Integer))

Լավ. տեսնենք, թե սա ինչպես է աշխատում։

$ ghc ex0.hs
[1 of 1] Compiling Main             ( ex0.hs, ex0.o )
Linking ex0 ...
$ ./ex0 2
2
$ ./ex0 12
479001600
$ ./ex0 20
2432902008176640000
$ ./ex0 40
815915283247897734345611269596115894272000000000

Լավ էլ աշխատում է։ Բայց, իհարկե, թերություններ կան։ Առաջին թերությունը տեխնիկական է. դիտարկված չէ այն դեպքը, երբ հրամանային տողում ոչինչ տրված չէ։ Օրինակ, եթե աշխատեցնեմ ծրագիրը՝ հրամանային տողում ոչինչ չտալով, ապա կստանամ հաղորդագրություն այն մասին, որ head ֆունկցիային տրված է դատարկ ցուցակ.

$ ./ex0
ex0: Prelude.head: empty list

Սա պետք է ուղղել՝ main գործողության մեջ պայման գրելով։ Այսպես.

main = do
    args <- getArgs
    if not (null args)
    then print (factorial (read (head args) :: Integer))
    else putStrLn "Ոչինչ տրված չէ։"

Հիմա եթե ծրագիրն աշխատեցնեմ դատարկ հրամանային տողով, ապա որպես պատասխան կստանամ «Ոչինչ տրված չէ։»։

Հաջորդիվ. թերևս Հասկել լեզվով գրող ոչ մի ծրագրավորող թվի ֆակտորիալը հաշվող ֆունկցիան չի գրի այնպես, ինչպես ես գրել եմ։ Վարպետ Հասկել-ծրագրավորողը պարզապես կգրի.

factorial :: Integer -> Integer
factorial n = product [1 .. n]

Եվ վերջ։ Այստեղ գրված է ֆակտորիալի բառացի սահմանումը՝ այն 1-ից n թվերի ([1 .. n]) արտադրյալն է (product):

Thursday, September 18, 2014

C++: Ֆունկցիա

Մաթեմատիկական մեկնաբանությամբ ֆունկցիան մի գործողություն է, որը մի բազմությունը՝ որոշման տիրույթը, արտապատկերում է մեկ այլ բազմության՝ արժեքների տիրույթին։ C++ լեզվում նույնպես այդպես է․ ֆունկցիան ստանում է արգումենտներ և վերադարձնում է արժեք։ Բայց նաև, ծրագրավորման գործի առանձնահատկություններից ելնելով, C++ լեզուն թույլատրում է սահմանել ֆունկցիաներ, որոնք արգումենտներ չեն ստանում կամ/և արժեքներ չեն վերադարձնում։

main ֆունկցիայի օրինակով արդեն տեսանք, որ C++ լեզվում ֆունկցիայի սահմանումն ունի չորս բաղադրիչ. i) վերադարձվող արժեքի տիպ, ii) անուն, iii) արգումենտների ցուցակ, iv) մարմին։ Օրինակ, եթե սահմանենք տրված շառավղով շրջանի մակերեսը հաշվող area ֆունկցիան, ապա այն կունենա այսպիսի տեսք.
double area( double radius )
{
  return radius * radius * 3.14159265358979323846;
}
Այս ֆունկցիայի վերադարձվող արժեքի տիպը double է, անունը area է, արգումենտների ցուցակում թվարկված է միակ radius արգումենտը՝ իր տիպի հետ, իսկ մարմինը բաղկացած է միակ return հրամանից, որի արգումենտում շրջանի մակերեսի հաշվման բանաձևն է։

Ֆունկցիայի տիպ է կոչվում վերադարձրած արժեքի տիպի ու արգուենտների տիպերի ցուցակի համադրությունը։

Եթե հարկավոր է սահմանել մի ֆունկցիա, որից արժեք չենք ակնկալում, ապա պետք է վրա վերադարձվող արժեքի տիպը նշել որպես void։ Այսպիսի ֆունկցիային երբեմն անվանում են նաև պրոցեդուրա։ Օրինակ, սահմանենք մի ֆունկցիա, որը ստանում է առանց նշանի ամբողջ թիվ (unsigned int) և արտածում է այդ թվի տասական, ութական ու տասնվեցական ներկայացումները։ Այդ ներկայացումները ստացվում են հոսքերի dec, oct և hex մանիպուլյատորների օգնությամբ, որոնք սահմանված են iostream ֆայլում։
void print_number( unsigned int num )
{
  std::cout << std::setw(8) << std::dec << num
            << std::setw(8) << std::oct << num
            << std::setw(8) << std::hex << num
            << std::endl;
}
Արտածման հոսքի setw պարամետրով մանիպուլյատորը, որ սահմանված է գրադարանի iomanip ֆայլում, հնարավորություն է տալիս սահմանել այն դիրքերի քանակը, որոնք պետք է օգտագործվեն արժեքի արտածման համար։

Եթե ֆունկցիան պետք է ստանա մի քանի արգումենտներ, ապա դրանք արգումենտների ցուցակում թվարկվում են իրարից ստորակետով անջատված։ Օրինակ, սահմանենք avg3 ֆունկցիան, որը արգումենտում ստանում է երեք իրական թիվ և վերադարձնում դրանց թվաբանական միջինը։
double avg3( double a, double b, double c )
{
  auto sum = a + b + c;
  return sum / 3;
}
Եթե մի որևէ f() ֆունկցիայում օգտագործվելու է մի այլ g() ֆունկցիա, ապա այն պետք է ավելի շուտ հայտարարված կամ սահմանված լինի։ Օրինակ, ծրագրում այդ երկու ֆունկցիաների փոխադարարձ դասավորությունը կարող է լինել այսպիսին.
int g( double a, char b )
{
    // ինչ-որ հրամաններ
    return 777;
}

void f( int x )
{
    // ինչ-որ հրամաններ
    auto v = g( 3.14, 'p' );
    // ինչ-որ հրամաններ
}
Կամ, f() ֆունկցիային կարող է նախորդել միայն g() ֆունկցիայի հայտարարությունը։ Ահա այսպես.
int g( double, char );

void f( int x )
{
    // ինչ-որ հրամաններ
    auto v = g( 3.14, 'p' );
    // ինչ-որ հրամաններ
}

int g( double a, char b )
{
    // ինչ-որ հրամաններ
    return 777;
}
Առաջին տողում գրված արտահայտությունը կոմպիլյատորին տեղեկացնում է, որ գոյություն ունի double և char տիպի արգումենտներ ստացող և int արժեք վերադարձնող g անունով ֆունկցիա, որի սահմանումը գալու է քիչ ավելի ուշ։