Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Monday, March 7, 2016

Ինչի՞ց սկսել Java լեզվի ուսումնասիրությունը

Իմ ընկերները, ուսանողները և ծանոթները, այն մարդիկ, ովքեր որոշել են սովորել ծրագրավորման Java լեզուն, հաճախ են հարցնում, թե ինչ գրքերից սկսել։ Ստորև ես հավաքել եմ այն մի քանի գրքերը, որոնք ինքս օգտագործել եմ (և օգտագործում եմ) Ջավա լեզվի հետ աշխատելիս։

1. Մի քանի տարի առաջ եմ հայտնաբերել Robert Sedgewick-ի, Kevin Wayne֊ի «Introduction to programming in Java», գիրքը, որը նախատեսված է Պրինստոնի համալսարանում ծրագրավորման սկզբնական դասընթացի համար։ Գեղեցիկ ու բազմազան օրինակներով, սխեմաներով ու պատկերներով հարուստ գիրք է։ (Վերջերս այս գիրքը վերահրատարակվել է՝ Java լեզուն Python լեզվով փոխարինված։)
2. Java 8. Руководство для начинающих, Герберт Шилдт ― հաջող գիրք է, հայտնի հեղինակը բավականին մանրամասնորեն շարադրում է Ջավա լեզվի վերջին տարբերակի հնարավորությունները։ Շատ հարմար է սկսելու համար։
3. Java 8. Полное руководство, Герберт Шилдт ― նույն հեղինակի մի ուրիշ գիրք, որն արդեն Ջավա լեզվի սպառիչ տեղեկատու է։ Այս գրքում կարելի է գտնել համարյա ամեն ինչ։
4. Java 7. Наиболее полное руководство, Хабибуллин Ильдар ― էլի լավ գիրք է, բայց սա ես խորհուրդ կտայի պարզապես ձեռքի տակ ունենալ, և եթե ին֊որ բան չես գտնում Շիլդտի գրքերում, նայել այստեղ։
5. Язык программирования Java SE 8. Подробное описание, Джеймс Гослинг, Гай Л. Стил և ուրիշներ ― հզոր գիրք է։ Հեղինակները հենց Ջավա լեզվի հեղինակներն են, ովքեր Ջավայի մասին գիտեն ամեն ինչ։
6. Java. Эффективное программирование, Блох Джошуа ― նորից հետաքրքիր գիրք է, որտեղ քննարկվում են Ջավա լեզվով ծրագրավորման առանձին հարցեր, առաջարկվում են հաճախ հանդիպող խնդիրների արդյունավետ լուծումներ։
7. Алгоритмы на Java, Седжвик Роберт, Уэйн Кевин ― իմ ամենասիրած գրքերից է։ Սա արդեն ոչ թե Ջավա լեզվի մասին է, այլ Ալգորիթմների իրականացման մասին է Ջավա լեզվի օգտագործմամբ։
8. Algorithms (4th Edition), Robert Sedgewick, Kevin Wayne ― նախորդ գրքի անգլերեն (օրիգինալ) տարբերակն է։
Բոլոր գրքերը կարելի է գտնել այստեղ։

Saturday, June 6, 2015

March թեսթի իրականացումը Java լեզվով

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


Հիշող սարքերի ներդրված թեսթավորման տարածված եղանակներից մեկը March թեսթերի կիրառումն է։ Այս գրառման մեջ ես ուզում եմ պատմել հիշող սարքի մոդելի, անսարքության մոդելի, ինչպես նաև March թեսթի մոդելի իրականացման մասին։

Ընդհանուր առմամբ գաղափարը հետևյալն է․ a) սահմանել տրված չափերով հիշող սարք, b) նրանում ներմուծել որոշ տիպի անսարքություններ, c) կազմել March թեսթեր, d) գործարկել թեսթերը հիշող սարքի մոդելի վրա և գրանցել արդյունքները։

Նշված տիպի մոդելավորումն օգտագործվում է March թեսթերի մշակման համար։ Մոդելավորման օգնությամբ ստեղծվում են թեսթեր, որոնք պետք է հիշող սարքի վրա հայտնաբերեն թեսթավորողին հայտնի անսարքությունները։

Հիշող սարքը

Սովորական և անսարք բջջի մոդելը

Հիշող սարքը մեկ բիթ ինֆորմացիա պահող հիշող տարրերի՝ բջիջների մատրից է։ Այդ բջջի մոդելը ես իրականացրել եմ Cell դասով։ Այս Cell-ը սարքին բջջի մոդելն է։
package memory;

/**
 * Հիշող բջջի մոդելը
 */
class Cell {
    // տվյալ բջիջը պարունակող մատրիցը
    private Memory parent = null;
    // տողը և սյունը
    private int row = 0, column = 0;
    // բջջի արժեքը
    private char value = 'x';

    // կոնստրուկտորը
    public Cell(Memory p, int r, int c)
    {
        parent = p;
        row = r;
        column = c;
    }

    // վերադարձնում է արժեքը
    public char read()
    {
        return value;
    }

    // փոխում է արժեքը
    public void write(char v)
    {
        value = v;
    }
}
Այս դասի parent անդամը այն հիշող սարքի մոդելի հղում է, որը պարունակում է տվյալ բջիջը։ row և column ինդեքսները ցույց են տալիս, թե հիշող սարքի մատրիցի որ տողում և սյունում է տեղադրված բջիջը։ Այս երեք հատկությունները հնարավորություն են տալիս մոդելավորել այնպիսի անսարքություններ, որտեղ մի բջջի ոչ սարքին լինելը ազդում է մեկ այլ սարքին բջջի պարունակության վրա։

Հիշող բջջի հետ կապված անսարքությունները մոդելավորելու համար պետք է ընդլայնել Cell դասը։ Օրինակ, հաճախ են հանդիպում այնպիսի բջջիջներ, որոնց կարդալու գործողությունը վերադարձնում է հաստատուն 0 կամ 1 արժեքը։ Հաստատուն զրո վերադարձնող բջիջը կարելի է մոդելավորել, օրինակ, ConstZero դասով։
package memory;

