მრავალგანზომილებიანი მასივი Java-ში

გუშინ ერთ-ერთ ამოცანაში მეხსიერების ლიმიტს ვაჭარბებდი იმის გამო, რომ არასწორად მქონდა აღწერილი მრავალგანზომილებიანი მასივი. c-ში და c++-ში ამის პრობლემა არ იქნებოდა, თუმცა, იმის გამო, რომ ჯავაში ყველაფერი ობიექტია (პრიმიტიული ტიპების გარდა), მრავალგანზომილებიანი მასივების შექმნისას მეხსიერება განსხვავებული გზით უნდა დავითვალოთ.

მაგალითად ასეთი მასივის ზომა c++-ში 96 ბაიტია (32 ბიტიან სისტემაში):
int arr[2][3][4];
2 * 3 * 4 = 24. თითოეული კიდევ 4 ბაიტიანია int ტიპის გამო და ჯამში 24 * 4 = 96 ბაიტი გამოდის.
int arr[4][3][2] ასეთი აღწერის დროს შედეგი იგივეა.

შევადაროთ Java-ში ეს ორი აღწერა:
int arr[][][] = new int[40000][200][2];
int arr[][][] = new int[2][200][40000];

აზრობრივად განსხვავება არ არის, თუმცა პირველი გზა გაცილებით მეტ მეხსიერებას მოიხმარს. აი, რატომ:
1. ჯავას მასივი თავისთავად ობიექტია. თითოეულ ობიექტში (არა reference-თან, არამედ heap-ში) დამატებით რამდენიმე ბაიტს იკავებს სპეციალური object header ინფორმაცია. object header-ში ინახება ვირტუალური მანქანისთვის საჭირო მონაცემები, რასაც garbage collector-ისთვის და სხვადასხვა მიზნებისთვის იყენებს. როგორც ვიცი, ჩვეულებრივ ეს 8 ბაიტია ხოლმე 32-ბიტიან მანქანაზე და 16 ბაიტი 64-იანზე. ამის გარდა, მასივის ობიექტში ინახება მასივის ზომაც, ანუ დამატებით 4 ბაიტი. კიდევ შეიძება მეხსიერებაში padding-ს დასჭირდეს რამდენიმე ბაიტი. ამიტომ ზუსტად არ დავთვლი და ვთქვათ, ერთი მასივის ობიექტისთვის (ელემენტების გარდა) საჭიროა X ბაიტი.

2. int[a][b] – ჯავაში ეს არის a ცალი b ელემენტიანი მასივის მასივი, ანუ სინამდვილეში ერთის მაგივრად a+1 ობიექტი გვაქვს.
int[a][b][k]სამგანზომილებიანის შემთხვევაში a * b + a + 1 ცალი ობიექტი და ა.შ.

ახლა გამოვთვალოთ და შევადაროთ ზევით ნახსენები ორი მასივის ზომა:
(40,000 * 200 + 40,000 + 1) * X + (40,000 * 200 * 2 * 4)
(2 * 200 + 2 + 1) * X + (40,000 * 200 * 2 * 4)

მეორე ნაწილი რაც int ტიპის ელემენტების ზომას ითვლის, ცხადია, ორივესთვის ერთი და იგივე იქნება.
პირველი ნაწილის მიხედვით კი 8,039,598 ცალი ზედმეტი ობიექტი იქმნება პირველ შემთხვევაში, მეხსიერებაც შესაბამისად გაცილებით მეტი სჭირდება.

ისე, პროფაილერით ვერ ვხედავ სინამდვილეში მართლაც ამდენი ობიექტია თუ არა და იდეა ხომ არ გაქვთ როგორ შეიძლება შევამოწმო?

დინამიური მასივი

პროგრამირებაში დინამიური მასივი ისეთი მონაცემთა სტრუქტურაა, რომელსაც ცვლადი სიგრძე აქვს, მასში ელემენტების ჩამატება / წაშლა და random access შეიძლება – ანუ მის ნებისმიერ ელემენტზე წვდომას ფიქსირებული დრო სჭირდება და მასივის მთლიან ზომაზე არ არის დამოკიდებული.

ასეთ სტრუქტურას სხვადასხვა დასახელებით შეხვდებით – dynamic array, growable array, resizable array, dynamic table, mutable array, array list.

