Monday, December 17, 2012

Java: Ֆակտորիալ՝ նոր մոտեցում

Առաջին գրառման մեջ ես ներկայացրեցի դրական ամբողջ թվի ֆակտորիալը հաշվելու ծրագիրը որպես Java ծրագրավորման լեզվով գրված առաջին ծրագիր։ Այն աշխատում է և արտածում է 12 թվի ֆակտորիալ։ Այն ցույց է տալիս, թե ինչպես սահմանել Factorial դասը և նրա համար սահմանել main ստատիկ մեթոդը, հայտարարել ամբողջաթիվ (int) փոփոխականներ ու նրանց վերագրել արժեքներ, կազմակերպել կրկնություն (while) և արտածել հաշվարկման արդյունքները (System.out.println())։ Այդ ծրագրի օրինակով ցուցադրվեց նաև թե ինչպես թարգմանել Java լեզվով գրված կոդն ու կատարել վիրտուալ մեքենայի օգնությամբ։

Բայց խնդրի լուծման տեսակետից բերված ծրագիրն ուներ մի քանի թերություններ։ Թվարկեմ դրանք և փորձեմ առաջարկել լուծումներ։
  1. int տիպը Java լեզվում ուն 32 բիթ երկարություն և, քանի որ ֆակտորիալը արագ աճող ֆունկցիա է, նրանում կարելի է հաշվել 1-ից 12 թվերի ֆակտորիալները։ Լուծում կարող էր հանդիսանալ 64 բիթ երկարությամբ long տիպը, բայց նրանով էլ կարելի է հաշվել միայն 1-ից 20 թվերի ֆակտորիալները։
  2. Ծրագիրը գրված է միայն 12 թվի ֆակտորիալը հաշվելու համար։ Այն հնարավորություն չունի օգտագործողից հարցնելու թիվը նրա ֆակտորիալը հաշվելու համար։
  3. Ֆակտորիալի հաշվարկը կատարվում է main մեթոդի մարմնում։ Ավելի հարմար կլիներ սահմանել մի ֆունկցիա, որն արգումենտում ստանում է դրական ամբողջ թիվ և վերադարձնում է նրա ֆակտորիալը։

* * *
Թվի երկարության հետ կապված հարցերը լուծելու լավագույն միջոցը նախատեսված է Java լեզվի ստանդարտ գրադարանի java.math փաթեթում։ Այդ փաթեթի BigInteger դասով իրականացված են անսահմանափակ երկարությամբ ամբողջ թվերը։ Այդ դասը մեր ծրագրում մատչելի դարձնելու համար Factorial դասի սահմանումից առաջ պետք է գրել.
import java.math.BigInteger;
Սա ցույց է տալիս, որ ծրագրում կարող ենք հայտարարել և օգտագործել BigInteger դասի նմուշներ։
BigInteger դասն ունի մի քանի կոնստրուկտորներ, որոնք ստեղծում և արժեքավորում են օբյեկտը, բայց դրանցից մեզ հետաքրքրելու է միայն BigInteger(String val) տարբերակը, որը օբյեկտն արժեքավորում է թվի տեքստային ներկայացմամբ։ Օրինակ, 123456789012345678901234567890 արժեքով BigInteger օբյեկտ ստեղծելու համար պետք է գրել.
BigInteger num1 = new BigInteger("123456789012345678901234567890");
Բացի Java լեզվի բազային տիպերով ներկայացվող օբյեկտներից, իսկ դրանք են byte (մեկ բայթ), short (կարճ ամբողջ թիվ, 16 բիթ), int (ամբողջ թիվ, 32 բիթ), long (երկար ամբողջ թիվ, 64 բիթ), float (սահող կետով թիվ, 32 բիթ), double (կրկնակի ճշտության իրական թիվ, 64 բիթ), boolean (տրամաբանական) և char (նիշ), բոլոր մյուս օբյեկտները կառուցվում են new գործողության օգտագործմամբ։ BigInteger num1; արտահայտությամբ պարզապես հայտարարվում է հղում BigInteger տիպի օբյեկտին, իսկ օբյեկտը կառուցվում է new BigInteger("1234..."); արտահայտությամբ։

Ֆակտորիալի հաշվարկման համար պետք է հայտարարել երկու BigInteger օբյեկտ. prod, որը պետք է ունենա 1 նախնական արժեքը, և n, որի ֆակտորիալը պետք է հաշվել։ Այս անգամ n-ի սկսզբնական արժեք ընդունենք 200 թիվը.
BigInteger prod = BigInteger.ONE;
BigInteger n = new BigInteger("200");
BigInteger դասում հայտարարված է 1 թվային արժեքով ONE հաստատունը, որն օգտագործված է որպես prod փոփոխականի նախնական արժեք։ Նույն դասում սահմանված են նաև ZERO և TEN հաստատունները՝ համապատասխանաբար 0 և 10 թվային արժեքներով։