/**
 * Մշտական '0' վերադարձնող անսարք բջիջ
 */
public class ConstZero extends Cell {
    public ConstZero(Memory p, int r, int c)
    {
        super(p, r, c);
    }

    @Override
    public char read()
    {
        return '0';
    }
}

Բջիջների մատրիցը

Հիշող սարքը, որը հիշող բջիջների մատրից է մոդելավորված է Memory դասով։ Այն պարունակում է տողերի ու սյուների քանակները ցույց տվող rows և columns անդամները, և Cell օբյեկտների matrix մատրիցը։
package memory;

/**
 * Հիշողղ սարքի մոդելը
 */
public class Memory {
    // տողերի քանակը
    public int rows = 0;
    // սյուների քանակը
    public int columns = 0;
    // բջիջների մատրիցը
    private Cell[][] matrix = null;

    // կոնստրուկտորը
    public Memory(int r, int c)
    {
        rows = r;
        columns = c;
        matrix = new Cell[rows][columns];

        for( int i = 0; i < rows; ++i )
            for( int j = 0; j < columns; ++j )
                matrix[i][j] = new Cell(this, i, j);
    }

    // անսարքություն մոդելավորելու համար
    // տրված դիրքում տեղադրել տրված բջիջը
    public void replaceCell(int r, int c, cell cl)
    {
        matrix[r][c] = cl;
    }

    // կարդալ, տրված է գծային հասցեն
    public char read(int addr)
    {
        return read(addr / rows, addr % rows);
    }
    // կարդալ, տրված է ֆիզիկական հասցեն
    private char read(int r, int c)
    {
        return matrix[r][c].read();
    }

    // գրել, տրված է գծային հասցեն
    public void write(int addr, char d)
    {
        write(addr / rows, addr % rows, d);
    }
    // գրել, տրված է ֆիզիկական հասցեն
    private void write(int r, int c, char d)
    {
        matrix[r][c].write(d);
    }

    // արտածել մատրիցի պարունակությունը
    public void Print()
    {
        for( int r = 0; r < rows; ++r ) {
            System.out.printf("%x: ");
            for( int c = 0; c < columns; ++c )
                System.out.print(read(r,c));
            System.out.println();
        }
        System.out.println();
    }
}
Կոնստրուկտորում matrix մատրիցը արժեքավորվում է «սարքին» բջիջներով։ Իսկ replaceCell մեթոդը հնարավորություն է տալիս մատրիցի տրված դիրքի բջիջը փոխարինել նոր տրվածով։ replaceCell մեթոդին տրվում են բջջի ֆիզիկական հասցեները՝ տողը և սյունը, քանի որ հաճախ հիշող սարքերում կիրառվում է ավելի բարդ հասցեավորում քան գծայինն է, և հարմար է անսարք բջիջը տեղադրել ըստ ֆիզիկական դիրքի։

Հիմա, օրինակ, կարող եմ ստեղծել 8 տողեր և 4 սյուներ ունեցող հիշող սարք և դրա երկրորդ տողի առաջին բջջում տեղադրել մի անսարք բջիջ։
Memory mem = new Memory(8, 4);
mem.replaceCell(1, 0, new ConstZero(mem, 1, 0));
Կամ, քանի որ Java լեզուն հնարավորություն է տալիս օբյեկտի ստեղծման ժամանակ վերասահմանել նրա մեթոդները, կարող եմ ConsOne տիպի անսարքություն մոդելավորել այսպես․
mem.replaceCell(1, 1, new Cell(mem, 1, 1) {
    @Override
    public byte read() { return '1'; }
});

March թեսթը

March թեսթը March ալգորիթմների շարք է, որոնք հաջորդաբար գործարկվում են հիշող սարքի մոդելի վրա և ստեղծում են թեսթավորման արդյունքներ։ Ալգորիթմն իր հերթին March էլեմենտների շարք է, իսկ այս վերջինն էլ March գործողությունների շարք է։

Ես դիտարկում եմ միայն պարզագույն March թեսթերը, որոնցում հանդիպում են միայն գրելու և կարդալու գործողություններ։ Եվ որպեսզի March թեսթը նկարագրելիս ավելի անսխալ լինեմ, բերեմ դրա EBNF քերականությունը։
MarchTest = { Algorithm }.
Algorithm = IDENT '{' Element { ';' Element } '}'.
  Element = ('=>' | '<=') '(' Operation { ',' Operation } ')'.
Operation = ('W' | 'R') ('0' | '1').

March գործողություն

Տվյալ կոնտեքստում կան երկու March գործողություններ․ բջջում տրված արժեքը գրողW (write) գործողությունը, և բջջից տրված սպասվող արժեքը կարդացող R (read) գործողությունը։

Ես ուզում եմ նշված երկու գործողությունները մոդելավարել Operation դասով։ Վերջինիս code անդամը գործողության տեսակն է՝ R կամ W, իսկ data անդամը՝ այն արժեքը, որը պետք է գրել բջջում, կամ պետք է սպասել բջջից կարդալիս։
package march;

import memory.*;

public class Operation {
    // գործողությունը
    private char code = 'X';
    // գրելու կամ կարդալու տվյալը
    private char data = '?';

    public Operation(char c, byte d)
    {
        code = c;
        data = d;
    }
March գործողությունը Memory օբյեկտի վրա գործարկելու համար սահմանեմ runOn մեթոդը, որը ստանում է թեսթավորվող հիշող սարքի հղումը և այն գծային հասցեն, որում գտնվող բջջի նկատմամբ կիրառվում է գործողությունը։
    public boolean runOn(Memory mem, int addr)
    {
        boolean status = true;

        if( opcode == 'W' )
            mem.write(addr, value);
        else if( opcode == 'R' )
            status = value == mem.read(addr);

        if( !status )
            System.out.printf("'%s' գործողությունը ձախողվեց %d հասցեի վրա։",
                              toString(), addr);

        return status;
    }
Եվ վերջապես, ToString մեթոդը, որը վերադարձնում է գործողության տեքստային տեսքը։
    @Override
    public String toString()
    {
        return Character.toString(opcode) + Character.toString(value);
    }
}

