Thursday, December 27, 2012

Python: Կապակցված ցուցակներ (I)

Որպես Python ծրագրավորման լեզվով գրված առաջին ընդգրկուն օրինակ ուզում եմ ներկայացնել կապակցված ցուցակները՝ նրանց ներքին կառուցվածքով և նրանց հետ կատարվող գործողությունների տիպիկ բազմությամբ։ Սկսեմ միակապ ցուցակներից, որոնց ամեն մի հանգույցը պարունակում է ինֆորմացիոն դաշտ և ցուցիչ իրեն հաջորդող հանգույցին։ Մոդելավորենք այդ հանգույցը Python լեզվով սահմանված Node դասով։ Այդ դասի data դաշտը նախատեսված է տվյալների համար, իսկ link դաշտը ցուցիչ է մեկ այլ հանգույցի։ Դասի դաշտերն արժեքավորող __init__ մեթոդը ստանում է հանգույցի data դաշտում գրվելիք տվյալը։
class Node:
  data = 0
  link = None

  def __init__(self, val):
    self.data = val
Node դասի Print մեթոդը արտածում է data դաշտի արժեքը, ապա, եթե link ցուցիչը տարբեր է None արժեքից՝ ցույց է տալիս մեկ այլ հանգույցի, ապա արտածում է ստորակետ սիմվոլը։
  def Print(self):
    c = ', ' if self.link != None else ''
    print(self.data, end=c)
Միակապ հանգույցներով կապակցված ցուցակը սահմանված է որպես List դաս։ Այդ դասի միակ head դաշտը ցույց է տալիս ցուցակի առաջին հանգույցին։
class List:
  data = None
Եթե head==None, ապա ցուցակը դատարկ է։ Այս վերջին փաստը ծրագրավորված է Empty մեթոդով։
  def Empty(self):
    return None == self.head
Ցուցակի պարունակությունն արտածելու համար սահմանված է Print մեթոդը։ Այն արտածում է «[» նիշը, անցում է կատարում ցուցակի հանգույցներով և ամեն մի հանգույցն արտածում է Node դասի Print մեթոդով, ապա վերջում արտածում է «]» նիշը։
  def Print(self):
    print('[', end='')
    temp = self.head
    while temp != None:
      temp.Print()
      temp = temp.link
    print(']')
Ցուցակի սկզբում նոր տարր (նոր հանգույց) ավելացնելու գործողությունը հասարակ է։ Պետք է ստեղծել նոր հանգույց, նրա link ցուցիչը կապել ցուցակի սկիզբը ցույց տվող head ցուցիչին, ապա head ցուցիչը տեղափոխել նոր ավելացրած հանգույցի վրա։
  def AddFront(self, val):
    nd = Node(val)
    nd.link = self.head
    self.head = nd
Քիչ ավելի շատ գործողություններ է պահանջվում նոր տարրը ցուցակի վերջում ավելացնելու համար։ Ստեղծվում է նոր հանգույց՝ տրված պարունակությամբ։ Եթե ցուցակը դատարկ է՝ head==None, ապա head ցուցիչը կապվում է նոր ստեղծված հանգույցին։ Եթե ցուցակը դատարկ չէ, ապա որևէ ժամանակավոր ցուցիչով որոշվում է ցուցակի վերջին հանգույցը և այդ վերջին հանգույցի link ցուցիչը կապվում է նոր հանգույցին։
  def AddBack(self, val):
    nd = Node(val)
    if self.head == None:
      self.head = nd
    else:
      tail = self.head
      while tail.link != None:
        tail = tail.link
      tail.link = nd
Ցուցակի սկզբից հանգույց հեռացնելու համար head ցուցիչը տեղափոխվում է առաջինին հաջորդող հանգույցի վրա (այն կարող է բացակայել, եթե ցուցակը պարունակում է միայն մեկ հանգույց), և վերադարձվում է հին առաջին (նախորդ) հանգույցի պարունակությունը։ Քանի որ Python-ը աղբի ավտոմատ հավաքման մեխանիզմով լեզու է, կարիք չկա բացահայտ կերպով ազատել հեռացված հանգույցի զբաղեցրած հիշողությունը։
  def RemoveFront(self):
    if self.head == None:
      return None
    t = self.head
    self.head = t.link
    t.link = None
    return t.data
Ցուցակի վերջից հանգույց հեռացնելու համար էլի պետք է մի քանի գործողություն ավել անել։ Նախ, եթե ցուցակը դատարկ է, ապա ոչինչ անել պետք չէ։ Եթե ցուցակը պարունակում է միայն մեկ տարր, ապա head ցուցիչին վերագրվում է None և վերադարձվում է այդ միակ հանգույցի պարունակությունը։ Եթե ցուցակում մեկից ավելի հանգույցներ են, ապա որևէ ժամանակավոր ցուցիչով որոնվում է նախավերջին հանգույցը։ Այդ նախավերջին հանգույցի link ցուցիչին վերագրվում է None, և վերադարձվում է վերջին հանգույցի պարունակությունը։
  def RemoveBack(self):
    if self.head == None:
      return None
    if self.head.link == None:
      val = self.head.data
      self.head = None
      return val
    else:
      tail = self.head
      while tail.link.link != None:
        tail = tail.link
      val = tail.link.data
      tail.link = None
      return val