random access-ის უზრუნველსაყოფად მასივისთვის მეხსიერების უწყვეტი ნაწილი გამოიყოფა, ანუ, მისი ელემენტები მეხსიერებაში ერთმანეთის მიყოლებით ინახება.
სტატიკური მასივის შემთხვევაში ეს პრობლემას არ წარმოადგენს, რადგან ჩვენ თავიდანვე ვეუბნებით სიგრძეს. დინამიურ მასივს კი სწორედ მაშინ ვიყენებთ, როდესაც წინასწარ არ შეგვიძლია ზომის განსაზღვრა. მაშინ რამხელა მეხსიერება უნდა გამოიყოს, რომ ელემენტები კვლავ მიყოლებით არსებულ ბაიტებში იქნას შენახული?

ცხადია, თავის დაზღვევის მიზნით დიდი მეხსიერების გამოყოფას აზრი არ აქვს. 20 ელემენტის შენახვისთვის მილიონი ბაიტი წინასწარ არ უნდა დარეზერვდეს ასეთი მასივისთვის, რომელიც შემდეგ ცარიელი დარჩება.

ბევრ პროგრამირების ენაში ეს პრობლემა შემდეგნაირად არის გადაწყვეტილი:
დინამიური მასივის უკან სტატიკური მასივი დგას, რომელიც დასაწყისში მცირე სიგრძის მითითებით იქმნება. როდესაც მასივი გაივსება და შემდეგ კვლავ შეეცდებიან მასში ელემენტის ჩამატებას, შეიქმნება უფრო დიდი ზომის სტატიკური მასივი, მასში გადაიწერება არსებული მასივის ელემენტები და ძველი წაიშლება. შესაბამისად, დინამიურ მასივში ზოგიერთი ელემენტის ჩამატებას შედარებით მეტი დრო მიაქვს.

ასეთ გადაწყვეტაში მნიშვნელოვანია შეკითხვაზე პასუხი: რამდენჯერ უნდა გაიზარდოს მასივი გადაწერის წინ?

აღვნიშნოთ, რომ ისევ კომპრომისს ვეძებთ დროის და მეხსიერების ხარჯვას შორის. თუ თითო-თითოდ გავზრდით მასივს, ყოველ ახალ ელემენტზე მთელი მასივის გადაწერა დროს წაიღებს, ხოლო თუ რამდენჯერმე დავაგრძელებთ, შეიძლება დიდი ზომის მეხსიერება ცარიელი დაგვრჩეს.

ოპტიმალურ ვარიანტად რიცხვი 2 არის მიღებული. ამ რიცხვს ზრდიან ამ ამცირებენ იმის მიხედვით მეტი მეხსიერების ხარჯი ურჩევნიათ თუ დროის.

სხვადასხვა პროგრამირების ენაში ან რეალიზაციაში ის ზოგჯერ განსხვავებულია – მაგალითად, ვექტორი c++-ში 2-ჯერ იზრდება, Java-ს ვექტორისთვისაც default მნიშვნელობა ეს რიცხვია, თუმცა პარამეტრის სახით არის და შეცვლა შეიძლება. Java-ს ArrayList-ის სტატიკური მასივი 3/2-ჯერ იზრდება. HashMap-ის მასივი 2-ჯერ. პითონის C-ის იმპლემენტაციაში რაღაც უცნაურად არის, 9/8 გამოდის საშუალოდ. ამ ბმულზეა კოდი. აქ კი მისი გარჩევა

თუ პროგრამისტისთვის წინასწარ არის ცნობილი მინიმუმ რამხელა მასივი დასჭირდება, შეუძლია ეს გაითვალისწინოს და დინამიური მასივი თავიდანვე იმხელა შეიქმნას.
მაგალითად c++-ის vector-ს აქვს ფუნქცია reserve რომლითაც წინასწარ შეიძლება მეხსიერების რეზერვირება.

Java-ში ArrayList და HashMap ობიექტებს კონსტრუქტორში გადაეცემა initialCapacity პარამეტრი. HashMap-ში არა მხოლოდ მასივის გადაწერა, არამედ ჰეშების თავიდან გენერაცია ხდება.