March էլեմենտ

March էլեմենտն ունի երկու բաղադրիչ․ a) հասցեների թվարկման ուղղությունը՝ որոշվող => և <= սիմվոլներով, և b) March գործողությունների շղթան։
package march;

import memory.*;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * March էլէմենտը
 */
public class Element {
    // հասցեները նվազում են
    public static final int DEC = -1;
    // հասցեներն աճում են
    public static final int INC = 1;

    // հասցեի թվարկման ուղղությունը
    private int direction = 0;
    // գործողությունների շարքը
    private List<Operation> operations = null;

    public Element(int dir, List<Operation> opers)
    {
        direction = dir;
        operations = new ArrayList<Operation>();
        operations.addAll(opers);
    }
March էլեմենտի գործարկումը հիշող սարքի մոդելի վրա կատարվում է runOn մեթոդով։ Քանի որ հասցեների թվարկման ուղղությունը կարող է լինել ինչպես աճող այնպես էլ նվազող, ապա begin և end փոփոխականներում հաշվում եմ առաջին ու վերջին հասցեն։ Հետո while ցիկլի մարմինը կատարվում է այնքան ժամանակ, քանի դեռ begin փոփոխվող հասցեն չի հասել end հասցեին։ Ցիլկի մարմնում մի ուրիշ for ցիկլ է, որը հիշող սարքի մոդելի վրա հերթականորեն աշխատեցնում է March գործողությունները և status բուլյան փոփոխականի մեջ է կուտակում հաջողությունների ու ձախողումների արդյունքը։
    public boolean runOn(Memory mem)
    {
        // ամենամեծ հասցեն
        final int maxaddr = mem.rows * mem.columns - 1;

        // առաջին հասցեն
        int begin = direction == INC ? 0 : maxaddr;
        // վերջին հասցեն
        int end = direction == INC ? maxaddr : 0;

        boolean status = true;
        while( begin != end + direction ) {
            for( Operation op : operations ) {
                boolean opok = op.runOn(mem, begin);
                status = status && opok;
            }
            begin += direction;
        }

        return status;
    }
Էլեմենտի տեքստային ներկայացումը ստանալու համար գրել եմ toString մեթոդը։
@Override
    public String toString()
    {
        String ops = operations.stream()
                               .map(Operation::toString)
                               .collect(Collectors.joining(","));
        String dr = (direction == INC ? "=>" : "<=");
        return String.format("%s(%s)", dr, ops);
    }
}

March ալգորիթմ

March ալգորիթմը March էլեմենտների հավաքածու է՝ խմբավորված ինչ-որ անունի տակ։ Algorithm դասով մոդելավորել եմ March ալգորիթմը։
package march;

import memory.*;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * March ալգորիթմի մոդելը
 */
public class Algorithm {
    // ալգորիթմի անունը
    private String name = "";
    // էլեմենտների ցուցակը
    private List<Element< elements = null;

    /**/
    public Algorithm(String nm, List<Element> elems)
    {
        name = nm;
        elements = new ArrayList<Element>();
        elements.addAll(elems);
    }
Ալգորիթմը հիշող սարքի մոդելի վրա գործարկվում է նորից runOn անունն ունեցող մեթոդի միջոցով։ for ցիկլը անցնում է բոլոր էլեմենտներով և դրանք գործարկում է՝ արգումենտում տալով հիշող սարքի հղումը։ Կատարման կումուլյատիվ արդյունքը ձևավորվում է status փոփոխականում։
    public boolean runOn(Memory mem)
    {
        boolean status = true;

        for( Element el : elements ) {
            boolean elok = el.runOn(mem);
            status = status && elok;
        }

        if( !status )
            System.out.printf("'%s' ալգորիթմի կատարումը ձախողվեց։\n",
                     toString());

        return status;
    }
Վերջապես toString մեթոդը վերադարձնում է ալգորիթմի տեքստային ներկայացումը։
 @Override
    public String toString()
    {
        String estr = elements.stream()
                              .map(Element::toString)
                              .collect(Collectors.joining(";"));
        return String.format("%s { %s }", name, estr);
    }
}

Ալգորիթմների կառուցումը

Ես արդեն պատմեցի Operation, Element և Algorithm դասերի մասին։ Դրանցով կարելի է կազմել թեսթավորման ալգորիթմներ և դրանք գործարկել անսարքությունների մոդելներ պարունակող հիշող սարքի մոդելի վրա։ Բայց միայն նշված դասերի կոնստրուկտորներն օգտագործելով անհարմար է կառուցել մեծ ալգորիթմներ։ Օրինակ, միայն A0 { =>(W0); =>(W1,R1) } ալգորիթմը կառուցելու համար պետք է գրել հետևյալ Java կոդը։
List<Operation> ops0 = new ArrayList<>();
ops0.add( new Operation('W', '0') );

List ops1 = new ArrayList<>();
ops1.add( new Operation('W', '1') );
ops1.add( new Operation('R', '1') );

Element el0 = new Element(Element.INC, ops0);
Element el1 = new Element(Element.INC, ops1);

List els0 = new ArrayList<>();
els0.add(el0);
els0.add(el1);

Algorijthm al0 = new Algorithm("A0", els0);
Շատ ավելի հարմար կլիներ, որ ալգորիթմը կառուցվեր մարդուն հարմար տեքստային ներկայացումից։ Դրա համար march փաթեթում ավելացրել եմ Parser դասը, որի parse մեթոդը ստանում է March ալգորիթմի տեքստային ներկայացումը և վերադարձնում է կառուցված Algorithm օբյեկտի հղումը։
package march;