Ցունցակում տրված արժեքով հանգույցը որոնելու համար Search մեթոդում մի ժամանակավոր ցուցիչ նախ կապվում է ցուցակի առաջին հանգույցին, ապա ցիկլով այն տեղաշարժվում է դեպի ցուցակի վերջը։ Ցիկլն ավարտվում է, երբ կա՛մ ժամանակավոր ցուցիչը հասել է ցուցակի վերջին, կա՛մ ցույց է տալիս մի հանգույցի, որի data դաշտը պարունակում է որոնվող արժեքը։
  def Search(self, val):
    temp = self.head
    while temp != None and temp.data != val:
      temp = temp.link
    return temp
Ցուցակում տարրեր կարելի է ավելացնոլ ոչ միայն սկզբից կամ վերջից, այլ նաև որևէ հանգույցից առաջ կամ հետո։ InsertAfter մեթոդը ստանում է մի արժեք և ցուցակի մի որևէ հանգույց, ապա տրված արժեքով մի նոր հանգույց է ավելացնում ցուցակի տրված հանգույցից հետո։
  def InsertAfter(self, val, node):
    nd = Node(val)
    nd.link = node.link
    node.link = nd
Իսկ InsertBefore մեթոդը տրված արժեքով նոր հանգույց է ավելացնում ցուցակի տրվախ հանգույցից առաջ։
  def InsertBefore(self, val, node):
    self.InsertAfter(val, node)
    node.link.data, node.data = node.data, node.link.data
Համապատասխանաբար RemoveAfter և RemoveBefore մեթոդները հեռացնում են ցուցակի տրվաց հանգույցին հաջորդող ու նախորդող հանգույցները՝ վերադարձնելով հեռացված հանգույցի պարունակությունը։
  def RemoveAfter(self, node):
    if node.link == None:
      return None
    t = node.link
    node.link = t.link
    t.link = None
    return t.data

  def RemoveBefore(self, node):
    if self.head == node:
      return None
    temp = self.head
    while temp.link != node:
      temp = temp.link
    val = temp.data
    temp.data = node.data
    self.RemoveAfter(temp)
    return val
Եվ վերջապես, մի հետաքրքիր գործողություն ևս։ Reverse մեթոդը շրջում է կապակցված ցուցակը։ Ահա այդ պարզ ալգորիթմը։
  def Reverse(self):
    a = self.head
    b = None
    while a != None:
      c = a.link
      a.link = b
      b = a
      a = c
    self.head = b

Monday, December 24, 2012

Tcl: Օբյեկտներին կողմնորոշված ծրագրավորման օրինակ

Մի քանի օր առաջ նորությունների կայքում կարդացի Tcl ծրագրավորման լեզվի 8.6 (Tcl 8.6) տարբերակի թողարկման մասին։ Հաղորդագրության մեջ, ի թիվս այլ կետերի, նշվում էր նաև, որ TclOO փաթեթը արդեն հանդիսանում է լեզվի բաղկացուցիչ մաս՝ որպես օբյեկտներին կողմնորոշված ծրագրավորման հիմնական միջոց։

Փորձեցի մաթեմատիկական արտահայտությունների օրինակով գրել մի կարճ ծրագիր՝ օգտագործելով հենց այդ TclOO ընդլայնումը։ Այս օրինակս ընդհանուր պատկերացում տալիս է դասերի, կոնստրուկտորների, մեթոդների ու դաշտերի դահմանման մասին։

Եվ այսպես, նախ սահմանեմ expression աբստրակտ դասը, որը ներկայացնում է միակ evaluate մեթոդը։
oo::class create expression {
  method evaluate {} {}
}
Այստեղ oo::class հրամանի create ենթահրամանով սահմանվում է դասը։ method հրամանով սահմանվում են դասի մեթոդները (այն շատ նման է proc հրամանին)։

Որպես expression աբստրակտ դասի առաջին ընդլայնում սահմանեմ number դասը։ Նշելու համար, որ այն expression դասի ընդլայնում է (ենթադաս է), օգտագործվել է superclass հրամանը՝ արգումենտում expression դասի անունով։
oo::class create number {
  superclass expression
  variable value
  constructor { v } {
    my variable value
    set value $v
  }
  method evaluate {} {
    my variable value
    return $value
  }
}
variable հրամանով հայտարարվում են դասի դաշտերը։ Այս դասի համար ես նախատեսել եմ թվի արժեքը ներկայացնող value դաշտը։ constructor հրամանով սահմանվում է դասի կոնստրուկտորը։ Այն ստանում է արգումենտների ցուցակ և կոնստրուկտորի մարմինը։ Դասի մեթոդներում, ինչպես նաև կոնստրուկտորում դասի դաշտերը (փոփոխականները) մատչելի են դառնում my հրամանով։ "my variable value" տողը հնարավորություն է տալիս կոնստրուկտորի մարմնում աշխատել value փոփոխականի հետ։ number դասի համար իրականացված evaluate մեթոդը պարզապես վերադարձնում է value փոփոխականի արժեքը։

