Your Ad Here
Kata learns to code
Jan 20

1 + 1 = ?
12*(16-(6-40/20)-7) + 5*(23-3*(7-2)) = ?
sqrt[pow[12*(16-(6-40/20)-7) + 5*(23-3*(7-2)),3]] = ?

package calexp;

public class CalculateExpression {

    private static final String MSG = "Invalid Expression";

    private static final String[] FUNCTION_LIST = {
        "abs", "sqrt", "sin", "cos", "tan", "cotan", "log", "exp", "pow"
    };

    private static final int[] PARAMETER_NUMBER = {
            1,      1,     1,     1,     1,       1,     1,     1,     2
    };

    public static double calculate(String exp) throws Exception {
        int sepa = findSeparator(exp);

        if (sepa == -1)
            return calcuSingleExp(exp);

        if (sepa == 0)
            switch (exp.charAt(0)) {
                case '+':
                    return calculate(exp.substring(1));
                case '-':
                    return -calculate(exp.substring(1));
                default:
                    throw new Exception(MSG);
            }

        double part1 = calculate(exp.substring(0, sepa));
        double part2 = calculate(exp.substring(sepa + 1));

        switch (exp.charAt(sepa)) {
            case '+':
                return part1 + part2;
            case '-':
                return part1 - part2;
            case '*':
                return part1 * part2;
            default:
                return part1 / part2;
        }
    }

    private static double calcuSingleExp(String exp) throws Exception {
        exp = exp.trim();
        if (exp.isEmpty()) throw new Exception(MSG);

        int len = exp.length();
        if (exp.charAt(len - 1) == ')') {
            if (len < 3 || findOpen(exp, len - 1) != 0)
                throw new Exception(MSG);
            return calculate(exp.substring(1, len - 1));
        }

        try {
            return Double.valueOf(exp);
        } catch (NumberFormatException e) {
            if (exp.charAt(len - 1) != ']') throw new Exception(MSG);
            int open = findOpen(exp, len - 1);
            String func = exp.substring(0, open).trim();

            exp = exp.substring(open + 1, len - 1);
            String[] sub = splitFay(exp);

            int funcIndex = findFuncIndexOf(func);
            if (funcIndex == -1) throw new Exception(MSG);
            if (sub.length > PARAMETER_NUMBER[funcIndex]) throw new Exception(MSG);

            double res = calculate(sub[0]);

            switch (funcIndex) {
                case 0: return Math.abs(res);
                case 1: return Math.sqrt(res);
                case 2: return Math.sin(res);
                case 3: return Math.cos(res);
                case 4: return Math.tan(res);
                case 5: return 1.0 / Math.tan(res);
                case 6: return Math.log(res);
                case 7: return Math.exp(res);
                default: return Math.pow(res, calculate(sub[1]));
            }
        }
    }

    private static int findFuncIndexOf(String func) {
        for (int i = 0; i < FUNCTION_LIST.length; i++)
            if (FUNCTION_LIST[i].equalsIgnoreCase(func))
                return i;
        return -1;
    }

    private static String[] splitFay(String listExp) throws Exception {
        java.util.ArrayList<String> listSub = new java.util.ArrayList<String>();
        char ch;
        int j = 0;
        for (int i = listExp.length() - 1; i >= 0; i--) {
            ch = listExp.charAt(i);
            if (ch == ')' || ch == ']')
                i = findOpen(listExp, i);
            else if (ch == ',') {
                listSub.add(listExp.substring(j, i));
                j = i + 1;
            }
        }
        listSub.add(listExp.substring(j, listExp.length()));
        String[] s = new String[listSub.size()];
        for (j = 0; j < s.length; j++)
            s[j] = listSub.get(j);
        return s;
    }