import java.util.ArrayList;
import java.util.List;

public class Parser {
    private char[] source = null;
    private int position = 0;

    public Algorithm parse(String src) throws Exception
    {
        source = src.replaceAll("\\s+", "").toCharArray();
        position = 0;

        return parseAlgorithm();
    }
Parser դասը մի պարզագույն քերականական վերլուծիչ է, որը «ճանաչում» է March ալգորիթմի վերը բերված քերականությունը․
Algorithm = IDENT '{' Element { ';' Element } '}'.
  Element = ('=>' | '<=') '(' Operation { ',' Operation } ')'.
Operation = ('W' | 'R') ('0' | '1').
Քերականական երեք կանոններից յուրաքանչյուրի համար ստեղծված են համապատասխան մեթոդները՝ parseAlgorithm, parseElement և parseOperation։ Սրանցից ամեն մեկը վերլուծում է քերականությամբ իր համար որոշված հատվածը և վերադարձնում է Algorithm, Element կամ Operation օբյկտի հղում։
    private Algorithm parseAlgorithm() throws Exception
    {
        char[] name = {0, 0};
        if( !Character.isUpperCase(source[position]) )
            throw new Exception("Սպասվում է լատիներեն մեծատառ։");
        name[0] = source[position++];
        if( !Character.isDigit(source[position]) )
            throw new Exception("Սպասվում է թվանշան։");
        name[1] = source[position++];

        if( source[position++] != '{' )
            throw new Exception("Սպասվում է '{'։");

        List<Element> elements = new ArrayList<>();
        elements.add(parseElement());
        while( source[position] == ';' ) {
            ++position;
            elements.add(parseElement());
        }

        if( source[position++] != '}' )
            throw new Exception("Սպասվում է '}'։");

        return new Algorithm(new String(name), elements);
    }

    private Element parseElement() throws Exception
    {
        int direction = 0;
        if( source[position] == '=' && source[position+1] == '>' )
            direction = 1;
        else if( source[position] == '<' && source[position+1] == '=' )
            direction = -1;
        else
            throw new Exception("Սպասվում է '=>' կամ '<=։");

        position += 2;

        if( source[position++] != '(' )
            throw new Exception("Սպասվում է '('։");

        List<Operation> operations = new ArrayList<>();
        operations.add(parseOperation());
        while( source[position] == ',' ) {
            ++position;
            operations.add(parseOperation());
        }

        if( source[position++] != ')' )
            throw new Exception("Սպասվում է ')'։");

        return new Element(direction, operations);
    }

    private Operation parseOperation() throws Exception
    {
        char code = source[position++];
        if( code != 'W' && code != 'R' )
            throw new Exception("Անծանոթ գործողություն");
        char data = source[position++];
        if( data != '0' && data != '1' )
            throw new Exception("Գործողության սխալ արժեք");

        return new Operation(code, data);
    }
Իհարկե, հարմար կլիներ Parser դասին ավելացնել ևս մի parse մեթոդ, որը ստանա ալգորիթմների ցուցակ և վերադարձնի Algorithm օբյեկտների հղումների ցուցակ։

Monday, March 31, 2014

Java և Kawa: Տեքստի տրոհումը Scanner դասի օգնությամբ

Շարունակելով բլոգիս «Tcl: Բառարանների օգտագործումը», «Python: Բառարանի օգտագործումը» և «C++11: Տեքստի տրոհումը բառերի՝ istream-ի միջոցով» գրառումների թեման, ես ուզում էի այս գրառմանս մեջ պատմել Java լեզվի միջոցներով տեքստը բառերի տրոհելու և բառերի հաճախությունը հաշվելու մասին։ Ինձ գայթակղեց Java-ի Scvanner դասը, որը կարելի է կանոնավոր արտահայտությունների միջոցով կարգավորել տեքստից բառեր կարդալու համար։ Բայց այս գրառման մեջ ուզում եմ նաև Java-ի HashMap բառարանի օգտագործումը համեմատ ել JVM վիրտուալ մեքենայով աշխատող GNU Kawa լեզվի (Scheme լեզվի իրականացում) hashtable բառարանի օգտագործման հետ։

Նախ ներկայացնեմ Java տարբերակը (օգտագործված է JDK 8-ը)։
package wordcount;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Scanner;

/**/
public class WordCount {
  /**/
  private static void readFile( String name, HashMap<String,Integer> words )
  {
    // տրված անունով ֆայլի համար ստեղծել Scanner օբյեկտ
    try( Scanner scan = new Scanner(new File(name)) ) {
      // բացի մեծատառերից ու փոքրատառերից ամեն ինչ համարել բաժանիչ
      scan.useDelimiter( "[^A-Za-z]+" );
      // քանի դեռ Scanner-ից կարելի է կարդալ
      while( scan.hasNext() ) {
        // կարդալ հերթական բառը
        String wo = scan.next().toLowerCase();
        // գտնել կարդացած բառի արտապատկերումը բառարանում
        Integer co = words.get(wo);
        // եթե այդ բառը բառարանում չկա, ավելացնել այն՝ 1 քանակով, 
        // իսկ եթե կա՝ քանակն ավելացնել 1-ով
        words.put(wo, co == null ? 1 : co + 1);
      }
    }
    catch( FileNotFoundException ex ) {
      System.err.println(ex.getMessage());
    }
  }
    