expression դասի երկրորդ ընդլայնումը ունար գործողությունները մոդելավորող unaryex դասն է։ Սրա կոնստրուկտորը ստանում է ունար գործողության նշանակումը և այն ենթաարտահայտությունը, որի վրա պետք է կիրառել գործողությունը։
oo::class create unaryex {
  superclass expression
  variable oper subex
  constructor { op ex } {
    my variable oper subex
    set oper $op
    set subex $ex
  }
  method evaluate {} {
    my variable oper subex
    set res [$subex evaluate]
    if {$oper eq "-"} {
      set res -$res
    }
    return $res
  }
}
evaluate մեթոդը նախ հաշվում է ենթաարտահայտության արժեքը, ապա, եթե գործողությունը "-" է, վերադարձնում է արժեքի բացասումը։

Եվ վերջապես, expression դասի մի ընդլայնում ևս։ Սահմանեմ binaryex դասը, որը մոդելավորում է բինար "+", "-", "*", "/" և "%" գործողությունները։
oo::class create binaryex {
  superclass expression
  variable oper subex0 subex1
  constructor { op exo exi } {
    my variable oper subex0 subex1
    set oper $op
    set subex0 $exo
    set subex1 $exi
  }
  method evaluate {} {
    my variable oper subex0 subex1
    set res0 [$subex0 evaluate]
    set res1 [$subex1 evaluate]
    set result 0
    switch $oper {
      "+" { set result [expr $res0 + $res1] }
      "-" { set result [expr $res0 - $res1] }
      "*" { set result [expr $res0 * $res1] }
      "/" { set result [expr $res0 / $res1] }
      "%" { set result [expr $res0 % $res1] }
    }
    return $result
  }
}
Այստեղ առանձնապես բացատրելու բան չկա. օգտագործված են Tcl լեզվի պարզագույն հրամաններ։

* * *
Դասերի հիերարխիան ստուգելու համար կազմեմ "(10 + 2 - 6) * 5 / -3" արտահայտության հաշվարկի ծրագիրը (որի արժեքը -10 է)։
proc example0 {} {
  # (10 + 2 - 6) * 5 / -3 = -10
  set n10 [number new 10]
  set n2 [number new 2]
  set n6 [number new 6]
  set n5 [number new 5]
  set n3 [number new 3]
  
  set u0 [unaryex new "-" $n3]
  
  set b0 [binaryex new "+" $n10 $n2]
  set b1 [binaryex new "-" $b0 $n6]
  set b2 [binaryex new "*" $b1 $n5]
  set b3 [binaryex new "/" $b2 $u0]
  
  set result [$b3 evaluate]
  return $result
}

* * *
Վերջում նշեմ, որ բոլոր փորձարկումներն արել եմ Tcl լեզվի ActiveTcl 8.6 իրականացմամբ։

Friday, December 21, 2012

Python: Բառարանի օգտագործումը

Այս գրառման մեջ ես առաջարկում եմ ևս մի լուծում իմ նախորդ գրառման մեջ առաջարկված և Tcl լեզվով լուծված խնդրի համար։ Նորից հիշեցնեմ խնդրի ձևակերպումը.
Տրված է որևէ գեղարվեստական ստեղծագործության տեքստ։ Կազմել տեքստում հանդիպող բառերի հաճախության բառարան, որտեղ ամեն մի բառին համապատասխանեցված է տեքստում նրա հանդիպելու քանակը։ Հաշվել տեքստի առանձին բառերի քանակի հարաբերությունը բոլոր բառերի քանակին։ Արտածել տաս ամենաշատ օգտագործված բառերի խմբերը։ Արտածել տաս ամենաերկար բառերը և նրանց հանդիպելու քանակը։ Արտածել միայն մեկ անգամ հանդիպող բառերի ցուցակը։
Այս անգամ նույն խնդրի լուծումը տալիս եմ Python ծրագրավորման լեզվով։