    private static int findSeparator(String exp) throws Exception {
        int len = exp.length();
        if (isOperator(exp.charAt(len - 1)))
            throw new Exception(MSG);

        int i, j = -1;
        char ch;
        for (i = len - 1; i >= 0; i--) {
            ch = exp.charAt(i);
            if (ch == ')' || ch == ']') {
                i = findOpen(exp, i);
                if (i == -1)
                    throw new Exception(MSG);
            } else if (ch == '+' || ch == '-') {
                return i;
            } else if (ch == '*' || ch == '/') {
                j = i;
            }
        }
        return j;
    }

    private static boolean isOperator(char ch) {
        return ch == '+' || ch == '-' || ch == '*' || ch == '/';
    }

    private static int findOpen(String exp, int off) throws Exception {
        int count = 0;
        char close = exp.charAt(off);
        char open = close == ')' ? '(' : '[';
        while (off >= 0) {
            if (exp.charAt(off) == close) {
                count++;
            } else if (exp.charAt(off) == open) {
                count--;
                if (count == 0) return off;
            }
            off--;
        }
        throw new Exception(MSG);
    }

    public static void main(String[] args) {
        try {
            String s = "12*(16-(6-40/20)-7)  +  5*(23-3*(7-2))";
            System.out.println(calculate(s));

            s = "sqrt[pow[12*(16-(6-40/20)-7)  +  5*(23-3*(7-2)),3]]";
            System.out.println(calculate(s));

            s = "abs[cotan[sin[(5*(13-7)- (5*6-9)*2)+pow[2,3]*(sqrt[9*pow[9.13,2.2]]-8+12*cos[23])]-log[4]]]";
            System.out.println(calculate(s));

            s = "(1 * (4 + 50)) / 69";
            System.out.println(calculate(s));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Jan 20

Một số loại điện thoại (như Motorola) thường có sẵn chức năng đèn lồng, tức là đèn điện thoại sáng liên tục trên 1 nền trắng, hoặc đèn flash (chớp sáng rồi tắt luân phiên). Điện thoại Nokia ít có hơn, thực ra việc code ứng dụng Java như vậy ko khó.

Sử dụng phương thức tĩnh setLights(num, level) của lớp DeviceControl trong gói com.nokia.mid.ui để điều khiển đèn điện thoại. Trong đó num = 0, level là mức độ sáng (0-100).

Để làm được màn hình trắng trơn, ta cần viết 1 Canvas có màu nền là White, đặt nó ở chế độ toàn màn hình bằng lệnh setFullScreenMode(true).

Code khá đơn giản:

// TestLight.java

import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import com.nokia.mid.ui.*;

public class TestLight extends MIDlet
    implements CommandListener, Runnable {

    private Display display;
    private Form form;
    private Command exitCommand;
    private Command setCommand;
    private MyCanvas cv;

    public TestLight() {
        this.display = Display.getDisplay(this);
        this.form = new Form("Katatonia");
        this.exitCommand = new Command("Exit", Command.EXIT, 1);
        this.setCommand = new Command("Fuck it!", Command.OK, 1);
        this.form.addCommand(this.exitCommand);
        this.form.addCommand(this.setCommand);
        this.form.setCommandListener(this);
        this.form.append("Last fair deal gone down");

        this.cv = new MyCanvas();
    }

    protected void destroyApp(boolean unc) {
    }

    protected void pauseApp() {
    }

    protected void startApp() throws MIDletStateChangeException {
        this.display.setCurrent(this.form);
    }

    public void commandAction(Command c, Displayable d) {
        if (c == this.exitCommand) {
            this.destroyApp(false);
            this.notifyDestroyed();
        } else if (c == this.setCommand) {
            new Thread(this).start();
        }
    }

    public void run() {
        this.display.setCurrent(this.cv);
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) DeviceControl.setLights(0, 0);
            else DeviceControl.setLights(0, 100);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.display.setCurrent(this.form);
    }

}

// MyCanvas.java

import javax.microedition.lcdui.*;

public class MyCanvas extends Canvas {

    public MyCanvas() {
        this.setFullScreenMode(true);
    }

    protected void paint(Graphics g) {
        g.setColor(255, 255, 255);
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
    }

}

Jan 20

Nếu một trang web có nhiều hình ảnh, chúng sẽ được trình duyệt tải về từ từ, tải được đến đâu hiển thị đến đó. Ok, nhưng nếu chúng ta ko muốn người dùng tương tác với hình ảnh trước khi chúng được tải về đầy đủ thì sao? Cách giải quyết là đầu tiên chỉ hiện lên màn hình "Loading…", đồng thời tiến hành tải ảnh vào bộ đệm của trình duyệt, sau khi tải xong thì mới cho hiện lên trang web đầy đủ.

Và chúng ta phải dùng JavaScript (xin đừng nhầm với Java, chúng hoàn toàn khác nhau). Một trong những thế mạnh to bự của JavaScript là nó có thể chọc ngoáy vào mã HTML vốn là tĩnh. Trang web ban đầu chỉ là mã HTML tĩnh để hiện "Loading…", khi tải xong, dùng Node.removeChild() để xóa chữ "Loading…", kế tiếp dùng Node.appendChild() để đưa vào nội dung chính.

Một sai lầm phổ biến tôi thường thấy là code đơn giản như sau:

var oImg = new Image;
oImg.src = "abc.jpg";

Câu lệnh thứ 2 sẽ mở ra một Thread mới, Thread này âm thầm tải ảnh "abc.jpg" vào bộ đệm, đồng thời, ko cần biết đã tải xong hay chưa, ngay tắp lự chạy dòng lệnh tiếp theo. Thế là hỏng việc!

Vậy thì vấn đề là ta phải túm được thời điểm tất cả các ảnh đã tải xong hết!

Bây giờ ta sẽ tạo 1 lớp tên là ImagePreloader, trong contructor của lớp này nhận vào 2 tham số là mảng URL của các tấm hình và tên gọi của hàm sẽ được thực thi sau khi tải xong hết các tấm hình ấy:

function ImagePreloader(images, call-back) {
    // store the call-back
    this.call-back = call-back;
    // initialize internal state
    this.nLoaded = 0;
    this.nProcessed = 0;
    this.aImages = new Array;
    // record the number of images
    this.nImages = images.length;
    // for each image, call preload()
    for (var i = 0; i < images.length; i++)
        this.preload(images[i]);
}

Hàm call-back được lưu lại để sử dụng sau này, các URL được truyền vào hàm preload:

ImagePreloader.prototype.preload = function(image) {
    // create new Image object and add to array
    var oImage = new Image;
    this.aImages.push(oImage);
    // set up event handlers for the Image object
    oImage.onload = ImagePreloader.prototype.onload;
    oImage.onerror = ImagePreloader.prototype.onerror;
    oImage.onabort = ImagePreloader.prototype.onabort;
    // assign pointer back to this
    oImage.oImagePreloader = this;
    oImage.bLoaded = false;
    // assign the .src property of the Image object
    oImage.src = image;
}

Hàm preload tạo ra 1 đối tượng Image mới, cho nó đăng kí 3 sự kiện: onload, onerror, onabort. onload được gọi khi tải thành công, onerror khi có lỗi và onabort khi user ngưng tải bằng cách nhấn nút Stop trên Toolbar của trình duyệt. Các câu lệnh tiếp theo đều dễ hiểu.

Cuối cùng, ta cài đặt sự kiện:

ImagePreloader.prototype.onComplete = function() {
    this.nProcessed++;
    if ( this.nProcessed == this.nImages ) {
        this.call-back(this.aImages, this.nLoaded);
    }
}

ImagePreloader.prototype.onload = function() {
    this.bLoaded = true;
    this.oImagePreloader.nLoaded++;
    this.oImagePreloader.onComplete();
}

ImagePreloader.prototype.onerror = function() {
    this.bError = true;
    this.oImagePreloader.onComplete();
}

ImagePreloader.prototype.onabort = function() {
    this.bAbort = true;
    this.oImagePreloader.onComplete();
}

Chú ý từ khóa this, theo lẽ thường, this sẽ trỏ vào lớp ImagePreloader, tuy nhiên ở trong ngữ cảnh này, this lại trỏ vào đối tượng Image xin đăng kí sự kiện.

Jan 20

Mặc dù vài tuần nữa là về nhà nhưng tôi ko khỏi vui mừng khi mama lại gửi đồ ăn.

Đương nhiên thứ mà lần nào cũng có là ruốc do chính tay mama làm (trong này gọi là chà bông, hừm, hồi đầu nghe tường xà bông, quái, người ta ăn được xà bông sao). Thứ này để được lâu, xếp vào loại lương khô. Tôi không ăn nổi ruốc miền Nam, ngọt quá! Mua mấy cái bánh mì không, cho ruốc vào, ngồi nhâm nhi đọc Teppi, nghe mấy bản nhạc ưa thích, chà chà…

Và… kẹo lạc! Cái thứ kẹo quê mùa. Các bạn trẻ xì tin ngày nay mấy ai thích ăn kẹo lạc? Hehe, tôi thì thích, nếu có trà xanh để nhắm nữa thì cứ gọi là hết cả gói to.

Nhắc đến kẹo lạc, có ai còn nhớ bài đồng dao ngày xưa này ko:

Anh lớp trưởng lấy chị quản ca
Tối hôm qua đi ra Bờ Hồ
Ăn kẹo lạc, hút thuốc lá
Một hai ba chúng ta… tụt quần

Jan 20

Trò chơi Trúc Xanh (Kawai Chen Program Study) khá phổ biến, đặc biệt đối với chị em phụ nữ. Luật chơi là tìm ra 2 quân giống nhau và có đường đi giữa chúng không rẽ quá 2 lần. Đường đi là 1 dãy liên tiếp các ô trống kề cạnh nhau. Dưới góc độ lập trình, tôi tự hỏi khi người chơi click vào 2 quân giống nhau làm thế nào để kiểm tra xem giữa chúng có tồn tại đường đi thỏa mãn hay không? Bảng Pokemon được biểu diễn bởi ma trận a[0..17, 0..10], ở đây để cho khớp với hệ tọa độ khi lập trình GUI tôi cho chỉ số cột của a đứng trước chỉ số hàng, điều này cũng không có gì quan trọng lắm. Bảng thực sự chỉ có các cột 1..16 và các hàng 1..9 các ô đường viền dùng làm lính canh (trong luật chơi cho phép đi ra ngoài bảng) các ô này đương nhiên đều là ô trống. Ma trận a quy ước như sau:

  • a[i, j] = 0 nếu ô (i, j) trống
  • a[i, j] = k > 0 nếu ô (i, j) chứa quân thứ k

Giả sử có 2 ô có quân Pokemon giống nhau cho trước (i1, j1) và (i2, j2) làm thế nào để kiểm tra đường đi thỏa mãn giữa chúng? Vì số lần rẽ chỉ giới hạn là 2 nên bạn có thể liệt kê, xét hết tất cả các trường hợp cùng với 1 đống if else lồng nhau loạn xạ, cách này không hay cho lắm. Ta hãy xét 1 cách khác hay hơn, chuyển bài toán về dạng đồ thị như sau:

  • 2 ô (i1, j1) và (i2, j2) cho trở thành ô trống
  • tập đỉnh của đồ thị là tập các ô trống
  • 2 đỉnh có cạnh nối với nhau nếu 2 ô tương ứng với chúng là (u1, v1) và (u2, v2) cùng nằm trên 1 cột hay 1 hàng và nằm giữa 2 ô này toàn là ô trống
  • trọng số tất cả các cạnh đều = 1

Bài toán chuyển về tìm đường đi ngắn nhất từ đỉnh ứng với ô (i1, j1) đến đỉnh ứng với ô (i2, j2). Nên dùng Tìm kiếm theo chiều rộng vì trọng số các cạnh chỉ là 1. Nếu đường đi này có độ dài không vượt quá 3 thì kết luận có đường đi thỏa mãn, ngược lại thì không có.

Các bạn thấy sao? Có ai có thuật toán nào nhanh hơn thì vào bàn bạc nhé!