Հաշվարկների համար մեզ հարկավոր են բազմապատկման և հանման գործողությունները։ BigInteger դասի multiply մեթոդը կատարում է երկու BigInteger-ների բազմապատկում, իսկ subtract մեթոդը BigInteger թվից հանում է մի ուիշը։ compareTo մեթոդն էլ համեմատում է երկու BigInteger օբյեկտներ և վերադարձնում է -1, 0, 1, երբ առաջին թիվը համապատասխանաբար փոքր է, հավասար է կամ մեծ է երկրորդից։

Այս ամենը հաշվի առնելով արդեն կարող ենք մեր հին ծրագրի ցիկլը ձևափոխել և գրել ահա այսպես.
while( n.compareTo(BigInteger.ZERO) == 1 ) {
    prod = prod.multiply(n);
    n = n.subtract(BigInteger.ONE);
}

* * *
Հաջորդ խնդիրը ծրագրի համար տվյալների ներածումն է։ Պետք է կազմակերպել այնպես, որ ծրագիրն օգտագործողից հարցնի մի որևէ ամբողջ թիվ և հաշվարկի ու արտածի դրա ֆակտորիալը։ Java ստանդարտ գրադարանի System դասի in դաշտի read մեթոդը հնարավորություն է տալիս ստեղնաշարից (ստանդարտ ներածման հոսքից) ներմուծել բայթերի զանգված՝ byte[]։ Օրինակ, գրենք կոդի մի հատված, որը ներածման հոսքից կարդում է բայթերի զանգված, այն վերածում է տողի, ապա արտածում է ստանդարտ արտածման հոսքի վրա։
Նախ հայտարարենք buffer անունով բայթերի զանգված, օրինակ, 20 երկարությամբ, որի մեջ պետք է կարդալ ներածված տողը։
byte[] buffer = new byte[20];
Ապա կանչենք read մեթոդը և նրա արգումենտում տանք buffer զանգվածը։ Կատարվելուց հետո buffer-ը կպարունակի ներածման հոսքից տրված տվյալները (ինչպես նաև ներածման ավարտին տրված նոր տողի սիմվոլը)։
System.in.read(buffer);
Այնուհետև String դասի կոնստրուկտորով բայթերի զանգվածից կառուցենք տեքստային ներտայացումը։
String str = new String(buffer);
Եվ վերջապես, արտածենք տեքստային ներկայացումը՝ նախապես trim մեթոդով նրա աջ ու ձախ կողմերից հեռացնելով բացատանիշերը։
System.out.println(str.trim());
Այսպիսով, ֆակտորիալի հաշվարկման մեր ծրագրում n փոփոխականին արժեք վերագրող «BigInteger n = new BigInteger("200");» տողը փոխարինենք հետևյալ հատվածով․
byte[] buffer = new byte[32];
System.in.read(buffer);
String str = new String(buffer);
BigInteger n = new BigInteger(str.trim());
Եվ, ի մի բերելով ամբողջ ասվածը, կունենանք ամբողջական ծրագրի հետևյալ տեսքը․
package factorial;

import java.math.BigInteger;

public class Factorial {
  public static void main(String[] args)
  {
    byte[] buffer = new byte[32];
    System.in.read(buffer);
    String str = new String(buffer);
    BigInteger n = new BigInteger(str.trim());
    
    BigInteger res = BigInteger.ONE;
    while( n.compareTo(BigInteger.ZERO) > 0 ) {
      res = res.multiply(n);
      n = n.subtract(BigInteger.ONE);
    }

    System.out.println(res);
  }
}
Երբ փոձենք «javac -d . Factorial.java» հրամանով թարգմանել ծրագիրը, ապա Java լեզվի կոմպիլյատորը կարտածի սխալի հաղորդագրություն՝ ահա այս տեսքով․
Factorial.java:9: unreported exception java.io.IOException; must be caught or declared to be thrown
    System.in.read(buffer);
                  ^
1 error
Բանն այն է, որ read մեթոդը գեներացնում է IOException տիպի բացառություն։ Եվ կոմպիլյատորը պահանջում է կա՛մ որսալ ու մաշկել այդ բացառությունը, կա՛մ հայտարարել, որ main մեթոդում գեներացվում են բացառություններ։ Առայժմ հետաձգենք բացառությունների մշակումը, բայց main մեթոդի վերնագիրը ձևափոխենք այնպես, որ երևա նրանում գեներացված բացառության տիպը․
public static void main(String[] args) throws IOException
{ ... }
Իհարկե, քանի որ java.io.IOException դասը սահմանված է java.io փաթեթում, Factorial դասի սահմանումից առաջ պետք է ներմուծել այն։
import java.io.IOException;