Եվ այսպես, տեքստից մեզ չհետաքրքրող (բառ չձևավորող) սիմվոլները հեռացնելու համար օգտագործելու ենք կանոնավոր արտահայտությունների re մոդուլը։
import re
Դատարկ բառարանը ստեղծվում է dict դասի կոնստրուկտորի կանչով։
words = dict()
Տեքստը տող առ տող ֆայլից կարդալու և բառերի տրոհելու համար with հրամանով ստեղծենք կատարման կոնտեքստ, որում fin փոփոխականին կապված է open հրամանով կարդալու համար բացված տեքստային ֆայլը (Ջեկ Լոնդոն, "Մարտին Իդեն"): Կատարման կոնտեքստում for հրամանով իտերացիա կազմակերպենք ֆայլի տողերով։ re մոդուլի sub մեթոդը տրված տողում փոխարինում է տրված կանոնավոր արտահայտությամբ ճանաչված հատվածները մեկ այլ տրված տեքստով։ Փոխարինումից հետո տողի բոլոոր մեծատառերը lower մեթոդով դարձնենք փոքրատառեր ու split մեթոդով տողը կտրտենք բառերի։ if հրամանով ստուգենք որ բառի երկարությունը մեծ լինի մեկից։ Հաջորդ if հրամանով և not in գործողությամբ ստուգենք ընթացիկ բառի առկայությունը բառարանում. եթե այն բացակայում է, ապա ավելացնում ենք՝ հաշվիչի 0 սկզբնական արժեքով։ Հաջորդ քայլում պարզապես հերթական բառի հաշվիչն ավելացնում ենք մեկով։
with open('martin-eden-jack-london.txt') as fin:
  for line in fin:
    line = re.sub('[^a-zA-Z]+', ' ', line)
    for wr in line.lower().split(' '):
      if len(wr) > 1:
        if wr not in words:
          words[wr] = 0
        words[wr] += 1
len ֆունկցիան վերադարձնում է dict օբյեկտի գրառումների քանակը։ մեր դեպքում դա տեքտի իրարից տարբեր բառերի քանակն է։ sum ֆունկցիան վերադարձնում է ցուցակի տարրերի գումարը։ Այստեղ ցուցակը words բառարանի արժեքների ցուցակն է, որի տարրերի գումարը ստացվում է տեքստի բոլոր բառերի քանակը։ format մեթոդը կատարում է տողի ֆորմատավորում, այն նաև տեղադրում է տրված արժեքները տողի նշված տեղերում։
uniwords = len(words)
allwords = sum(words.values())
print('{0} / {1} = {2}'.format(uniwords, allwords, 1.0 * uniwords / allwords))
sorted ներդրված ֆունկցիան վերադարձնում է արգումենտում տրված ցուցակի ըստ աճման կարգավորված տարբերակը։ Եթե reverse անվանված արգումենտը տրված է True, ապա վերադարձնում է ըստ արժեքների նվազման կարգավորված ցուցակը։ dict օբյեկտի values մեթոդը վերադարձնում է բառարանի արժեքների ցուցակը։ Մյուս, items մեթոդը հնարավորություն է տալիս իտերացիա կազմակերպել բառարանի բանալի-արժեք զույգերով։ in գործողությունը ստուգում է տարրի պատկանելիությունը ցուցակին։
tenbignums = sorted(words.values(), reverse=True)[0:10]
for word, count in words.items():
  if count in tenbignums:
    print('{0} : {1}'.format(word, count))
sorted ֆունկցիան key անվանված արգումենտով ստանում է այն բնութագիրը, ըստ որի պետք է համեմատվեն ցուցակի տարրերը։ Այստեղ որպես բնութագիր տրված է մի անանուն ֆունկցիա՝ ստեղծված lambda արտահայտության օգնությամբ, որը վերադարձնում է արգումենտի չափը։ Այս կերպ կարողանում ենք բառարանի բանալիների ցուցակը, որը ստացվում է keys մեթոդով, կարգավորել ըստ տարրերի երկարության։
tenbigwords = sorted(words.keys(), key=lambda w: len(w), reverse=True)[0:10]
for wd in tenbigwords:
  print('{0} : {1}'.format(wd, words[wd]))
Եվ վերջապես, տեքստում միայն մեկ անգամ օգտագործված գրված է մի հետաքրքիր արտահայտություն։ Այն կարելի է կարդալ մոտավորապես այսպես. "անցնել բառարանի տարրերով և ցուցակ կազմել այն բանալիներից, որոնց համապատասխանեցված արժեքը հավասար է մեկի"։
onlyone = [wd for wd, cnt in words.items() if cnt == 1]
print(onlyone)
* * *
Որպես գաղտնիք նշեմ, որ, չնայած խնդրի լուծումը համապատասխանում է պահանջին, բայց, այնուամենայինիվ, պարունակում է որոշ թերություններ։

Wednesday, December 19, 2012

Tcl: Բառարանների օգտագործումը

Խնդիրը

Տրված է որևէ գեղարվեստական ստեղծագործության տեքստ։ Կազմել տեքստում հանդիպող բառերի հաճախության բառարան, որտեղ ամեն մի բառին համապատասխանեցված է տեքստում նրա հանդիպելու քանակը։ Հաշվել տեքստի առանձին բառերի քանակի հարաբերությունը բոլոր բառերի քանակին։ Արտածել տաս ամենաշատ օգտագործված բառերի խմբերը։ Արտածել տաս ամենաերկար բառերը և նրանց հանդիպելու քանակը։ Արտածել միայն մեկ անգամ հանդիպող բառերի ցուցակը։

Լուծումը