წარმადობისთვის ამ პარამეტრის წინასწარ მითითება უმჯობესია. რამდენიმე ექსპერიმენტი ჩავატარე და მართლაც მივიღე დროში სხვაობა, თუმცა ჩემს ჩვეულებრივ ამოცანებში ასეთი სხვაობა უმნიშვნელოა. ბოლო ბოლო ლოგარითმული რაოდენობით ხდება მასივის გადაწერა და მილიონის შემთხვევაშიც კი ეს სულ რაღაც 20-ია. თუ მილიწამები მნიშვნელოვანია, იქ ღირებული იქნება ასეთი ოპტიმიზაცია.

ზევით ვახსენეთ, რომ დინამიურ მასივში ამ გადაწერის საჭიროების შედეგად ზოგიერთი ელემენტის ჩამატების დროს O(1)-დან იზრდება O(n)-მდე, სადაც n მასივში არსებული ელემენტების რაოდენობაა. ამის მიუხედავად, ამორტიზებული ანალიზის შედეგად, დინამიურ მასივში ელემენტის ჩამატების დრო O(1)-ად არის მიჩნეული.

თვითონ ამორტიზებული ანალიზის არსი იმაში მდგომარეობს, რომ ალგორითმის მუშაობის შეფასებისას გათვალისწინებული იქნას ის სწრაფი ოპერაციებიც, რომელიც ყველაზე ცუდი შემთხვევების მუშაობის დროს აბათილებენ. ალგორითმის შესრულების დროის შეფასებისას worst-case სცენარს ვიღებთ ხოლმე, თუმცა შეიძლება რომ დასამუშავებელ ინფორმაციაში მხოლოდ რაღაც პროპორციით იყოს მძიმე ოპერაციები. შესაბამისად, თუ ჩვენ ყველა ოპერაციას გავითვალისწინებთ შეფასების დროს, შესაძლოა მუშაობის დრო გაცილებით ნაკლები გამოვიდეს, ვიდრე რაოდენობა გამრავლებული ყველაზე დიდ დროზე.

მოდი გამოვთვალოთ n ელემენტიანი დინამიური მასივის შევსების დრო:
თუ ჩავთვლით რომ ყოველ ჯერზე (როდესაც ორის ხარისხს მივაღწევთ) სტატიკური მასივი ორმაგდება, შეგვიძლია ელემენტების გადაწერის რაოდენობა ასე შევაფასოთ:
ბოლოდან მოვყვეთ. ბოლოს ყველას გადაწერა მოუწევს, იმის წინ მხოლოდ ნახევრის, იმის წინ მეოთხედის და ა.შ.
n + n/2 + n/4 + n/8 + … = n (1 + 1/2 + 1/4 + 1/8 + …) = 2n

ამ გადაწერებს მივუმატოთ თვითონ ელემენტების ჩასმაც და გამოვა 3n. შესაბამისად საშუალოს თუ ავიღებთ, თითოეული ელემენტის ჩასმისთვის გამოგვივა O(3) = O(1) დრო.

jobs.ge საიტის REST კლიენტი ანდროიდზე

ალბათ გსმენიათ თბილისის GTUG-ის შესახებ – ეს არის თბილისის Google Technology User Group ქომუნითი, სადაც ჯერ ჯერობით მაინც დეველოპერები ჭარბობენ. ისინი ქმნიან და ერთმანეთს აცნობენ თავის პროდუქტებს, უყვებიან საკუთარ გამოცდილებას. ამიტომ GTUG-ის შეკრებებზე სხვადასხვა ტიპის სემინარებიც ტარდება ხოლმე. მაგალითად იყო მოხსენებები ანდროიდზე, RESTful ვებ სერვისებზე, რჩევებზე თუ როგორ შეიძლება შევქმნათ უკეთესი პროგრამული უზრუნველყოფა, Responsive web დიზაინზე, გუგლის App Engine-ზე, Cross-platform Mobile Development-ზე, Machine learning-ზე (გუგლის Prediction API), GWT-ზე, გუგლის რუკებზე, NoSQL მონაცემთა ბაზებზე, ალგორითმებზე…

როდესაც პირველი შეკრებები ტარდებოდა იქ ლაპარაკი იყო ანდროიდის აპლიკაციებზე. მეც დამაინტერესა და გადავწყვიტე ერთხელ მაინც მეცადა მობილურის აპლიკაციის დეველოპმენტი. საცდელად jobs.ge-ის კლიენტის წერა დავიწყე, რადგან ამ საიტს სახალხოდ ჰქონდა rss წყაროები. აპლიკაციის ნაწილი, რაც დავწერე, github-ზეა ხოლო მის შესახებ უფრო დეტალურად ამ დოკუმენტში წერია:

jobs.ge REST client application

ამ პოსტს იმიტომ ვწერ, რომ გადავწყვიტე მობილურის აპლიკაციები ჩემი საქმე არ არის 🙂 და დროც არ მაქვს მის გასაგრძელებლად. მახსოვს რამდენიმემ ინტერესი გამოხატა და თუ ვინმეს სურვილი აქვს შემიძლია გადავულოცო აპლიკაცია კოდებიანად და მან დაასრულოს. არ მოგერიდოთ და კომენტარებში დატოვეთ თქვენი აზრი 🙂 დიდი არაფერია დაწერილი – ორ კვირაში რაც გავარჩიე და დავწერე. რაც არის, დოკუმენტშია აღწერილი.

ალგორითმების გაღრმავებული კურსი თსუ-ში

ჯეოლიმპის ერთ-ერთი შემქმნელი, ელდარ ბოგდანოვი, ამ სასწავლო სემესტრიდან თსუ-ს ზუსტ და საბუნებისმეტყველო მეცნიერებათა ფაკულტეტზე ”ალგორითმების გაღრმავებულ კურსს” წაიკითხავს : )

ჩემი აზრით, კურსში ბევრი საინტერესო მასალაა ალგორითმების შესახებ. თემებს შორის არის – გრაფთა თეორია, მათი სახეები / რეალიზაცია, სიგანეში და სიღრმეში ძებნა, უმოკლესი გზების, ციკლების და ბმული კომპონენტების პოვნა; გრაფებთან და ხეებთან დაკავშირებული კლასიკური ამოცანები; რიცხვთა თეორიის საფუძვლები; ალგორითმები სტრიქონებზე; ვარიანტების სრული გადარჩევა რეკურსიის და ბიტმასკების მეშვეობით;
დინამიური პროგრამირება.

სრული სილაბუსი ამ ბმულზე შეგიძლიათ ნახოთ.

კურსი მჭიდროდ იქნება დაკავშირებული სხვადასხვა პროგრამირების შეჯიბრებებთან (GeOlymp, Codeforces, TopCoder…) და კოდები ავტომატურ რეჟიმში მატესტირებელი სისტემით გაიტესტება.

ასე რომ, ვინც თსუ-ს კომპიუტერულ მეცნიერებებზე სწავლობს, კოდის წერა უყვარს, პროგრამირებას სერიოზულად უყურებს და მეცადინეობის ხასიათზეა, გირჩევთ : )

GitHub-თან კავშირი პროქსის გავლით

არ მგონია რომ ეს პოსტი ბევრისთვის სასარგებლო გამოდგეს, მაგრამ მთელი დღე დამაკარგინა და ვერ მოვითმენ რომ არ დავწერო..

GitHub.com არის პროექტების ჰოსტინგის სერვისი ვებ ინტერფეისით, რომელიც Git version control სისტემას იყენებს. მოკლედ რომ ვთქვათ, თქვენ შეგიძლიათ იქ განათავსოთ პროექტის კოდები და ფაილები. ის ცვლილებების ისტორიასაც შეინახავს. შეიძლება სხვადასხვა განშტოებების და ვერსიების გამოყოფა და გუნდური მუშაობის დროს მოსახერხებელია კოდების ასე ცენტრალიზებულად არსებობა.

ძალიან არ ჩავუღრმავდები სისტემის აღწერას – კარგი ინსტრუქციები აქვთ. მაგრამ დღეს ისეთი კომპიუტერიდან დამჭირდა ცვლილებების ატვირთვა, რომელიც proxy-ის უკან იყო და ამისთვის ვერც ისე ბევრი რესურსი ვნახე.
თან არასტანდარტული პორტებიც დაბლოკილია, ssh-ით შესვლის დროს კი 22 პორტია საჭირო.

GitHub-ი რამდენიმე შესაძლებლობას გვაძლევს შესვლისთვის: ssh, https და git read-only
https-ით ssl-ის სერტიფიკატების პრობლემის გამო რატომღაც ვერ დავუკავშირდი, ამიტომ ვუკავშირდები ssh-ით ოღონდ https-ის პორტით – 443.