  /**/
  public static void main(String[] args) 
  {
    // ստեղծել String->Integer արտապատկերում
    HashMap<String,Integer> words = new HashMap<>();
    // կարդալ ֆայլի պարունակությունը words բառարանի մեջ
    readFile("~/Projects/martineden.txt", words);
    // բառ-քանակ զույգերն արտածել ստանդարտ արտածման հոսքին
    words.forEach( (k,v) -> System.out.printf("%s\t%s\n", k, v) );
  }
}
* * *
Kawa լեզուն JVM վիրտուալ մեքենայի համար գրված բազմաթիվ լեզուներից մեկն է։ Բայց ինձ համար հետաքրքիր է նրանով, որ այն Lisp լեզվի Scheme դիալեկտի իրականացում է Java լեզվով։ Ամբողջովին գրված լինելով Java լեզվով՝ այն ա) պլատֆորմից անկախ է, և բ) հնարավորություն ունի օգտագործել Java լեզվի ստանդարտ գրադարանը՝ այն ամենը, ինչ մատչելի է JVM կատարման միջավայրում։

Հիմա ցույց տամ, թե ինչպես եմ Kawa լեզվի միջոցներով կարդում ֆայլի բառերի հաջորդականությունը և կազմում դրանց հաճախությունների բառարանը։ Նախ սահմանեմ read-all-words ֆունկցիան, որն արգումենտում ստանում է Java լեզվի Scanner օբյեկտը և վերագարձնում է տեքստի բառերի հաճախությունների բառարանը՝ որպես Scheme լեզվի hashtable օբյեկտ։
(define (read-all-words sca :: Scanner)
  (define (read-all-words-rec ht)
    (when (invoke sca 'hasNext)
      (add-to-table (string-downcase (invoke sca 'next)) ht)
      (read-all-words-rec ht)))
  (let ((words (make-hashtable string-hash string=?)))
    (read-all-words-rec words)
    words))
read-all-words ֆունկցիայի համար սահմանված է read-all-words-rec լոկալ ռեկուրսիվ ֆունկցիան, որը Scanner օբյեկտից կարդում է մեկ բառ, այդ բառի բոլոր մեծատառերը դարձնում է փոքրատառ՝ string-downcase ֆունկցիայով, ապա ավելացնում է արգումենտում տրված բառարանում։ Բառարանը ստեղծվում է որպես read-all-words ֆունկցիայի լոկալ օբյեկտ՝ make-hashtable ֆունկցիայով։ Բառը բառարանում ավելացնող add-to-table ֆունկցիան սահմանված է հետևյալ կերպ․
(define (add-to-table w table)
  (let ((co (hashtable-ref table w 0)))
    (hashtable-set! table w (+ co 1))))
read-file ֆունկցիան, որ կսահմանեմ ստորև, տրված ֆայլի անունի համար ստեղծում է մի Scanner օբյեկտ և "[^A-Za-z]+" արտահայտությունը սահմանում է որպես դրա բաժանիչ։ Հետո read-all-words ֆունկցիայով ստանում է բառերի հաճախությունների բառարանը, այդ բառարանից կազմում է կետով զույգերի (dotted pair) ցուցակ, և վերադարձնում է այդ վերջին ցուցակն՝ ըստ բառերի այբբենական կարգի կարգավորած։
(define (read-file name :: )
  (let ((sca (Scanner:new (File:new name))))
    (invoke sca 'useDelimiter "[^A-Za-z]+")
    (let-values (((ks vs) (hashtable-entries (read-all-words sca))))
      (invoke sca 'close)
      (list-sort (lambda (a b) (stringlist ks) (vector->list vs))))))
* * *
Ինձ դուր է գալիս Lisp լեզվով ծրագրավորումը։ Բայց ինձ դուր է գալիս նաև Java լեզվի գրադարանները։ Kawa իրականացումը հնարավորություն է տալիս մեկտեղել երկու դուրեկան բան :)

Friday, March 21, 2014

Java: Անանուն ֆունկցիաները Java 8 լեզվում

Պատահաբար հանդիպեցի Java 8 լեզվում անանուն ֆունկցիաների իրականացման մասին Oracle ֆիրմայի մի հաղորդագրության։ Այդտեղ օրինակներ էր բերված, թե ինչ նպատակների համար են նախատեսված Java 8 լեզվի լյամբդա ֆունկցիաները, և թե ինչպես կարելի է օգտագործել դրանք։
     Իմ բլոգի «Բարձր կարգի ֆունկցիաներ և անանուն ֆունկցիաներ» գրառման մեջ ես ֆունկցիայի ինտեգրալի թվային հաշվման օրինակով համեմատել էի Common Lisp և C++11 լեզվի անանուն ֆունկցիաները։ Այս գրառման մեջ ուզում եմ նույն այդ օրինակը ցույց տալ Java 8 լեզվի անանուն ֆունկցիաների օգտագործմամբ։

Մաթեմատիկական ֆունկցիան, որ կարելի է ինտեգրել, իրականացնում է MathFunc ինտերֆեյսը։ Այն սահմանված է MathFunc.java ֆայլում։
package integral;

public interface MathFunc {
 public double apply( double x );
}
Թվային ինտեգրման մեթոդն իրականացնում է Method ինտերֆեյսը, որը սահմանված է Method.java ֆայլում՝ հետևյալ կերպ։
package integral;

public interface Method {
 public double apply( MathFunc f, double a, double b );
}
Սահմանեմ Integral դասը և սահմանեմ այդ դասի evaluate մեթոդը, որն արգումենտում սպասում է ինտեգրման մեթոդը, ինտեգրվող ֆունկցիան, ինտեգրման միջակայքը և միջակայքի՝ հատվածների տրոհման գործակիցը։ Հաշվելու եղանակը պարզագույնն է. meth օբյեկտի apply մեթոդին են փոխանցվում ինտեգրվող ֆունկցիան և այն փոքր հատվածը, որի վրա նշված թվային մեթոդով հաշվվում է ինտեգրալը։
package integral;

public class Integral {
    public static double evaluate(Method meth, MathFunc func, 
                                double left, double right, double epsilon)
    {
        double result = 0.0;
        for( double x = left; x < right; x += epsilon )
            result += meth.apply(func, x, x + epsilon);
        return result;
    }
}
Որպեսզի, օրինակ, Integral դասի evaluate ստատիկ մեթոդի օգնությամբ հաշվեմ \(f(x)=3x^2-2x+1\) ֆունկցիայի ինտեգրալը, պետք է նախ՝ \(f(x)\) ֆունկցիան սահմանեմ որպես MathFunc ինտերֆեյս իրականացնող օբյեկտ, իսկ ինտեգրման մեթոդը, օրինակ սեղանների կանոնը, սահմանեմ որպես Method ինտերֆեյսն իրականացնող օբյեկտ։
     Սահմանեմ Calc դասը՝ main մուտքի կետով։
package integral;

public class Calc {
    /**/ 
    public static void main( String[] args )
    {
        // ինտեգրման մեթոդ
        Method Simple = new Method() {
            @Override
            public double apply( MathFunc f, double a, double b )
            { return (b - a) * f.apply((b + a) / 2); }
        };

        // ինտեգրվող ֆունկցիա 
        MathFunc Sin = new MathFunc() {
            @Override
            public double apply( double x )
            { return 3*x*x - 2*x + 1; }
        };

        // ինտեգրալի հաշվումն ու արտածումը
        double r0 = Integral.evaluate(Simple, Sin, 0, 1, .0001);
        System.out.println( r0 );
    }
}
Բայց սա հնացած եղանակն է։ Անանուն ֆունկցիաների օգտագործմամբ գրվում է շատ ավելի համառոտ, գեղեցիկ ու հասկանալի կոդ։ Ստորև բերված main ֆունկցիայում ինտեգրման մեթոդն ու ինտեգրվող ֆունկցիան evaluate ֆունկցիային փոխանցված են որպես անանուն ֆունկցիաներ։
package integral;

public class Calc {
    /**/ 
    public static void main( String[] args )
    {
        double r1 = Integral.evaluate( (f,a,b)->(b-a)*f.apply((b+a)/2), 
                                       (x)-> 3*x*x - 2*x + 1, 0, 1, .0001 );
        System.out.println( r1 );
    }
}
* * *
Java 8 լեզվում անանուն (լյամբդա) ֆունկցիան սահմանվում է հետևյալ քերականությամբ․

(արգումենտների ցուցակ) -> ֆունկցիայի մարմին

Օրինակ, թվային մոտավոր ինտեգրման սեղանների կանոնի բանաձևը վերը բերված օրինակում սահմանված է ահա այսպես․
(f, a, b) -> (b - a) * f.apply((b + a) / 2)
Կարելի է անանուն ֆունկցիան կապել որևէ փոփոխականի հետ, օրինակ, թվային մոտավոր ինտեգրման Սիմպսոնի կանոնի բանաձևը սահմանված է որպես անանուն ֆունկցիա և վերագրված է simpson փոփոխականին․
public static Method simpson = 
   (f, a, b) -> ((b - a) / 6) * (f.apply(a) + f.apply(b) + 4*f.apply((a+b)/2));
Ինտեգրալը հաշվելիս կարելի է evaluate ֆունկցիային փոխանցել simpson փոփոխականը․
double r2 = Integral.evaluate( simpson, (x)-> 3*x*x - 2*x + 1, 0, 1, .0001 );

Առայժմ այսքանը։ Ես դեռ նոր եմ ուսումնասիրում Java 8-ի անանուն ֆունկցիաները։ Հետքգայում միգուցե նորից անդրադառնամ այս թեմային ու ներկայացնեմ ավելի հետաքրքիր օրինակներ։

Friday, September 20, 2013

GNU/bytecode գրադարանի օգտագործման օրինակ

Ծրագրավորման լեզուների կոմպիլյացիայի հերցերն ուսումնասիրելիս ես տևական ժամանակ փնտրում էի մի գրադարան, որը հարավորություն կտար առանց մանրամասնությունների մեջ մտնելու գեներացնել Java վիրտուալ մեքենայի class ֆայլեր։ Առաջին որոնումները բերեցին ASM և BCEL գրադարաններին և Jasmin «Java ասեմբլերին»։ Վերջինս հնարավորություն է տալիս մնեմենիկ հրամաններով հրագրավորել ալգորիթմը, ապա այն «ասեմբլացնել» ու ստանալ վիրտուալ մեքենայի բայթ-կոդ՝ class ֆայլ։

Բայց, տարբեր պատճառներով, այդ գրադարաններից ոչ մեկը ես հարմար չգտա սովորելու համար։ Հետագա որոնումների ընթացքում հանդիպեցի GNU/bytecode գրադարանին, որ Scheme ծրագրավորման լեզվի Kawa իրականացման մաս է կազմում։

Այս գրառման մեջ ես ուզում եմ ֆակտորիալի և ամենամեծ ընդհանուր բաժանարարի հաշվման պարզ օրինակներով ցույց տալ, թե ինչպես կարելի է գեներացնել այդ երկու ալգորիթմները պարունակող class ֆայլը։

Նախ սկսեմ Java տարբերակից.

public class Algorithms {
  public static int factorial( int n )
  {
    if( n == 1 ) return 1;
    return n * factorial( n - 1 );
  }

  public static int gcd( int n, int m )
  {
    while( n != m )
      if( n > m )
        n -= m;
      else
        m -= n;
    return n;
  }
}

Այս դասի նկարագրությունը Algorithms.java ֆայլի մեջ գրառելուց և javac կոմպիլյատորով թարգմանելուց հետո ստացվում է Algorithms.class ֆայլը։ class ֆայլը կարելի է javap դիզասեմբլերով հետ թարգմանել ու տեսնել թե Java լեզվի կոմպիլյատորն ինչ հրամաններ է գեներացրել։ Այդ բոլոր հրամանների նկարագրությունը կարելի է գտնել Java վիրտուալ մեքենայի նկարագրության մեջ։

Compiled from "Algorithms.java"
public class Algorithms {
  public Algorithms();
    Code:
       0: aload_0
       1: invokespecial #1    // Method java/lang/Object."<init>":()V
       4: return 

  public static int factorial(int);
    Code:
       0: iload_0
       1: iconst_1 
       2: if_icmpne     7
       5: iconst_1 
       6: ireturn 
       7: iload_0 
       8: iload_0 
       9: iconst_1 
      10: isub 
      11: invokestatic  #2    // Method factorial:(I)I
      14: imul 
      15: ireturn 

  public static int gcd(int, int);
    Code:
       0: iload_0
       1: iload_1 
       2: if_icmpeq     24
       5: iload_0 
       6: iload_1 
       7: if_icmple     17
      10: iload_0 
      11: iload_1 
      12: isub 
      13: istore_0 
      14: goto          0
      17: iload_1 
      18: iload_0 
      19: isub 
      20: istore_1 
      21: goto          0
      24: iload_0 
      25: ireturn 
}

Այս արտածումը տրված է JVM-ի հրամաններ մնեմոնիկ ներկայացմամբ, որոնց բացատրությունները բերված են JVM Specification էջում։

 

Հիմա ցույց տամ, թե ինչպես եմ gnu.bytecode գրադարանի օգտագործմամբ ստեղծում factorial և gcd ալգորիթմները պարունակող class ֆայլը։ Արդեն նշեցի, որ gnu.bytecode գրադարանը Kawa լեզվի իրականացման մաս է։ Չնայած որ կարելի է կոդից առանձնացնել միայն gnu.bytecode-ն և կառուցել առանձին *.jar ֆայլ, ես կօգտագործեմ հենց kawa-1.13.jar ֆայլը։

ՈՒրեմն, նախ ներմուծում եմ gnu.bytecode գրադարանը․ import gnu.bytecode.*;

Հետո սահմանում եմ AlgoEx դասն իր main մեթոդով․

public class AlgoEx {
    public static void main(String[] args) throws Exception
    {

Հետո ստեղծում եմ ClassType դասի օբյեկտ, որը ներկայացնում է Java վիրտուալ մեքենայի դասը։ Այնուհետև դասի համար որպես ծնող նշում եմ java.lang.Object դասը, իսկ որպես տեսանելիության մոդիֆիկատոր նշում եմ public - Access.PUBLIC հոստատունի օգնությամբ։

        // class `Algorithms'
        ClassType clo = new ClassType("Algorithms");
        clo.setSuper("java.lang.Object");
        clo.setModifiers(Access.PUBLIC);

Քանի որ և՛ factroial, և՛ gcd մեդոդները սահմանելու եմ public ու static մոդիֆիկատորներով, այստեղ սահմանել եմ pubstat հաստատունը․

        final int pubstat = Access.PUBLIC | Access.STATIC;

Հետո clo դասում ավելացնում եմ նոր մեթոդ՝ "factorial" անունով և "(I)I" սիգնատուրայով։ addMethod մեթոդի վերադարձրած հղումև վերագրում եմ Method տիպի mfac փոփոխականին։

        // method `factorial'
        Method mfac = clo.addMethod("factorial", "(I)I", pubstat);

CodeAttr դասը նախատեսված է բուն կոդի գեներացիայի համար։ Ստորև բերված բլոկում կառուցված է ամբողջ թվի ֆակտորիալը հաշվող ֆունկցիայի կոդի գեներացիան․

        CodeAttr code = mfac.startCode();
        code.pushScope();
        code.emitLoad(code.getArg(0));
        code.emitPushInt(1);
        code.emitIfEq();
        code.emitPushInt(1);
        code.emitReturn();
        code.emitElse();
        code.emitLoad(code.getArg(0));
        code.emitDup();
        code.emitPushInt(1);
        code.emitSub(Type.intType);
        code.emitInvoke(mfac);
        code.emitMul();
        code.emitReturn();
        code.emitFi();
        code.popScope();

Նույն կերպ կառուցված է gcd մեթոդի կոդի գեներացիան։

        // method `gcd'
        Method mgcd = clo.addMethod("gcd", "(II)I", pubstat);
        Label bw = new Label(code);
        Label ew = new Label(code);
        code = mgcd.startCode();
        code.pushScope();
        bw.define(code);
        Variable n = code.getArg(0);
        Variable m = code.getArg(1);
        code.emitLoad(n);
        code.emitLoad(m);
        code.emitGotoIfEq(ew);
        code.emitLoad(n);
        code.emitLoad(m);
        code.emitIfGt();
        code.emitLoad(n);
        code.emitLoad(m);
        code.emitSub(Type.intType);
        code.emitStore(n);
        code.emitElse();
        code.emitLoad(m);
        code.emitLoad(n);
        code.emitSub(Type.intType);
        code.emitStore(m);
        code.emitFi();
        code.emitGoto(bw);
        ew.define(code);
        code.emitLoad(n);
        code.emitReturn();
        code.popScope();
 

Եվ վերջում writeToFile մեթոդով կառուցված դասի պարունակությունը գրվում է class ֆայլի մեջ։

        // write class file
        clo.writeToFile();
    }
}

Հիմա թարգմանեմ այս ֆայլը Java կոմպիլյատորով և գործարկեմ այն, որպեսզի ստանամ Algorithms.class ֆայլը։

$ javac -classpath .:kawa-1.13.jar AlgoEx.java 
$ java -classpath .:kawa-1.13.jar AlgoEx

Այս երկու հրամանների կատարումից հետո գեներացվում է Algorithms.class ֆայլը։ Կարելի է javap դիզասեմբլերով տեսնել այս ֆայլի պարունակությունը։ Բայց ես կգրեմ մի տեստային ֆայլ, որը կօգտագործի Algwrithms դասի factorial և gcd ֆունկցիաները։

public class Test {
    public static void main(String[] args)
    {
        System.out.println(Algorithms.factorial(10));
        System.out.println(Algorithms.gcd(123,31));
    }
}

Այս դասի թարգմանությունն ու կատարումը ցույց է տալիս, որ gnu.bytecode գրադարանի օգնությամբ կառուցված class ֆայլը ճիշտ է․

$ javac Test.java
$ java Test
3628800
1

Friday, March 29, 2013

Այլ ծրագրի աշխատեցնելը Java ծրագրից

Ինչ որ մի անգամ ինձ պետք էր անել այնպես որ օգտագործողի գրաֆիկական ինտերֆեյսում (GUI) անընդհատ ռեժիմով արտածվեն մեկ այլ պրոցեսում աշխատեցրած ծրագրի արտածման ստանդարտ հոսքի (stdout) և սխալների ստանդարտ հոսքի (stderr) հաղորդագրությունները։ Այս գրառման մեջ ես պարզեցրած օրինակով ցույց կտամ, թե ինչպես դա արեցի։

1. NetBeans աշխատանքային միջավայրում ստեղծենք ScriptRunner անունով պրոյեկտը և նրա լռելությամբ ստեղծված scriptrunner փաթեթում ավելացնենք JFrame դասից ժառանգված MainWindow անունով պատուհանը։

2. Պատուհանի վրա ավելացնենք արտաքին սկրիպտի ճանապարհը նշելու, այն աշխատեցնելու և արդյունքներն արտածելու հետևյալ երեք օբյեկտները։
  • txtScript : JTextField - սկրիպտի կամ հրամանի ճանապարհը նշելու դաշտ,
  • btnRun : JButton - աշխատանքը սկսելու կոճակ,
  • areaResults : JTextArea - արդյունքներն արտածելու տիրույթ:
Դասավորենք օբյեկտներն այնպես, որ նրանք ունենան ստորև բերված նկարի տեսքը։

3. "Run" կոճակի կոնտեքստային մենյուից ընտրենք Events->Action->actionPerformed գործողությունը, որի հետևանքով պատուհանի կոդի հատվածում կստեղծվի btnRunActionPerformed անունով մեթոդի դատարկ մարմինը։ Այս մեթոդը կանչվելու է, երբ սեղմենք "Run" կոճակը։
private void btnRunActionPerformed(java.awt.event.ActionEvent evt) { 
  //
}


Արտաքին ծրագիրն աշխատեցնում եմ Process դասի օգնությամբ, որը հնարավորություն է տալիս իր getInputStream մեթոդից ստեղծել կարդալու հոսք և կարդալ արտաքին ծրագրի արտածման ու սխալների ստանդարտ հոսքերի արտածումները։ Օրինա, եթե պետք է Java ծրագրից աշխատեցնել ls ծրագիրը, ապա պետք է նախ ProcessBuilder դասի կոնստրուկտորին տալ աշխատեցվելիք ծրագրի անունը, ապա կանչելով նրա start մեթոդը, ստեղծել նոր պրոցես։
Process ps = (new ProcessBuilder("ls")).start();
InputStream is = ps.getInputStream();
Պրոցեսի տված հաղորդագրությունները սինխրոն կարդալու համար ստեղծել եմ մի thread, որը կարդում է պրոցեսի արտածման հոսքի հետ կապված ընթերցման հոսքի պարունակությունը և արտածում է areaResults դաշտում։

Ահա btnRunActionPerformed մեթոդի իրականացումը։
private void btnRunActionPerformed(java.awt.event.ActionEvent evt) {                                       
  try {
    // պրոցեսի ստեղծում
    ProcessBuilder pb = new ProcessBuilder(txtScript.getText());
    // սխալների հոսքն ուղղել stdout-ի վրա
    pb.redirectErrorStream(true); 
    // պրոցեսի գործարկում
    Process procFlow = pb.start();
    
    // վերցնել պրոցեսի արտածման հոսքը
    InputStream inp = procFlow.getInputStream();
    final BufferedReader reader = 
            new BufferedReader(new InputStreamReader(inp));

    // thread-ի ստեղծում
    Thread outputThread = new Thread() {
      @Override
      public void run() 
      {
        try {
          // ապահովել reader-ը,
          synchronized(reader) {
            String text = "";
            // քանի դեռ կարդալու բան կա, կարդալ
            while(null != (text = reader.readLine())) {
              // տեքստը կցել areaResults-ի պարունակության պոչից
              areaResults.append(text + "\n");
              // կուրսորը տեղափոխել տեքստի վերջը
              areaResults.setCaretPosition(areaResults.getText().length());
            }
          }
        }
        catch (Exception e) {}
      }
    };
    
    // կարդացող thread-ի գործարկում
    outputThread.start();
  }
  catch(IOException ex) {
    JOptionPane.showMessageDialog(this, ex.getMessage(), 
               "Error", JOptionPane.ERROR_MESSAGE);
  } 
}

* * *
Ծրագիրը փորձարկելու համար ստեղծել եմ Bash-ի մի սխրիպտ, որը երկու վայրկյան ընդմիջումներով հաղորդագրություններ է արտածում արտածման ստանդարտ հոսքի վրա։
#!/bin/bash

for m in 1 2 3 4 5 6 7 8 9 10 11 12
do
  echo Message number: $m
  sleep 2
done
Ծրագիրն աշխատեցնելուց հետո txtScript դաշտում ներմուում եմ սկրիպտի ճանապարհը ու սեղմում եմ Run կոճակը։ Արդյունքն այն է, ինչ ես սպասում էի։


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 ծրագրի կառուցվածքին։ Տեսանք, թե ինչպես պետք է թարգմանել ու կատարել ծրագիրը հրամանային տողից։ Չնայած, որ ստացանք աշխատող ծրագիր, բայց բազմաթիվ հարցեր, թե՛ աշխատանքի տեխնիկայի, թե՛ խնդրի լուծման հետ կապված, դեռ մնում են չպարզաբանված։ Այս բլոգի հաջորդ գրառման մեջ ես կփորձեմ ընդգծել այս առաջին օրինակի թերություններն ու բացթողումները և առաջարկել դրանց լուծումները։