Դատարկ բառարանը ստեղծվում է dict հրամանի create ենթահրամանով: set հրամանը տրված փոփխականին (օբյեկտին, տեղին) վերագրում է տրված արժեքը։ Ստեղծենք words բառարանը, որն արտապատկերում է տեքստի բառերը տեքստում նրանց հանդիպելու քանակին.
set words [dict create]
Տող առ տող կարդանք տեքստային ֆայլը և նրա բառերն ավելացնենք հաճախությունների բառարանում։ open հրամանը տրված ֆայլը բացում է տրված ռեժիմով (գրել, կարդալ և այլն) և վերադարձնում է ֆայլի դեսկրիպտոր։ gets հրամանը ֆայլից կարդում և վերադարձնում է մեկ տող։ Եթե նրան տրված է երկրորդ արգումենտը, ապա կարդացած տողը վերագրվում է այդ արգումենտին, իսկ ֆունկցիան վերադարձնում է կարդացած նիշերի քանակը։ regsub հրամանը տողում փոփոխություններ է կատարում ըստ տրված կանոնավոր արտահայտության։ string հրամանի tolower ենթահրամանը տրված տողի բոլոր մեծատառերը դարձնում է փոքրատառ։ foreach հրամանը իտերացիա (ցիկլ) է կատարում տրված ցուցակով։ split հրամանը տողը կտրտում է՝ օգտագործելով տրված բաժանիչները։ string հրամանի length ենթահրամանը վերադարձնում է տողի երկարությունը։ if հրամանը կատարում է մարմնում տրված հրամանները, եթե պայմանը ճշմարիտ է։ dict հրամանի exists ենթահրամանը ստուգում է արդյո՞ք տրված բառարանում առկա է տրված բանալին, իսկ set ենթահրամանը բառարանում ավելացնում է տրված բանալի-արժեք զույգը։ dict հրամանի մեկ այլ, incr ենթահրամանը տրված արժեքն ավելացնում է բառարանի տրված բանալիին համապատասխան արժեքին։ close հրամանը փակում է բացած ֆայլը։
# բացել տեքստային ֆայլը կարդալու համար
set fin [open {martin-eden-jack-london.txt} r]
while {[gets $fin line] >= 0} {
  # հեռացնել բոլոր տառ չհանդիսացող սիմվոլները
  set line [regsub -all -- {\W+} $line { }]
  # տողի բոլոր սիմվոլները դարձնել փոքրատառ
  set line [string tolower $line]
  # կտրտել տողը և անցնել բառերով
  foreach wd [split $line { }] {
    # դիտարկել միայն մեկից մեծ երկարությամբ բառերը
    if {[string length $wd] > 1} then {
      # եթե բառարանում չկա տվյալ բառին համապատասխան գրառում
      if {![dict exists $words $wd]} then {
        # ավելացնել այն՝ զրո արժեքով
        dict set words $wd 0
      }
      # մեկով ավելացնել դիտարկվող բառի ցուցիչը
      dict incr words $wd
    }
  }
}
# փակել տեքստային ֆայլը
close $fin
dict հրամանի size ենթահրամանը վերադարձնում է բառարանի տարրերի քանակը, իսկ for ենթահրամանը իտերացիա է կազմակերպում բառարանի բանալի-արժեք զույգերով։ incr հրամանը տրված փոփոխականին գումարում է տրված արժեքը։
# ունիկալ (առանձին) բառերի քանակը
set uniwords [dict size $words]
# բոլոր բառերի քանակի հաշվարկը
set allwords 0
# անցում բառարանի բանալի-արժեք զույգերով
dict for {k v} $words {
  incr allwords $v
}
# առանձին բառերի քանակի հարաբերությունը բոլոր բառերի քանակի
puts "$uniwords / $allwords = [expr 1.0 * $uniwords / $allwords]"
Նախապատրաստենք մի նոր բառարան, որն արտապատկերում է քանակը բառերի ցուցակին։ Այն օգտագործվելու է տրված քանակով բառերի ցուցակի ստացման համար։ dict հրամանի lappend ենթահրամանը տրված արժեքը կցում է բառարանի տրված բանալուն համապատասխանեցված ցուցակին։
set counts [dict create]