* * *
Հիմա սահմանենք Factorial դասի երկու նոր ստատիկ մեթոդներ, որոնցից առաջինը օգտագործողից պահանջում է ներածել թիվ և վերադարձնում է այդ թվի BigInteger ներկայացումը։ Իսկ երկրորդը արգումենտում ստանում է BigInteger թիվ և վերադարձնում է նրա ֆակտորիալը նորից BigInteger տեսքով։
Առաջին մեթոդի սահմանումը շատ պարզ է և ունի հետևյալ տեսքը․
private static BigInteger inputNumber() throws IOException
{
  byte[] buffer = new byte[32];
  System.in.read(buffer);
  String str = new String(buffer);
  return new BigInteger(str.trim());
}
private ծառայողական բառով սահմանված մեթոդները (ինչպես նաև դասերը, դաշտերը և այլն) տեսանելի են միայն դասի սահմանման մեջ։ Այս inputNuber մեթոդն օգտագործվելու է միայն main մեթոդում, և նրա տեսանելիության տիրույթը սահմանափակված է private բառով։

Նշեցինք, որ ֆակտորիալը հաշվող ֆունկցիան ստանում է BigInteger թիվը և վերադարձնում է նրա ֆակտորիալը՝ ներից BigInteger տեսքով։ Սահմանենք factorial ստատիկ մեթոդը, բայց այս անգամ կրկնման հրամանի փոխարեն օգտագործենք ռեկուրսիա։ (N=0, ապա N!=1, հակառակ դեպքում՝ N!=N * (N-1)!
private static BigInteger factorial(BigInteger num)
{
  if( 0 == num.compareTo(BigInteger.ZERO) )
    return BigInteger.ONE;
  return num.multiply(factorial(num.subtract(BigInteger.ONE)));
}
Քանի որ BigInteger օբյեկտները չփոփոխվող են (immutable), multiply (բազմապատկում) և subtract (հանում) մեթոդները ստեղծում և վերադարձնում են համապատասխան նոր օբյեկտը։
* * *
Մի վերջին անգամ անգամ ցույց տանք ծրագրի ամբողջական տեսքը և փորձենք հաշվել 1000 թվի ֆակտորիալը։
package factorial;

import java.io.IOException;
import java.math.BigInteger;

public class Factorial {
  private static BigInteger inputNumber() throws IOException
  {
    byte[] buffer = new byte[32];
    System.in.read(buffer);
    String str = new String(buffer);
    return new BigInteger(str.trim());
  }

  private static BigInteger factorial(BigInteger num)
  {
    if( 0 == num.compareTo(BigInteger.ZERO) )
      return BigInteger.ONE;
    return num.multiply(factorial(num.subtract(BigInteger.ONE)));
  }
    
  public static void main(String[] args) throws IOException
  {
    BigInteger n = inputNumber();
    BigInteger res = factorial(n);
    System.out.println(res);
  }
}
Թարգմանենք հետևյալ հրամանով (կամ սեղմենք NetBeans միջավայրի Run կոճակը).
$ javac -d . Factorial.java
Ապա կատարենք ծրագիրը Java վիրտուալ մեքենայով, և ներածենք 1000 թիվը.
$ java factorial/Factorial
1000
Ծրագրի աշխատանքի արդյունքում արտածվելու է հետևյալ բավականին մեծ արժեքը, որը կարելի է ստուգել ու համոզվել որ 1000-ի ֆակտորիալն է.
402387260077093773543702433923003985719374864210714632543799910429938512398629020592044208486969404800479988610197196058631666872994808558901323829669944590997424504087073759918823627727188732519779505950995276120874975462497043601418278094646496291056393887437886487337119181045825783647849977012476632889835955735432513185323958463075557409114262417474349347553428646576611667797396668820291207379143853719588249808126867838374559731746136085379534524221586593201928090878297308431392844403281231558611036976801357304216168747609675871348312025478589320767169132448426236131412508780208000261683151027341827977704784635868170164365024153691398281264810213092761244896359928705114964975419909342221566832572080821333186116811553615836546984046708975602900950537616475847728421889679646244945160765353408198901385442487984959953319101723355556602139450399736280750137837615307127761926849034352625200015888535147331611702103968175921510907788019393178114194545257223865541461062892187960223838971476088506276862967146674697562911234082439208160153780889893964518263243671616762179168909779911903754031274622289988005195444414282012187361745992642956581746628302955570299024324153181617210465832036786906117260158783520751516284225540265170483304226143974286933061690897968482590125458327168226458066526769958652682272807075781391858178889652208164348344825993266043367660176999612831860788386150279465955131156552036093988180612138558600301435694527224206344631797460594682573103790084024432438465657245014402821885252470935190620929023136493273497565513958720559654228749774011413346962715422845862377387538230483865688976461927383814900140767310446640259899490222221765904339901886018566526485061799702356193897017860040811889729918311021171229845901641921068884387121855646124960798722908519296819372388642614839657382291123125024186649353143970137428531926649875337218940694281434118520158014123344828015051399694290153483077644569099073152433278288269864602789864321139083506217095002597389863554277196742822248757586765752344220207573630569498825087968928162753848863396909959826280956121450994871701244516461260379029309120889086942028510640182154399457156805941872748998094254742173582401063677404595741785160829230135358081840096996372524230560855903700624271243416909004153690105933983835777939410970027753472000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

No comments:

Post a Comment