ჩემთან ასეთი ნაბიჯები შევასრულე:
~/.ssh საქაღალდეში შევქმენი კონფიგურაციის ფაილი config შემდეგი შიგთავსით:

Host github.com
User <მომხმარებელი>
Hostname ssh.github.com
ProxyCommand /c/connect.exe -H <პროქსის მისამართი>:<პროქსის პორტი> %h %p
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa
Port 443

ProxyCommand არის option, რომელიც OpenSSH-ში SOCKS პროტოკოლის შესაცვლელად ჩაემატა და საშუალებას იძლევა რომ სხვა პროგრამები გამოიყენოს ssh-მა პროქსისთან ურთიერთობისთვის. ერთ-ერთი ასეთი პროგრამაა connect.c

მე ვინდოუსიდან ვცდილობდი ატვირთვას ამიტომ გადმოვწერე კომპილირებული ფაილი აქედან: http://dl.dropbox.com/u/2177278/connect.exe (თავის საიტზე არ მუშაობდა ლინკი).

ასეთმა კონფიგურაციამ არ იმუშავა, მაგრამ connect.exe-ს აქვს ერთი კარგი პარამეტრი -d დებაგისთვის (მაგ: ProxyCommand /c/connect.exe -d -H <პროქსის მისამართი>:<პროქსის პორტი> %h %p) და მაგის მითითების შემდეგ უფრო დეტალურად წერს სად რა პრობლემა შეხვდა. ძალიან დიდი ალბათობით მანდვე იპოვით მიზეზს.

ჩემ შემთხვევაში პრობლემა პროქსისთან აუტენტიფიკაცია იყო. პროქსის პაროლის მითითებისთვის რამდენიმე მეთოდი არსებობს, მაგრამ environment variable-ებში ღია სახით გაწერა არ მომეწონა და არც იმუშავა ჩემთან. არც git-ის global http.proxy პარამეტრმა იმუშავა (აქაც ღია სახით იყო გაწერილი).

ბოლო ბოლო გადმოვწერე Cntlm Authentication Proxy, რომელიც მანქანაზე გასაშვები სერვისია, პროქსის და აპლიკაციებს შორის დგება და ავტორიზაციას გადის მათ მაგივრად. მისი გამოყენების მარტივი მაგალითი არსად ედოთ და მაინც წამაკითხეს მთელი დოკუმენტაცია 😀 ამიტომ მოკლედ დავწერ როგორ მუშაობს:

Cntlm სერვისია და ინსტალაციის შემდეგ ავტომატურად არის გაშვებული. შეგიძლიათ Control panel-ის Administrative Tools-ში შეხვიდეთ და ამოაგდოთ.
პროგრამა უსმენს კონფიგურაციაში გაწერილ მისამართს და პორტს, მაგალითად: 127.0.0.1:3128
ამიტომ ყველა იმ პროგრამის კონფიგურაციაში, რომელიც გინდათ რომ მისი გავლით პროქსის დააკავშიროთ, პროქსის მისამართის მაგივრად უნდა მიუთითოთ ეს მისამართი და პორტი.

პროქსის პაროლის შეყვანა ერთხელ არის საჭირო – თავიდან. შემდეგ დოკუმენტაციის (Configuration hints განყოფილების) მიხედვით უნდა დააგენერირებინოთ თავისი პაროლი, გადაიტანოთ კონფიგურაციის ფაილში და შემდეგ სულ იმით გაივლის ხოლმე ავტორიზაციას.

მე Git Bash-დან ვუშვებდი ბრძანებებს და კავშირს ამით ვამოწმებდი ხოლმე:
$ ssh -v git@github.com

ამ პრობლემის გადასაჭრელად ძირითადად ეს პოსტები დამეხმარა:

Using Github Through Draconian Proxies
Git SSH problem – bad file number 

ცუდი ის იყო, რომ github-დან კლონი უპრობლემოდ გაკეთდა და ფაილები წამოიღო სერვერიდან, მაგრამ push-ის დროს არაფრით ამთავრებდა ოპერაციას (არც timeout შეცდომით) და მაგის გამო კავშირის პრობლემაზე არ მიფიქრია თავიდანვე. მერე გამახსენდა, რომ კლონი IDE-დან გავაკეთე.. და push არ ჰქონდა იქ..