# անցում բառարանի բանալի-արժեք զույգերով
dict for {k v} $words {
  # եթե բառարանում հերթական քանակին համապատասխան բառերի ցուցակը դատարկ է
  if {![dict exists $counts $v]} then {
    # ապա ստեղծել նոր արտապատկերում դատարկ ցուցակով
    dict set counts $v [list]
  }
  # դիտարկվող բառն ավելացնել համապատասխան թվի ցուցակում
  dict lappend counts $v $k
}
lrange հրամանը վերադարձնում է տրված ցուցակի մի հատվածը՝ նորից ցուցակի տեսքով։ lsort հրամանը կարգավորում է ցուցակի տարրերը տրված պայմանով։ dict հրամանի keys ենթահրամանը վերադարձնում է բառարանի բանալիների ցուցակը։ puts հրամանը ստանդարտ արտածման հոսքին է արտածում տրված արժեքը։
# տաս ամենահաճախ օգտագործված բառերը
foreach num [lrange [lsort -decreasing -integer [dict keys $counts]] 0 10] {
  puts "[dict get $counts $num] : $num"
}
proc հրամանով սահմանվում են նոր պրոցեդուրաներ (կամ ֆունկցիաներ)։ expr հրամանը հաշվարկում և վերադարձնում է իր արգումենտում տրված արտահայտության արժեքը։ return հրամանը նախատեսված է ֆունկցիայից արժեքի վերադարձի համար։
# մի ֆունկցիա, որը տողերի կարգի հարաբերություն է սահմանում ըստ երկարության
proc cmplen {a b} {
  return [expr [string length $a] < [string length $b]]
}
lsort հրամանը կարող է -command պարամետրով ստանալ կարգի հարաբերությունը։
# տաս ամենաերկար բառերը և նրանց հաճախությունները 
foreach wd [lrange [lsort -command cmplen [dict keys $words]] 0 10] {
  puts "$wd : [dict get $words $wd]"
}
dict հրամանի get ենթահրամանը վերադարձնում է բառարանի տրված բանալուն համապատասխանեցված արժեքը։
# միայն մեկ անգամ օգտագործված բառերը
puts [dict get $counts 1]
exit հրամանով ավարտվում են Tcl ծրագրերը։
exit 0

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

Wednesday, December 12, 2012

Java: Գործիքների տեղադրում և գործարկում

Նախորդ գրառման մեջ ես պատմեցի, թե ինչպես խմբագրել (edit), թարգմանել (compile) և կատարել (run) պարզագույն Java ծրագիրը։ Ենթադրվում էր, որ համակարգում արդեն տեղադրված է Java կատարման միջավայրն (JRE) ու ծրագրավորման գործիքները (JDK), և մեզ մնում է միայն որևէ տեքստային խմբագրիչով խմբագրել ծրագրի տեքստը, պահպանել այն ֆայլում, թարգմանել javac կոմպիլյատորով և կատարել java վիտուալ մեքենայով՝ բայթ-կոդի ինտերպրետատորով։

Այս գրառման մեջ ես ցույց կտամ, թե ինչպես Ubuntu GNU/Linux համակարգում տեղադրել Java ծրագրավորման լեզվով ծրագրավորելու համար անհրաժեշտ գործիքները՝ JRE, JDK և ծրագրավորման ինտեգրացված միջավայր NetBeans։

Տեղադրելու համար ես ընտրել եմ Java ծրագրավորման լեզվի OpenJDK իրականացումը։ Այն Ubuntu օպերացիոն համակարգում տեղադրելու համար պետք է տերմինալից ներածել հետևյալ հրամանները
$ sudo apt-get install openjdk-6-jre
$ sudo apt-get install openjdk-6-jdk
Տեղադրելուց հետո կարող ենք ստուգել կոմպիլյատորի ու վիրտուալ մեքենայի առկայությունը՝ պահանջելով արտածել տեղադրված տարբերակի մասին տեղեկությունները։
$ javac -version
javac 1.6.0_24
$ java -version
java version "1.6.0_24"
OpenJDK Runtime Environment (IcedTea6 1.11.5) (6b24-1.11.5-0ubuntu1~12.04.1)
OpenJDK 64-Bit Server VM (build 20.0-b12, mixed mode)
Հիմա կարող ենք թարգմանել ու կատարել թվի ֆակտորիալը հաշվող ծրագիրը։

NetBeans ծրագրավորման ինտեգրացված միջավայրը տեղադրելու համար այստեղից պետք է ներբեռնել համապատասխան փաթեթը։ (Ես ընտրել եմ NetBeans 7.2.1 Java SE (77 MB) տարբերակը, որի ֆայլը կոչվում է netbeans-7.2.1-ml-javase-linux.sh։) Այնուհետև, եթե բեռնված ֆայլը կատարվող չէ, ապա chmod հրամանով պետք է այն դարձնել կատարվող և գոծարկել հրամանային տողից։ Տեղադրման պրոցեսը շատ պարզ է (տեղադրող ծրագիրը ճանաչում է համակարգում տեղադրած Java լեզվի իրականացումը):
Տեղադրելուց հետո, երբ գործարկենք NetBeans միջավայրը, կտեսնենք ահա այսպիսի պատկեր․


Windwows օպերացիոն համակարգի համար կարելի է Oracle կազմակերպության սայթից մեկ փաթեթով ներբեռնել Java լեզվի իրականացումն ու NetBeans միջավայրը։ Այդ փաթեթի տեղադրումը նույնպես շատ պարզ է։
* * *
Հիմա, երբ տեղադրված են անհրաժեշտ գործիքները, նորից գրենք թվի ֆակտորիալը հաշվող ծրագիրը, բայց արդեն NetBeans միջավայրի օգտագործմամբ։

1. NetBeans միջավայրի «File» մենյուից ընտրում ենք «New Project...» կետը, որից հետո բացված պատուհանում «Categories» ցուցակից ընտրում ենք «Java» կետը, իսկ «Projects» ցուցակից՝ «Java Application» կետը։ Ապա սեղմում ենք «Next» կոճակը։


2. Հաջորդ քայլում փոխարինում ենք «Project Name» դաշտի արժեքը «Factorial» բառով և սեղմում ենք «Finish» կոճակը։


NetBeans միջավայրը ստեղծում է factorial անունով փաթեթ և գեներացնում է Factorial.java ֆայլի մակետը, որը պարունակում է Factorial դասի նախնական սահմանումը՝ main մեթոդով։



Հիմա տեսնենք, թե ի՞նչ է գեներացրել NetBeans-ը Factorial դասի համար։ Եթե դեն նետենք մեկնաբանությունները, որոնք, ի դեպ, ժառանգվել են ծրագրավորման C լեզվից, ապա կտեսնենք հետևյալը․
package factorial;

public class Factorial {
    public static void main(String[] args)
    {

    }
}
Այստեղ նորություն է միայն package factorial; տողը։ Այն տեղեկացնում է, որ Factorial դասը պատկանում է factorial փաթեթին։ Փաթեթը Java լեզվում ծրագրային բաղադրիչների խմբավորման միջոց է։ Այս պարզագույն խնդրում ունենք միայն մեկ փաթեթ՝ factorial, որը պարունակում է միակ Factorial դասը։ Հետագա օրինակներում կտեսնենք, թե ինչպես մեծ նախագիծը փաթեթների օգնությամբ տրոհել տրամաբանական միավորների։
Այս գեներացված main ֆունկցիայի մարմինը լրացնենք ֆակտորիալի հաշվման ալգորիթմով և սեղմենք NetBeans միջավայրի գործիքների տողի կոճակը։
Ծրագրի կատարվելուց հետո NetBeans միջավայրի ներքևի մասում բացվում է արտածումների պատուհանը՝ Output, որում էլ արտածվում են ծրագրի աշխատանքի արդյունքները։

* * *

Այսքանով ես կսահմանափախեմ գործիքներին վերաբերող ամենաանհրաժեշտ տեղեկությունները։ Հետագա խնիրները ներկայացնելիս արդեն գործիքների այս կամ այն հնարավորություններին հղումներ կկատարեմ միայն անհրաժեշտության դեպքում։

Tuesday, December 11, 2012

Java: Առաջին ծրագիրը

Արդեն դարերի ավանդույթ է դարձել որևէ ծրագրավորման լեզվի հնարավորությունները ցուցադրելիս որպես առաջին ծրագրի օրինակ մատուցել ստանդարտ արտածման հոսքի վրա "Hello, World!" տեքստն արտծող ծրագիրը։ Մի կողմ թողնենք այն և որպես առաջին ծրագիր դիտարկենք տրված դրական ամբողջ թվի ֆակտորիալը հաշվող և արտածող ծրագիրը։ Այն, կարծում եմ, և՛ ավելի հետաքրքիր է, և՛ ավելի խոսուն։

Եվ այսպես. տրված n դրական ամբողջ թվի ֆակտորիալը դա 1-ից n ամբողջ թվերի արտադրյալն է։ Այն հաշվելու համար պարզապես պետք է կազմակերպել մի ցիկլ՝ կրկնություն, որն անցնում է 1..n թվերով և կուտակում է դրանց արտադրյալը։ Փսևդոկոդով գրելու դեպքում, օրինակ 12 թվի ֆակտորիալը հաշվելու համար, կունենանք ահա այսպիսի ծրագիր.
n = 12
prod = 1
WHILE n > 0 DO
  prod = prod * n
  n = n - 1
END
PRINT prod
Java ծրագրի կատարումը սկսվում է գլխավոր դասի main անունով ստատիկ մեթոդից։ Դասը սահմանվում է class ծառայողական բառով, որին հետևում է դասի անունը, ապա մեթոդների ու դաշտերի սահմանումները։ Օրինակ, Factorial անունով դասը կարող ենք սահմանել հետևյալ կերպ.
public class Factorial {
...
}
Որտեղ public բառն ասում է, որ տվյալ դասը կարող են օգտագորվծել այլ փաթեթներում (փաթեթների մասին քիչ ավելի ուշ)։
Factorial դասի համար սահմանենք main ստատիկ մեթոդը.
public class Factorial {
  public static void main(String[] args)
  {
    ...
  }
}
Նորից public ծառայողական բառն ասում է, որ main մեթոդը տեսանելի է Factorial դասից դուրս։ static բառն ասում է, որ այս մեթոդն ընդհանուր է Factorial դասի մոլոր նմուշների համար (սրանք էլ մանրամասնորեն կքննարկենք ավելոի ուշ)։ void բառն ասում է, որ main մեթոդը որևէ արժեք չի վերադարձում։ main մեթոդի արգումենտների ցուցակում գրված "String[] args" արտահայտությունը նշում է, որ այս մեթոդը սպասում է (ընդունում է, ակնկալում է) մեկ արգումենտ՝ տողերի միաչափ զանգված (վեկտոր)։ main մեթոդի կատարման ժամանակ նրա արգումենտն արժեքավորվում է հրամանային տողի պարունակությամբ (այս մասին էլ ավելի ուշ)։
Հիմա սկսենք ֆակտորիալի հաշվարկը։ Ասացինք, որ աշխատելու ենք ամբողջ թվերի հետ։ Հայտարարենք n և prod ամբողջ թվերը՝ առաջինն արժեքավորելով 12 արժեքով, իսկ երկրորդը՝ 1 արժեքով։
int n = 12, prod = 1;
Կազմակերպենք ցիկլ, որը կատարվում է քանի դեռ ճշմարիտ է n > 0 պայմանը։ Իսկ ցիկլի մարմնում հաշվարկվում է prod = prod * n արտադրյալը, և մեկով նվազեցվում է n փոփոխականի արձեքը։
Պայմանով ցիկլերը կազմակերպվում են while կառուցվածքով։ Այն կատարում է իր մարմնում գրված հրամաններն այնքան ժամանակ, քանի դեռ ճշմարիտ է կրկնման պայմանը։
while( n > 0 ) {
  prod = prod * n;
  n = n - 1;
}
Տվյալ դեպքում ցիկլն անպայման կավարտվի, քանի որ կրկնությունների ընթացքում n դրական թվի արժեքը շարունակ նվազում է։ Եվ երբ ավարտվի ցիկլը, prod փոփոխականում կուտակված կլինի 1..n թվերի արտադրյալը։
Եվ վերջապես, ինչպե՞ս արտածել հաշվարկման արդյուքները։ Java լեզվի ստանդարտ գրադարանի System դասի out դաշտի println մեթոդը ստանդարտ արտածման հոսքի վրա դուրս է բերում իր արգումենտում տրված արժեքը։ prod փոփոխականի արժեքը արտածելու համար պետք է գրել.
System.out.println(prod);
* * *
Ի մի բերելով շարադրվածը կազմենք ամբողջական ծրագիրը և կատարենք այն։ Նախ՝ որևէ տեքտային խմբագրիչոով ստեղծենք Factorial.java անունով ֆայլ և նրա մեջ գրենք հետևյալը.
/*
  First program in java
*/
public class Factorial {
  public static void main(String[] args)
  {
    int n = 12, prod = 1;
    while( n > 0 ) {
      prod = prod * n;
      n = n - 1;
    }
    System.out.println(prod);
  }
}
Պահպանենք ֆայլը պրոյեկտների համար նախատեսված մի պանակում՝ նախապես այս օրինակի ֆայլերի համար ստեղծելով factorial ենթապանակը (այն ինձ մոտ home-ում ստեղծված Project/java-examples պանակում է)։ cd հրամանով փոխենք աշխատանքային պանակն այնտեղ, որտեղ պահպանված է Factorial.java ֆայլը, և, ենթադրելով, որ համակարգում արդեն տեղադրված է Java լեզվի կոմպիլյատորն (javac) ու վիրտուալ մեքենան (java), թարգմանենք մեր գրած ծրագիրը բայթ-կոդի։
$ javac Factorial.java
Եթե թարգմանության՝ կոմպիլյացիայի պրոցեսում սխալներ չեն հայտնաբերվել, ապա հենց նույն պանակում ստեղծվում է Factorial.class անունով ֆայլ։ Սա մեր ծրագիրն է՝ Java ծրագրավորման լեզվից թարգմանած Java վիրտուալ մեքենայի բայթ-կոդերի։ Այն կատարելու համար պետք է կանչել Java վիրտուալ մեքենան՝ նրա արգումենտում տալով այն դասի անունը, որում սահմանված է main մեթոդը։ Մեր դեպքում դա միակ Factorial դասն է։
$ java Factorial
Տերմինալին արտածվում է 479001600, որը, կարող ենք ստուգել և համոզվել, հենց 12 թվի ֆակտորիալն է։
* * *
Սա առաջին ծրագիրն էր՝ գրված Java ծրագրավորման լեզվով։ Այս պահին դեռ ամեն ինչ չէ, որ պարզ ու հասկանալի է։ Մենք կարողացանք ծանոթանալ պարզագույն Java ծրագրի կառուցվածքին։ Տեսանք, թե ինչպես պետք է թարգմանել ու կատարել ծրագիրը հրամանային տողից։ Չնայած, որ ստացանք աշխատող ծրագիր, բայց բազմաթիվ հարցեր, թե՛ աշխատանքի տեխնիկայի, թե՛ խնդրի լուծման հետ կապված, դեռ մնում են չպարզաբանված։ Այս բլոգի հաջորդ գրառման մեջ ես կփորձեմ ընդգծել այս առաջին օրինակի թերություններն ու բացթողումները և առաջարկել դրանց լուծումները։