วันอังคารที่ 28 ธันวาคม พ.ศ. 2547

ช่วยเหลือผู้ประสบภัยธรรมชาติ

ข่าวเกี่ยวกับซึนามิ (津波) ที่เกิดเมื่อวันอาทิตย์ที่ผ่านมาเริ่มทยอยออกอากาศทางทีวีญี่ปุ่นเพิ่มขึ้นทุกที. หลายชาติในแถบนั้นรวมทั้งไทยด้วยโดนคลื่นซึนามิทำความเสียหายทั้งชีวิตและทรัพย์สินอย่างมหาศาล. ในทีวีเช้านี้ผู้สืบข่าวญี่ปุ่นที่ไปทำข่าวที่ภูเก็ตเล่าเหตุการณ์ให้ฟังด้วยว่าหลังจากคลื่นถล่มแล้ว, ร้านค้าข้าวของต่างๆกระจัดกระจาย. เขาสังเกตว่าบางคนเดินตระเวนเก็บข้าวของหรือสินค้าที่เกลื่อนกลาดทั้งๆดูเหมือนว่าคนเหล่านี้ไม่ใช่เจ้าของ. พูดง่ายๆคือขโมยนั่นเอง. น่าเศร้าใจที่ว่าคนหลายคนทุกข์ร้อน, บ้างก็สูญเสียคนรู้จัก, สูญเสียคนในครอบครัว, สูญเสียทรัพย์สิน, สูญเสียการค้าขาย, แล้วยังมีคนทำอย่างนี้กันได้.

ในข่าวบอกว่าซึนามินั้นไม่มีรูปแบบที่ตายตัว. ในกรณีของภูเก็ตเกิดน้ำลดระดับอย่างกระทันหันแล้วมีคลื่นตามมา. ในกรณีที่เกิดน้ำลดกระทัดหันแบบนี้จะเกิดคลื่นยักษ์ตามมาเสมอ. ส่วนซึนามิที่ไปที่อินเดียศรีลังกกานั้นจะเป็นคนละแบบ. คือไม่มีน้ำลดก่อน, อยู่ๆก็จะเป็นคลื่นยักษ์เข้าถล่มทันที. สาเหตุที่เป็นเช่นนี้เพราะทิศทางการแตกของเปลือกโลกที่อยู่ใต้น้ำไม่เหมือนกัน. (ผู้เชี่ยวชาญแผ่นดินไหวเขาว่าอย่างนั้นในข่าว)

เห็นใน Yahoo Japan Volunteer มีรายชื่อองค์กรที่รับบริจาคช่วยผู้ประสบภัยแถบนั้น. วันนี้เลยไปไปรษณีย์บริจาคเงินผ่านไปรษณีย์ (ธนาคาร) เรียบร้อยแล้ว. ดีกว่าดูข่าวเฉยๆ.

วันศุกร์ที่ 24 ธันวาคม พ.ศ. 2547

หนังสือ (คอมฯ) ที่มีอิทธิพลต่อข้าพเจ้า - ตอนที่ 4

ลิงค์เกี่ยวข้อง

  1. Introduction to ANSI C and UNIX
  2. Tcl/Tk
  3. The C programming language, UEP

วันนี้เอาเรื่องเบาๆเกี่ยวกับหนังสือ (คอมฯ) ที่มีอิทธิพลต่อข้าพเจ้าตอนที่ 4 ครับ. พูดถึงหนังสือเกี่ยวกับ TeX/LaTeX ซะหน่อย. สมัยเป็นนักศึกษามหาวิทยาลัยปีหนึ่งจำได้ว่าในชั่วโมงคอมฯเป็นครั้งที่ใช้ LaTeX. ชั่วโมงคอมฯนั้นเป็นวิชาสอนการใช้ยูนิกซ์และหนึ่งในนั้นเกี่ยวกับการใช้ LaTeX เขียนรายงานหรือเขียนเอกสาร. ตอนนั้นก็ไม่มีอะไรมากมาย, อาจารย์ให้แบบฝึกหัดสร้างเอกสาร LaTeX ส่ง. ในเอกสารให้ใส่รูปภาพด้วย. หนังจากนั้นก็ไม่ค่อยได้ใช้ LaTeX เท่าไรจนกระทั่งขึ้นปี 4 ต้องมีรายงานอาจารย์ทุกสัปดาห์กับงานวิจัย. ก็ใช้ LaTeX อย่างจริงจังในครั้งนั้น.

หนังสืออ่านประกอบเป็นหนังสือภาษาญี่ปุ่น

日本語LATEX2εブック


หมายเหตุ: รูปจาก http://www.ascii.co.jp

พอเรียนปริญญาโทแล้วรู้สึกว่าต้องหาหนังสืออ่านเพิ่มเติมมากกว่านี้เลยซื้อหนังสือที่อาจเรียกได้ว่าเป็นคู่มือของคนใช้ LaTeX ได้แก่ The LaTeX Companion


หมายเหตุ: รูปจาก http://www.bluesky.com/bookshelf/latexcompanion/goossens.gif

หนังสือเล่มนี้มีเป็นชุด. เล่มที่เหลือได้แก่ LaTeX Web Companion และ LaTeX Graphics Companion. เดี๋ยวนี้มีรวมอดิชันใหม่เป็นชุดกล่องขายพร้อมกันเลย. ในความรู้สึกของผม, LaTeX companion จริงๆแล้วไม่ค่อยเหมาะกับคนที่เริ่มใช้ LaTeX แต่เหมาะกับคนที่รู้ LaTeX พอควรแล้วต้องการปรับแต่งเอกสารให้สวยยิ่งขึ้นเช่น แนะนำแพ็กเกจที่เป็นประโยชน์ต่างๆ, วิธีปรับแต่งหัวข้อ ฯลฯ. ส่วนถ้าต้องการเริ่มต้นใช้ LaTeX ก็อ่านจากแหล่งข้อมูลที่เคยเขียนไปแล้วก็ได้. ถ้าจะเอาจริงจังเรื่อง TeX หรือ LaTeX แบบลึกๆก็ต้อง The TeX book ของผู้สร้าง TeX, Donald E. Knuth.

TLE 7.0 - ปรับแต่งพฤติกรรมของแป้นพิมพ์

แต่ละคนมีความชอบพอกับพฤติกรรมของเดกส์ท็อปต่างกันไปคนละแบบ. สำหรับผมอย่างแรกที่คงต้องทำคือสลับเปลี่ยนตำแหน่งคีย์ Cap Lock กับคีย์ Ctrl.

เปลี่ยน Caps Lock ให้เป็น Ctrl

การเปลี่ยน Caps Lock ให้เป็น Ctrl ทำได้จากเมนูหลัก, เลือก "ปรับแต่งเดกส์ทอป" | "แป้นพิมพ์". ไปที่แท็บ "ปรับแต่ผังแป้นพิมพ์". ที่ช่องหน้าต่างทางขวามือ, กระจาย "Control Key position" แล้วเลือก "Make Caps Lock an additional Control". แล้วกดปุ่ม "เพิ่ม". ปิดหน้าต่างให้เรียบร้อยหลังเลือกเสร็จ.

เปลี่ยน key binding ของ Gtk+ ให้เป็นแบบ emacs

เวลาใช้แอพพริเคชันที่ใช้ไลบรารี Gtk+ เวลากด Ctrl+a จะเป็นการเลือกทั้งหมด (select all). แต่สำหรับคนที่คุ้นเคยกับ emacs, Ctrl+a หมายถึงเลื่อนเคอร์เซอร์ไปที่ต้นบรรทัด. ผมชินกับการใช้ Ctrl+a เลยต้องเปลี่ยนพฤติกรรมแป้นพิมพ์ให้เป็นแบบ emacs. อ้างอิงจาก mozillazine เขาบอกว่าถ้าเป็น gnome 2.8 ซึ่งลินุกซ์ทะเล 7.0 ใช้อยู่ต้องแก้ด้วย gconf-editor. เรียก gconf-editor จากเทอร์มินอล. แล้วแก้คีย์ /desktop/gnome/interface/gtk_key_theme เปลี่ยนค่าจาก "Default" ให้เป็น "Emacs".


ลิงค์ที่เกี่ยวข้อง

วันอังคารที่ 21 ธันวาคม พ.ศ. 2547

Qt way - สร้างเส้นโค้งมังกรตอนที่สอง

สารบัญ

เมื่อวานแนะนำไฟล์เฮดเดอร์ซึ่งเป็นส่วนนิยามคลาสไปแล้ว. วันนี้ขอแนะไฟล์ dragonfractal.cpp ซึ่งเป็นส่วนรหัสสร้างอ็อบเจ็ค.

ไฟล์ dragonfractal.cpp

ขอแทรกคำอธิบายไปในระหว่างไฟล์เลยแล้วกันครับ.
     1  #include "dragonfractal.h"
     2  #include <iostream>
     3  #include <qpainter.h>
     4  #include <qwmatrix.h>
     5  #include <qpoint.h>
     6  using namespace std;
ช่วงแรกเป็นการ include ไฟล์เฮดเดอร์ต่างๆที่จำเป็นสำหรับการคอมไพล์. iostream ใช้กับฟังก์ชัน printDirection() สำหรับแสดงผลทางเทอร์มินอล. qpainter.h เป็นไฟล์เฮดเดอร์ของคลาส QPainter ใช้สำหรับวาดเส้น, รูปต่่างๆในวิดเจ็ด. ไฟล์เฮดเดอร์ qwmatrix.h เป็นไฟล์เฮดเดอร์ของคลาส QWMatrix ใช้สำหรับเปลี่ยนระบบจุดพิกัดย้ายจุดเริ่มต้น, หมุน ฯลฯ.
     9  void DragonFractal::init(int order)
    10  {
    11    resize(400,400);
    12    setBackgroundColor(Qt::white);
    13    setBeginX( width() / 2);
    14    setBeginY( height() / 3 );
    15    setBlockWidth(10);
    16    setOrder( order);
    17  }
ฟังก์ชัน init() เป็นฟังก์ชันสำหรับตั้งค่าเริ่มต้นต่างๆเวลาสร้างอ็อบเจ็ค. ฟังก์ชันนี้จะเรียกใช้จากคอนสตรักเตอร์อีกที. บรรทัดที่ 11 และ 12 เป็นการใช้ฟังก์ชันที่สืบทอดมาจากคลาส QWidget ได้แก่ resize() เปลียนขนาดวิดเจ็ดให้มีขนาด 400x400 พิกเซล. ต่อจากนั้นใช้ setBackgroundColor() เซ็ตสีพื้นฉากหลังให้เป็นสีขาว. พึ่งเห็นว่าฟังก์ชันนี้ควรเลิกใช้แล้ว (obsolete) ให้ใช้ฟังก์ชัน setPaletteBackgroundColor แทน. ใน Qt มีการนิยามสีต่างๆที่ใช้บ่อยไว้ให้แล้วเช่นสีแดง Qt::red, สีดำ Qt::black เป็นต้น.

บรรทัดที่ 13 และ 14 เป็นการตั้งจุดเริ่มต้นของแฟรกทอลโดยคำนวณจากความกว้างและสูงของวิดเจ็ด. ฟังก์ชัน setBlockWidth ใช้ตั้งความยาวของเส้นที่วาดแต่ละครั้ง. ส่วน setOrder เซ็ตจำนวนทบของกระดาษที่พับ.

    20  DragonFractal::DragonFractal( QWidget *parent, char *name, int order)
    21    : QWidget( parent, name)
    22  {
    23    init(order);
    24  }
    25
    26  DragonFractal::DragonFractal( int order)
    27    : QWidget( 0, 0)
    28  {
    29    init(order);
    30  }
บรรทัดที่ 20 - 30 เป็นคอนสตรักเตอร์สร้างไว้สองแบบ. โดยปรกติคอนสตรักเตอร์ที่เป็นวิดเจ็ดจะรับอาร์กิวเมนต์สองตัวได้แก่พอนท์เตอร์ชี้ไปที่วิดเจ็ดพ่อแม่และชื่อ. ในที่นี้เพิ่มอาร์กิวเมนต์อีกตัวคือ order. จะเห็นว่าในคอนสตรักเตอร์มีการเรียกคอนสตรักเตอร์ของคลาสที่สืบทอดมาด้วย.
    33  void DragonFractal::setBeginX( int x)
    34  {
    35    bx = x;
    36  }
    37  void DragonFractal::setBeginY( int y)
    38  {
    39    by = y;
    40  }
    41  void DragonFractal::setBlockWidth( int w)
    42  {
    43    bw = w;
    44  }
บรรทัดที่ 33 - 44 เป็นฟังก์ชันสำหรับตั้งค่าคุณสมบัติต่างๆของอ็อบเจ็คท์. ไม่ได้ทำอะไรมากแค่เอาค่าที่ได้รับก็อปปี้ใส่ในตัวแปรที่เป็นเมมเบอร์ของอ็อบเจ็คท์.
    45  void DragonFractal::setOrder( int order)
    46  {
    47    o = order;
    48    int c; // for flip-flop left-right
    49    for( int i=1; i<= o; i++){
    50      if( i == 1){
    51        // true : left, false : right
    52        direction.append(true);
    53      } else {
    54        QValueList::iterator it = direction.begin();
    55        c = 0;
    56        while( it != direction.end())
    57          {
    58            if( c % 2 == 0 )
    59              {
    60                direction.insert(it, true);
    61              } else {
    62                direction.insert(it, false);
    63              }
    64            ++c;
    65            ++it;
    66          }
    67        direction.append(false);
    68      }
    69    }
    70  }
ฟังก์ชัน setOrder เป็นฟังก์ชันสำหรับใช้สร้างเส้นทางสำหรับวาดแฟรกทอล. ตัวแปร c ใช้สำหรับตรวจสอบว่าตอนไหนควรจะใส่ค่า L (true) หรือ R (true) ในลิงค์ลิส direction. ถ้าจำนวน order เป็น 1 จะมีการเลี้ยวซ้ายอย่างเดียว. ถ้า order มากกว่า 1 จะเข้าวงวน while ใช้ iterator ของ QValueList เป็นตัวช่วยวน. ถ้าค่าตัวแปร c เป็นเลขคู่ (c%2 == 0) ก็จะแทรก L (true) หน้าลิสที่กำลังจัดการอยู่. ถ้าค่าตัวแปร c เป็นเลขคี่ก็จะแทรก R (false). พอออกจากวงวน while ก็เพิ่ม R (false) ตามหนึ่งตัว. ถ้า order เท่ากับ 3 จะได้ค่าของตัวแปร direction เป็นลิสของ LLRLLRR.
   L       i=1
 L   R     i=2
L R L R    i=3

ผลสุดท้าย LLRLLRR.
    71  void DragonFractal::setStartPoint( int x, int y)
    72  {
    73    setBeginX(x);
    74    setBeginY(y);
    75  }
    76  void DragonFractal::setStartPoint( QPoint& p)
    77  {
    78    setBeginX(p.x());
    79    setBeginY(p.y());
    80  }
บรรทัดที่ 71 - 80 เป็นฟังก์ชันสำหรับตั้งจุดเริ่มต้น. ไม่มีอะไรมาเพียงแค่เรียกฟังก์ชัน setBeginX, setBeginY ต่ออีกที. QPoint เป็นคลาสสำหรับจุดพิกัด.
    84  void DragonFractal::printDirection()
    85  {
    86    QValueList::iterator it = direction.begin();
    87    while( it != direction.end())
    88      {
    89        if( *it )
    90          cout << "L";
    91        else
    92          cout << "R";
    93        ++it;
    94      }
    95    cout << endl;
    96  }
ฟังก์ชัน printDirection ใช้แสดงเส้นทางการวาดแฟรกทอลทางเทอร์มินอล. เนื่องจากค่าที่อยู่ในลิส direction เป็น bool เวลาแสดงผลเปลี่ยน true ให้แสดงด้วยอักษร L และเปลี่ยน false ให้แสดงด้วย R.
    98  void DragonFractal::drawFractal( QPainter &painter, QWMatrix &matrix)
    99  {
   100    painter.setPen(QPen(black));
   101    matrix.translate( bx, by); // goto begin point
ฟังก์ชัน drawFractal ใช้วาดแฟรกทอลบนวิดเจ็ด. จะรับ reference ของอ็อบเจ็คท์ QPainter กับ QWMatrix มา. painter.setPen เป็นฟังก์ชันของ QPainter ใช้ตั้งค่าปากกาให้มีสีดำ. matrix.translate เปลี่ยนจุดพิกัดเริ่มต้น 0,0 ไปที่จุด bx, by.
   104    QValueList::iterator it = direction.begin();
   105    while( it != direction.end())
   106      {
   107        painter.setWorldMatrix(matrix);
   108        painter.drawLine(0,0,0, bw);
   109        matrix.translate(0, bw);
   110        if( *it ) { // if we have to turn left
   111          matrix.rotate(-90);
   112        } else { //  Right
   113          matrix.rotate(90);
   114        }
   115        ++it;
   116      }
   117    painter.setWorldMatrix(matrix);
   118    painter.drawLine(0,0,0, bw);
   119  }
จากนั้นในบรรทัดที่ 104 -105 อ่านค่าเส้นทางที่อยู่ในตัวแปร direction. เวลาเปลี่ยนจุดพิกัดด้วย matrix.translate แล้วยังไม่มีผลทันที, ต้องเซ็ตให้ painter ใช้ matrix นั้นด้วยฟังก์ชัน setWorldMatrix แล้ววาดเส้นตรงขนาด bw ไปข้างหน้า (ตรงไปทางแกน y).
   121  void DragonFractal::paintEvent(QPaintEvent *)
   122  {
   123    QPainter painter(this);
   124    QWMatrix matrix;
   125    drawFractal( painter, matrix);
   126  }
ฟังก์ชัน paintEvent เป็นฟังก์ชันที่ต้องเขียนเพื่อวาดรูป. ในฟังก์ชันี้จะเป็นที่สร้าง QPainter และ QWMatrix แล้วส่งให้ฟังก์ชัน drawFractal วาดต่อไป.
   128  void DragonFractal::resizeEvent( QResizeEvent *e)
   129  {
   130    setStartPoint( (int)(bx * e->size().width() / e->oldSize().width()),
   131                   (int)(by * e->size().height() / e->oldSize().height()));
   132  }
ฟังก์ชันสุดท้ายคือ resizeEvent เป็นฟังก์ชันจัดการอีเวนท์เหมือนกับ paintEvent. ฟังก์ชันนี้จะรับพอนท์เตอร์ของ QResizeEvent เป็นอาร์กิวเมนต์. ทำให้เรารู้ข้อมูลเกี่ยวกับขนาดของวิดเจ็ดใหม่ด้วยฟังก์ชัน size() และรับรู้ขนาดของวิดเจ็ดก่อนเปลี่ยนขนาดจากฟังก์ชัน oldSize(). แล้วคำนวณจุดเริ่มต้นของแฟรกทอลใหม่. เนื่องจากมีการเปลี่ยนขนาดของวิดเจ็ด, ทำให้เกิดอีเวนท์ paintEvent โดยอัตโนมัติ, วิดเจ็ดจะวาดตัวเองใหม่.

ไฟล์ main.cpp

ไฟล์ main.cpp เป็นไฟล์ที่สร้างแอพพลิเคชันเหมือนโปรแกรม qt อื่นๆทั่วไป. สร้างอ็อบเจ็คท์ QApplication แล้วเซ็ตวิดเจ็ดหลัก, เรียกฟังก์ชัน exec() ทำงาน.
     1  #include 
     2  #include 
     3  #include "dragonfractal.h"
     4
     5  int main( int argc, char *argv[])
     6  {
     7          QApplication app( argc, argv);
     8          //      DragonFractal *a = new DragonFractal(0,0,QString(argv[1]).toInt());
     9                  DragonFractal *a = new DragonFractal(QString(argv[1]).toInt());
    10          //      a->printDirection();
    11          a->show();
    12          app.setMainWidget( a);
    13          app.exec();
    14  }

ต่อจากนี้

รหัสที่เขียนนี้ไม่สมบูรณ์เท่าไหร่. ยังมีที่ต้องแก้ไขอีกเยอะ. ความหน้าจะเพิ่มความสามารถใช้เมาส์ลากรูปแฟรกทอลไปในตำแหน่งที่ต้องการ. และจะได้แนะนำคำสั่ง diff และ patch ไปในตัว.

Debian - ติดตั้ง dhcp เซิร์ฟเวอร์

ประเภทเดียวกัน

วันนี้ต้องย้าย dhcp เซิร์ฟเวอร์ที่แต่ก่อนเคยสร้างไว้ด้วย Red Hat ให้มารันบนเครื่องใหม่ที่เป็น Debian. ตอนแรกก็ไม่รู้ชื่อแพ็กเกจว่าเป็นชื่ออะไรแต่เดาว่าชื่อแพ็กเกจต้องมาคำว่า dhcp แน่เลยใช้ apt-cache หาแพ็กเกจก่อน.

# apt-cache search dhcp
...
dhcp - DHCP server for automatic IP address assignment
dhcp-client - DHCP Client
dhcp-dns - Dynamic DNS updates for DHCP
dhcp-relay - DHCP Relay
dhcp3-client - DHCP Client
dhcp3-common - Common files used by all the dhcp3* packages.
dhcp3-dev - API for accessing and modifying the DHCP server and client state
dhcp3-relay - DHCP Relay
dhcp3-server - DHCP server for automatic IP address assignment
...
ดูเหมือนว่ามีหลายตัวแต่เราต้องการสร้างเซิร์ฟเวอร์เลยมีตัวเลือกอยู่สองแพ็กเกจคือ dhcp กับ dhcp3-server. ตอนแรกลง dhcp ปรากฏว่าเป็นเวอร์ชัน 2.x ไม่สามารถใช้คุณสมบัติใหม่ๆเช่นสร้างพูล (pool) แยกช่วงของ IP แอ็ดเดรสก็เลยต้องเอา dhcp ออก, แล้วลง dhcp3-server แทน. ก่อนอื่นรัน apt-get แบบลองดูด้วยตัวเลือก -s (simulate) ก่อนว่าจะติดตั้งอะไรบ้างแล้วค่อยติดตั้งจริงโดยไม่ใช้ตัวเลือกนี้.
# apt-get -s install dhcp3-server
# apt-get install dhcp3-server
ระหว่างที่ติดตั้งจะมีการรัน post-installation script ถามคำถามว่าจะใช้อินเทอร์เฟสตัวไหน. ในกรณีทีมีแลนการ์ดใบเดียวก็ตอบ eth0. ถ้ามีแลนการ์ดหลายใบก็กำหนดใบที่ต้องการแจกจ่าย IP ด้วย dhcp เซิร์ฟเวอร์.

ต่อจากนั้นจะมีคำเตือนต่อว่าให้สร้างไฟล์ตั้งค่าเริ่มต้นเอง. โดยที่ไฟล์ตั้งค่าเริ่มต้นคือไฟล์ /etc/dhcp3/dhcpd.conf. ไฟล์นี้จะสร้างโดยตัวโปรแกรมติดตั้งไว้ให้แล้ว. แก้ไขไฟล์นี้ตามที่ต้องการแล้วก็จะใช้ได้.

หลังจากนั้นจะมีคำเตือนอีกว่าค่าเซิร์ฟเวอร์นี้จะใช้ค่า non-authoritative โดยปริยาย. มีผลทำให้เซิร์ฟเวอร์ไม่ส่งค่า DHCPNAK ให้ไคลเอ็นท์และไคลเอ็นท์จะไม่ปล่อย IP ที่ได้รับ. เวลาใช้จริงต้องเปลี่ยนค่านี้ให้เป็น authoritative.

จบ apt-get แล้วระบบพยายามสตาร์ท dhcp เซิร์ฟเวอร์เลยแต่ล้มเหลวเพราะเรายังไม่ปรับแต่งอะไร.

ไฟล์ dhcpd.conf

เราต้องเขียนไฟล์ /etc/dhcp3/dhcpd.conf ก่อนที่จะใช้ dhcp เซิร์ฟเวอร์ได้. ไฟล์นี้มีคอมเมนต์อยู่แล้ว, เราสามารถดูตัวอย่างและเขียนตามได้ง่ายๆ.
     1  ddns-update-style none; # ไม่ใช้ dynamic DNS
     2  option domain-name "example.org"; # ชื่อโดเมนของระบบเครือข่าย
     3  option domain-name-servers ns1.example.org, ns2.example.org; # ชื่อหรือ IP ของ DNS เซิร์ฟเวอร์
     4  default-lease-time 600; # จำนวนวินาทีที่ให้ใช้ IP ที่แจกจ่าย
     5  max-lease-time 7200; # จำนวนวินาที (maximum) ที่ให้ใช้ IP ที่แจกจ่าย
     6  authoritative; # บอกว่าเครื่องเซิร์ฟเวอร์นี้เป็น authoritative
     7  log-facility local7; # เก็บล็อกแบบ local7
     8
     9  host comp1 { # ลงทะเบียนเครื่องที่รู้จัก
    10    hardware ethernet 08:00:07:26:c0:a5; # MAC แอ็ดเดรสของเครื่อง
    11    fixed-address 192.168.0.200; # ให้ IP แบบคงที่ไม่เปลี่ยนแปลง
                                       # IP นี้ต้องไม่อยู่ในพูล
    12  }
    13
    14  host comp2 { # เป็นเครื่องที่ลงทะเบียนไว้อีกเครื่องหนึ่งแต่ได้ IP ไม่เจาะจง
    15    hardware ethernet 00:50:56:C0:00:08;
    16  }
    17
    18  subnet 192.168.0.0 netmask 255.255.255.0 { # ซับเน็ตเวิร์ก
    19          option routers 192.168.0.254; # เกตเวย์โดยปริยายของเครือข่าย
    20          pool {
    21                  range 192.168.0.1 192.168.0.100; # กำหนดช่วง IP หลายบรรทัดก็ได้
    22                  deny unknown clients; # ให้ IP เฉพาะเครื่องที่ลงทะเบียนข้างบนไว้เท่านั้น.
    23          }
    24          pool {
    25                  range 192.168.0.101 192.168.0.110;
    26                  allow unknown clients; 
                        # เครื่องที่ไม่ลงทะเบียนก็จะได้ IP จากพูลนี้
    27          }
    28  }
เสร็จแล้วก็สตาร์ท dhcp เซิร์ฟเวอร์ด้วยคำสั่ง
# /etc/init.d/dhcp3-server start
ล็อกของโปรแกรมจะอยู่ในไฟล์ /var/log/syslog เปิดดูตามก็ได้. ส่วนไฟล์ dhcpd.leases ซึ่งเป็นไฟล์ที่เก็บข้อมูลเช่น IP, เครื่องที่รับ IP ไปอยู่ใต้ไดเรกทอรี /var/lib/dhcp3 ครับ.

วันจันทร์ที่ 20 ธันวาคม พ.ศ. 2547

Qt way - สร้างแฟรกทอลเส้นโค้งมังกร

สารบัญ
วันก่อนโพสเรื่องเกี่ยวกับ dragon curve (ขอแปลเป็นภาษาไทยว่าเส้นโค้งมังกรแล้วกัน), ตั้งแต่วันนี้เป็นต้นไปคิดว่าจะแนะนำการเขียนโปรแกรมด้วย Qt โดยโปรแกรมนี้จะวาดเส้นโค้งมังกรตามจำนวนทบ (order) ที่ระบุ.

รหัสต้นฉบับ

ในไฟล์ .tar.gz จะมีไฟล์อยู่สามไฟล์ได้แก่
  • dragonfractal.h เป็นไฟล์นิยามคลาสชื่อ DragonFractal สำหรับสร้างเส้นโค้งมังกร
  • dragonfractal.cpp เป็นไฟล์รหัสต้นฉบับสร้างฟังก์ชันทำให้คลาสเป็นรูปเป็นร่าง.
  • main.cpp ไฟล์สร้างอินแสตนท์ (instance) ของคลาส DragonFractal มาใช้งาน.

ถ้ากระจายไฟล์ dragonfractal.tar.gz แล้วจะได้ไฟล์ทั้งสามไฟล์อยู่ในไดเรกทอรีชื่อ dragonfractal. ให้สร้างโปรแกรมด้วยคำสั่งต่อไปนี้

$ tar xzvf dragonfractal.tar.gz
$ cd dragonfractal
$ qmake -project
$ qmake dragonfractal.pro
$ make
ถ้าไม่มีปัญหาอะไร, มีไลบรารี qt อยู่ในระบบก็จะได้ไฟล์ต่างๆดังนี้
$ ls
Makefile           dragonfractal.o    moc_dragonfractal.cpp
dragonfractal*     dragonfractal.pro  moc_dragonfractal.o
dragonfractal.cpp  main.cpp
dragonfractal.h    main.o
วิธีการรันโปรแกรมให้ใส่จำนวนทบเป็นอาร์กิวเมนต์ของโปรแกรม เช่น ./dragonfractal 10 แล้วจะได้หน้าต่างที่แสดงเส้นโค้งมังกรตามรูปต่อไปนี้ครับ. เวลาปรับขนาดหน้าต่าง, โปรแกรมจะวาดรูปใหม่พยายามให้แสดงรูปทั้งหมดได้ในหน้าต่าง.

ไฟล์ dragonfractal.h

เรามาดูเนื้อหาของไฟล์ต่างๆเริ่มจากไฟล์ dragonfractal.h กันทีละบรรทัดๆ.
     1  #ifndef DRAGON_FRACTAL
     2  #define DRAGON_FRACTAL
บรรทัดที่ 1 - 2 เป็น preprocessor ตั้งตัวแปร DRAGON_FRACTAL ถ้ายังไม่มีการตั้งตัวแปรนี้. ตรงนี้มีเพื่อไม่ให้เกิดการ include ไฟล์ dragonfractal.h นี้ซ้ำ.

ไฟล์เฮดเดอร์ (header file) ที่ใช้มีสองฉบับได้แก่

     3  #include <qwidget.h>
     4  #include <qvaluelist.h>
     5
ในที่นี้เราจะสร้างคลาส DragonFractal ให้เป็นวิดเจดตัวหนึ่งสืบทอดมาจาก QWidget ดังนั้นจึงต้อง include ไฟล์ qwidget.h. ส่วนไฟล์ qvaluelist.h ใช้สำหรับสร้างตัวแปรเป็น linked list (QValueList) ซึ่งจะเก็บข้อมูลของเส้นทางการวาดโค้งได้แก่ลำดับการเลี้ยวซ้ายเลี้ยวขวา.

ต่อจากนี้จะเป็นการนิยามคลาสชื่อ DragonFractal โดยสืบทอดเชื้อสาย (inherit) จากคลาส QWidget.

     6  class DragonFractal : public QWidget
     7  {
     8    Q_OBJECT
     9      Q_PROPERTY(int beginX READ beginX WRITE setBeginX)
    10      Q_PROPERTY(int beginY READ beginY WRITE setBeginY)
    11      Q_PROPERTY(int blockWidth READ blockWidth WRITE setBlockWidth)
    12      Q_PROPERTY(int order READ order WRITE setOrder)
    13
Q_OBJECT เป็นแมคโคร (macro) ใช้กับคลาสที่มีการใช้ slot/signal. ส่วน Q_PROPERTY เป็นแมคโครเช่นกันใช้สำหรับประกาศคุณสมบัติต่างๆของคลาสนี้. รูปแบบที่ใช้กันบ่อยได้แก่
Q_PROPERY( ประเภท ชื่อคุณสมบัติ READ ชื่อฟังก์ชันสำหรับอ่านค่าคุณสมบัติ WRITE ชื่อฟังก์ชันสำหรับตั้งค่าคุณสมบัติ)
คือประกาศประเภทของคุณสมบัติ (type), ชื่อคุณสมบัติ. ชื่อคุณสมบัตินี้ไม่ใช่ตัวแปรที่เก็บค่าของคุณสมบัตินั้น. เป็นแค่ชื่อไว้อ้างอิงสามารถใช้กับฟังก์ชัน QObject::setProperty( const char * name, const QVariant & value ) ได้. ถ้ามีการสร้างคุณสมบัติของอ็อบเจ็ค, ควรประกาศคุณสมบัตินั้นด้วย Q_PROPERTY เสมอ.
    14      public:
    15    DragonFractal(QWidget *parent = 0, char *name = 0, int order = 1);
    16    DragonFractal(int order);
บรรทัดที่ 14-16 เป็นการประกาศคอนสตรักเตอร์ (constructor) ของอ็อบเจ็คให้มีสองแบบคือแบบทั่วไปที่รับ Qwidget กับชื่อเป็นอาร์กิวเมนต์, และรับ order (จำนวนทบของการพับกระดาษ) เป็นอาร์กิวเมนต์อย่างเดียว.
    17
    18    void init(int order);
บรรทัดที่ 18 เป็นฟังก์ชัน init สำหรับจัดการสร้างวิดเจดจริงๆซึ่งจะถูกเรียกใช้จากคอนสตรักเตอร์อีกที.
    19    int beginX() const {return bx;};
    20    void setBeginX(int x);
    21    int beginY() const {return by;};
    22    void setBeginY(int y);
    23    int blockWidth() const {return bw;};
    24    void setBlockWidth(int w);
    25    int order() const {return o;};
    26    void setOrder(int order);
บรรทัดที่ 19 - 26 เป็นฟังก์ชันต่างๆที่เกี่ยวกับคุณสมบัติของอ็อบเจ็ค. เช่นฟังก์ชัน beginX() เป็นฟังก์ชันสำหรับอ่านค่าคุณสมบัติชื่อ "beginX". ฟังก์ชันสำหรับอ่านค่าคุณสมบัติที่ประกาศด้วยแมคโคร Q_PROPERTY ต้องคืนค่าเป็นแบบ const เสมอเพื่อให้รู้ว่าอ่านอย่างเดียว (ไม่งั้นเกิด warning หรือ error ตอนคอมไพล์).
    27    void setStartPoint( int x, int y);
    28    void setStartPoint( QPoint &p);
เป็นฟังก์ชันสำหรับตั้งค่าจุดเริ่มต้นของเส้นโค้งที่จะวาด. สร้างฟังก์ชันไว้สองแบบคือรับอาร์กิวเมนต์สองตัวแบบ int และรับอาร์กิวเมนต์ตัวเดียวเป็นแบบ QPoint.
    29    void printDirection();
    30
ฟังก์ชัน printDirection() สำหรับแสดงเส้นทาง (ทิศ) เวลาวาดเส้นทางเทอร์มินอล.
    31   protected:
    32    void paintEvent(QPaintEvent *event);
    33    void resizeEvent(QResizeEvent *event);
ฟังก์ชันที่ต้องสร้างสำหรับจัดการอีเวนท์ (event) ต่างๆที่สำคัญ. ในที่นี้ต้องเขียนฟังก์ชัน paintEvent เพื่อใช้วาดรูปที่ต้องการ. ฟังก์ชันนี้จะถูกเรียกใช้เมื่อวิดเจดของเรา (DragonFractal) ต้องแสดงผลทางหน้าจอ, โผล่จากการถูกบังจากหน้าต่างอื่น, หน้าต่างเปลี่ยนขนาด ฯลฯ. ฟังก์ชัน resizeEvent เป็นอีกฟังก์ชันหนึ่งที่ใช้ในที่นี้. จะถูกเรียกใช้เมื่อขนาดหน้าต่างของวิดเจดเปลี่ยนไป. ในกรณีของ DragonFractal ถ้าขนาดหน้าต่างเปลี่ยนไป, เราจะตั้งค่าจุดเริ่มต้นใหม่ให้สัมพันธ์กับขนาดหน้าต่าง, จะได้แสดงรูปได้เต็มๆเมื่อขยายขนาดหน้าต่าง.
    34   private:
    35    QValueList<bool> direction; // line direction
บรรทัดที่ 35 เป็นการประกาศตัวแปรชื่อ direction แบบ QValueList<bool>. ตัวแปรนี้จะเป็น linked list ในลิสจะเก็บค่าถูกหรือผิด (boolean) เท่านั้น. ในโปรแกรมจะให้ true แทนการเลี้ยวซ้าย (L) และ fasle แทนการเลี้ยวขวา (R). ตัวแปรนี้ใช้เก็บเส้นทางสำหรับวาดแฟรกทอล. สาเหตุที่ใช้ QValueList เพราะสะดวกในการแทรกข้อมูล.
    36    void drawFractal( QPainter &painter, QWMatrix &matrix);
บรรทัดที่ 36 ประกาศฟังก์ชันที่ใช้วาดแฟรกทอลซึ่งจะถูกเรียกใช้จากฟังก์ชัน paintEvent อีกที. เวลาวาดรูปต้องใช้ QPainter และ QWMatrix เปลี่ยนระบบแกนหมุนทิศไปมาเพื่อให้วาดรูปได้สะดวก.
    37    int o;   // order (how many folds)
    38    int bx;  // begin X
    39    int by;  // begin Y
    40    int bw;  // block width
    41  };
บรรทัดที่ 37 ถึง 41 เป็นการประกาศตัวแปรที่เก็บค่าคุณสมบัติต่างๆ.

แล้วพรุ่งนี้จะต่ออธิบายรหัสต้นฉบับที่สร้างคลาส, ฟังก์ชันตัวจริงต่อครับ.

วันศุกร์ที่ 17 ธันวาคม พ.ศ. 2547

เกี่ยวกับภาษาไทย

ไม่รู้ว่าคุณรู้สึกเหมือนผมไหมว่าคนที่อยู่ในกลุ่ม TLWG มีความสนใจด้านภาษาและมีเป้าหมายอะไรบางอย่างเหมือนกันไม่มากก็น้อย. ภาษาในที่นี้ผมหมายถึงภาษามนุษย์โดยเฉพาะภาษาไทยนะครับ, ไม่ใช่ภาษาคอมพิวเตอร์.

สมัยที่เรียนหนังสือตอนมัธยม, วิชาที่ไม่ชอบที่สุดคือวิชาภาษาไทย. ส่วนวิชาที่ชอบที่สุดคือวิชาพละศึกษาเพราะได้สนุกกับกีฬา (เสียใจด้วยที่ไม่ใช่คณิตศาสตร์หรือวิทยาศาสตร์). พอมาเรียนที่ญี่ปุ่น, ผมเริ่มรู้สึกอะไรบางอย่างว่าสิ่งที่สำคัญจริงๆแล้วคือภาษา. ไม่ใช่ภาษาอังกฤษนะ, แต่เป็นภาษาไทย. ตอนเรียนภาษาญี่ปุ่นนี่รู้สึกเลยว่าการอธิบายให้คนอื่นเข้าใจในเรื่องต่างๆที่อยู่ในความคิดเรานี่ยากมาก. ไปๆมาๆกลับรู้สึกละอายว่าเราเป็นคนไทยกับไม่ค่อยสนใจในภาษาไทยเท่าที่ควร (ไม่ได้หมายความผมสนใจภาษาญี่ปุ่นหรืออะไรเป็นพิเศษนะครับ). ทุกทีที่เรียนไวยกรณ์ญี่ปุ่นใหม่ๆทำให้อดคิดไม่ได้ว่าเราเรียนภาษาไทยมายังไง. ภาษาไทยที่เราใช้นั้นถูกต้องดีหรือเปล่า, มีหลักเกณฑ์ไหม? อาจจะเป็นเพราะภาษาไทยเป็นภาษาแม่เลยเรียนรู้จากความเคยชินมากกว่าเรียนจากไวยกรณ์. เพราะบางทีคนญี่ปุ่นบางคนยังใช้ภาษาญี่ปุ่นผิดๆเลย. ที่ผมรู้ก็คงเป็นเพราะผมเรียนจากไวยกรณ์, ส่วนภาษาที่คนญี่ปุ่นใช้มาจากความเคยชิน. ในทางกลับกัน, รู้ตัวเลยว่าเราไม่แม่นไวยกรณ์ภาษาไทยเท่าไหร่นัก. บางที่สอนภาษาไทยให้คนญี่ปุ่นก็อธิบายไม่ได้ว่าทำไมถึงเป็นอย่างนั้น. บอกได้แต่ว่ามันต้องเป็นอย่างนั้น, อันนี้เป็นอย่างนี้, อันนี้เป็นข้อยกเว้น. เลยรู้สึกว่าภาษาไทยไม่ค่อยเป็นระเบียบ. แต่จริงๆแล้วคงเป็นระเบียบแต่ผมไม่รู้เท่านั้นเอง.

ไม่ใช่เรื่องภาษาเท่านั้น, วัฒนธรรมก็เหมือนกัน. ประเพณี, ประวัติศาสตร์ประเทศ ฯลฯ เราคนไทยรู้กันแค่ไหน? อาจารย์คนญี่ปุ่นเคยถึงกับพูดว่า "นักเรียนต่างชาติเป็นคนที่ไม่รู้จักชาติตัวเองมากที่สุด". สำหรับผมแล้วคิดว่าจริง, ตรงกับตัวเองเลย. ดังนั้นทุกวันนี้จึงเขียนไทย, ใช้ภาษาไทยให้มากและถ้าเขียนก็อยากเขียนดีๆให้คนอื่นเข้าใจ. บางทีรู้สึกว่าเมื่อเราออกมาจากเมืองไทยแล้วก็เห็นอะไรๆหลายอย่างคนละมุมมองซึ่งไม่เคยรู้สึกนึก, หรือคิดมาก่อน. ทำให้รู้สึกถึงความสำคัญของภาษาตัวเอง. ยังคิดอยู่เหมือนกันว่าถ้าไม่ได้มาเรียนญี่ปุ่นแต่ไปเรียนที่อเมริกาหรือชาติที่พูดภาษาอังกฤษเป็นหลัก, ผมจะคิดถึงความสำคัญของภาษาไทยเหมือนวันนี้หรือไม่.

เคยคิดว่าทำไมญี่ปุ่นถึงเป็นชาติที่เก่งในหลายๆด้าน. ส่วนตัว, ผมคิดว่าส่วนหนึ่งมาจากภาษา. เพราะภาษาเป็นการถ่ายทอดความคิดและความรู้ให้สืบต่อกันไป. ผมคิดว่าคนญี่ปุ่นชอบเขียน. เขียนเว็บ, เขียนไดอารี, บันทึกโน่นนี่. คนญี่ปุ่นชอบอ่าน. พอคนชาติเดียวกันอ่านสิ่งเหล่านั้นโดยเฉพาะสิ่งที่มีประโยชน์ก็ทำให้เขาพัฒนา.

สรุปคือภาษาเป็นสิ่งสำคัญ. การเขียนเป็นการถ่ายทอดความคิด (ถูกหรือผิด), ความรู้ (จริงหรือเท็จ). เขียนให้มาก, อ่านให้เยอะ, แล้วคิดก็จะดี. ช่วยพัฒนาตัวเองและพัฒนาชาติด้วย.

ที่เขียน blog บ่อยๆเป็นภาษาไทย (ถ้าไม่แพ้ความขี้เกียจของตัวเอง) ก็ไม่รู้มีใครอ่านบ้าง. แต่ถ้าสิ่งที่เขียนเป็นประโยชน์สำหรับใครบางคนแล้ว, ผมก็พอใจแล้วครับ.

เส้นโค้งมังกร - Dragon curve

มีหนังสืออยู่ที่บ้านชื่อเรื่อง C言語による最新アルゴリズム事典 (สารนุกรมอัลกอริทึมสำหรับภาษา C) เขียนโดย 奥村晴彦. อาทิตย์นี้หยิบขึ้นมาอ่านเพราะอยากจะเขียนโปรแกรมกราฟิกด้วย Qt ซะหน่อย. อ่านเจอเรื่องเกี่ยวกับพับกระดาษว่าถ้าพับกระดาษที่เป็นเส้นหนึ่งครั้งแล้วค ลี่ออกมาให้ด้านแต่ละด้านตั้งฉากกันตามรอยพับ, ก็จะได้รูปตัว L. ถ้าพับสองทบแล้วคลี่ออกให้ทุกด้านตั้งฉากกันตามรอยพับไปเรื่อยก็จะได้รูปร่า งที่มีรูปแบบเหมือนรูปต่อไปนี้.

ถ้าพับกระดาษหลายๆทบไปเรื่อยๆ (ถ้าพับได้จริง) แล้วคลี่ออกมาจะได้ลวดลายที่เรียกว่าเส้นโค้งมังกร (dragon curve) ซึ่งเป็นแฟรกทอล (fractal) แบบหนึ่งตามตัวอย่างต่อไปนี้.

ถ้าพับกระดาษได้ 12 ทบแล้วคลี่ออกมาก็จะได้ตามรูปข้างบนครับ. คุณสมบัติของลวดลายนี้มีอยู่ว่าเวลาที่คลี่กระดาษออกมาแล้ว, ด้านต่างๆจะไม่ทับกัน.

ในหนังสือมีรหัสต้นฉบับภาษา C ให้ดูด้วยแต่อ่านแล้วไม่ค่อยเข้าใจเลยมานั่งคิดเองทำเองดีกว่า.

หาข้อมูลเพิ่มเติมได้ความว่ามีวิธีคิดเป็นระบบเรียกว่า L-system และเส้นโค้งรูปมังกรก็เป็นหนึ่งในนั้น.

ก่อนอื่นลองหาความสัมพันธ์ระหว่างจำนวนทบ (order) กับจำนวนด้านและจำนวนมุมดูได้แบบนี้.

จำนวนทบจำนวนด้านจำนวนมุม
010
121
247
3815
.........
n2n
จากแหล่งข้อมูลเขาบอกว่าเส้นโค้งเริ่มด้วยเส้นตรง, ทบที่หนึ่งสมมติว่าเลี้ยวขวา. ทบที่ 2 จะเป็นเลี้ยวซ้าย (L) และเลี้ยวขวา (R). ทบที่ 3, 4,... ก็จะคล้ายๆกันคือเลี้ยวซ้าย, ขวาสลับกันไปเรื่อยๆ. ในกรณีที่ n=0 กับ n=1 จะเป็นกรณีพิเศษคือ n=0 ไม่มีการพับ, n=1 มีทบเดียวคือเลี้ยวซ้ายอย่างเดียว. เลยมานั่งเขียนภาพแล้วดูความสัมพันธ์ของมุมและการเลี้ยวแทน.

สรุปได้ว่าถ้านับมุมต่างๆจะได้ความสัมพันธ์เป็น.

n=0    00
n=1    010
n=2    02120
n=3    032313230
n=4    04342434143424340
n=5    .....
อันนี้ดูเป็นระเบียบมีกฏเกณฑ์ดีคล้ายๆกับเป็นกระจกเงาโดยมีจุดเลี้ยวกลับที่เลข 1. ส่วนถ้าดูการเลี้ยวเช่นลากเส้นหนึ่งทีแล้วเลี้ยวหนึ่งที (ซ้ายหรือขวา) ก็จะได้ความสัมพันธ์เป็น
n=0
n=1    L
n=2    LLR
n=3    LLRLLRR
n=4    LLRLLRRLLLRRLRR
n=5    LLRLLRRLLLRRLRRLLLRLLRRRLLRRLRR
n=6    ...
ตัวอย่างเช่น n=2, จากจุดเลข 0 ลากเส้นตรงหนึ่งเส้นไปหาจุดเลข 2, เลี้ยวซ้ายหนึ่งที (L) เพื่อไปที่จุดเลข 1. จากเลข 1 เลี้ยวซ้ายไปหาจุดที่ 2 รวมการเลี้ยวทั้งหมดเป็น LL. และสุดท้ายเลี้ยวขวาไปหาจุด 0 ได้จำนวนการเลี้ยวทั้วหมด 3 ครั้งเป็น LLR (L, R ในรูปเป็นการเลี้ยวคนละความหมายกัน. ในรูปจะนับจำนวนด้าน 2 ด้านเป็นหนึ่งเลี้ยว.). ดูๆเหมือนไม่มีกฏเกณฑ์แต่จริงๆแล้วพอดูดีๆเทียบกับความสัมพันธ์ของมุมก็จะพบว่ามีกฏเกณฑ์แฝงอยู่.
n=0
n=1    L
n=2    LLR
n=3    LLRLLRR
n=4    LLRLLRRLLLRRLRR
n=5    LLRLLRRLLLRRLRRLLLRLLRRRLLRRLRR
n=6    ...
พอจะเห็นความสัมพันธ์แล้วว่าตัวเลขมุมที่เขียนที่มากกว่าหรือเท่ากับ 2 เป็นต้นไปจะมีจำนวนเป็นคู่. ถ้าให้ตัวแรกเป็น L แล้วตัวที่สองจะเป็น R.
n=0    00
n=1    010
       0L0                         
n=2    02120
        L R               ->   0L1R0   -> 0L1R0
n=3    03L313R30 
        L R L R           ->   0LLR1LRR0
n=4    04L4L4R414L4R4R40   
        L R L R L R L R   ->   0LLRLLRR1LLRRLRR0
...

สรุปคือถ้าสร้างรูปแบบการเดินทาง (LLR....) นี้ได้แล้วก็สามารถเอาไปวาดเส้นโค้งมังกรได้. คือถ้าต้องการวาดเส้นโค้งมังกร n เท่ากับจำนวนใดๆ, ก่อนอื่นสร้างรูปแบบการเดินทาง LLRLL... ก่อน. เสร็จแล้วลากเส้นเดินทางตามรูปแบบที่สร้างไว้ก็ได้เส้นโค้งมังกรที่ต้องการครับ. ตอนต่อไปจะเริ่มเข้าตัวโปรแกรมโดยใช้ Qt เขียนครับ.

หมายเหตุ:

  • รูปวาดด้วย inkscape เช่นเคย.
  • วิธีการวาดเส้นโค้งมังกรมีหลายวิธี. วิธีนี้เป็นวิธีหนึ่งที่เข้าใจง่าย (สำหรับผม).

วันพฤหัสบดีที่ 16 ธันวาคม พ.ศ. 2547

รูปแสดงค่า sin, cos ของผลรวมของมุม

บางครั้งคิดไปคนเดียวว่าสิ่งที่เคยเรียนไปบางอย่างก็ลืมๆไปแล้วเช่น sin(a+b) = sin(a)cos(b) + cos(a)sin(b). ตอนแรกเกือบนึกสูตรไม่ออกว่าเป็นอย่างไร. พอนึกสูตรได้ก็ทำให้อยากคิดต่อว่าแล้วทำไม่ sin(a+b) = sin(a)cos(b) + cos(a)sin(b). จำได้ว่าสมัยที่ยังเป็นนักเรียนเคยพิสูจน์ด้วยรูปภาพได้, เลยลองวาดรูปพิสูจน์อีกครั้งว่าทำไม sin(a+b) = sin(a)cos(b) + cos(a)sin(b) ในวันนี้.

ไม่รู้ว่าจะเรียกว่าการพิสูจน์ได้หรือเปล่าเพราะเป็นแค่การวาดภาพประกอบความเข้าใจ, แต่การวาดภาพนี้แหละที่ทำให้ผมไม่เคยลืม (ถึงจะจำไม่ได้ทันทีพอวาดภาพนิดหน่อยก่อนเริ่มจำได้) ว่าทำไมสูตรถึงเป็นอย่างนั้น. ภาพที่วาดได้เป็นแบบนี้ครับ.

มีสามเหลี่ยมมุมฉากสองอันคือสามเหลี่ยม ABC กับสามเหลี่ยม ACE. ส่วนของเส้นตรง AE มีความยาวเป็น R.

สิ่งที่ต้องการหาคือค่าของ sin(a+b). จะเห็นว่าค่าของ sin(a+b) คือ EF/AE และจากรูปจะเห็นว่า EF = BC + CD. เพราะว่า CE = R sin(a), ดังนั้น CD = R sin(a)cos(b). เพราะว่า AC = R cos(a), ดังนั้น BC = R cos(a)sin(b).

sin(a+b) = EF/AE
         = (BC+CD)/AE
         = ((R cos(a)sin(b)) + (R sin(a)cos(b))) / R
         = cos(a)sin(b) + sin(a)cos(b)
ทำนองเดียวกัน
cos(a+b) = (AB - FB)/AE
         = (R cos(a)cos(b) - R sin(a)sin(b)) / R
         = cos(a)cos(b) - sin(a)sin(b)
ถ้าต้องการหาค่า sin(a-b) กับ cos(a-b) เราต้องรู้คุณสมบัติของตรีโกณก่อนว่า
sin(-a) = - sin(a)
cos(-a) = cos(a)

ดังนั้น

sin(a-b) = sin(a)cos(-b) + cos(a)sin(-b)
         = sin(a)cos(b) - cos(a)sin(b)

cos(a-b) = cos(a)cos(-b) - sin(a)sin(-b)
         = cos(a)cos(b) + sin(a)sin(b)
สรุป
sin(-a) = - sin(a)
cos(-a) = cos(a)
sin(a+b) = sin(a)cos(b) + cos(a)sin(b)
sin(a-b) = sin(a)cos(b) - cos(a)sin(b)
cos(a+b) = cos(a)cos(b) + sin(a)sin(b)
cos(a-b) = cos(a)cos(b) - sin(a)sin(b)
บางคนบอกว่าคณิตศาสตร์เป็นการจำ. เช่นการจำสูตร. แต่สิ่งที่สำคัญกว่าการจำสูตรคือการเรียนรู้ว่าสูตรเหล่านั้นมาได้อย่างไร. พอรู้ที่มาทำให้เข้าใจยิ่งขึ้น, และไม่ลืม. เลยคิดต่อไปว่าการพิสูจน์นี่เป็นการจำการพิสูจน์หรือไม่? คิดว่าไม่ใช่, เพราะการพิสูจน์ใช้ความคิดมากกว่าความจำ. บางทีเป็นการแสดงข้อเท็จจริงให้เห็นชัด.

หมายเหตุ: รูปวาดด้วย inkscape.

วันอังคารที่ 14 ธันวาคม พ.ศ. 2547

R

อ่าน slashdot.org มีคนถามว่ามีซอฟต์แวร์เปิดรหัส (open source software) เกี่ยวกับคณิตศาตร์อะไรบ้างที่แนะนำ. มีคนแนะนำให้ไปดูดิสทริบิวชันที่สร้างจาก Knoppix ชื่อ Quantian ก็เลยตามไปดูเพราะไม่รู้จักมาก่อน. แพ็กเกจหลายตัวน่าสนใจมากโดยเฉพาะ R Project ซึ่งเป็นภาษาและสภาพแวดล้อมสำหรับการคำนวณเกี่ยวกับสถิติทั้งหลาย. ดู screenshot แล้วอยากใช้แต่ไม่รู้จะใช้ทำอะไร. ภาพที่ชอบคือ

เพราะคิดว่า gnuplot วาดไม่ได้.

เห็นบอกว่าภาษา R เขียนให้เหมือน (เลียนแบบ?) กับภาษา S ซึ่งสร้างโดย Bell lab (อีกแล้ว) และมีซอฟต์แวร์ที่เป็นเครือญาติกับ S ได้แก่ S-Plus ที่มีชื่อเสียง (ไม่เคยใช้หรอกแต่เคยได้ยินเพื่อนสายศิลป์ตอนเรียนอยู่เคยพูดถึง) ด้านคำนวณสถิติ, เขียนกราฟ, นำเสนอข้อมูล. แต่เขาว่ากันว่า S-Plus นี่แพงมาก. ภาษา S ได้รับรางวัล ACM Software System Award ในปี 1998 ด้วย.

จำได้ว่าเคยเห็นหนังสือภาษาญี่ปุ่นเกี่ยวกับ R แต่ตอนนั้นไม่รู้จัก, ยังงงๆอยู่ว่าโปรแกรมอะไร. ดูไม่น่าเชื่อถือ, อักษรตัวเดียว. ว่าแล้วไปดู amazon.co.jp หาหนังสือเกี่ยวกับ R คงจะต้องซื้อมาอ่านดูซะแล้ว. เห็นคนให้ความเห็นหลายคนว่าดี. แต่ก่อนอื่นคงต้องไปอ่าน R 言語 จาก google.co.jp ซะก่อน. มีคนญี่ปุ่นเขียนไว้เพียบเลย.

ว่าแล้ว emerge ลงในเครื่องที่ทำงานเรียบร้อย

# emerge R
ใครเคยใช้หรือมีประสบการณ์อย่างไรก็มาบอกเล่าให้ฟังด้วยแล้วกันนะครับ.

วันจันทร์ที่ 13 ธันวาคม พ.ศ. 2547

หนังสือ (คอมฯ) ที่มีอิทธิพลต่อข้าพเจ้า - ตอนที่ 3

สารบัญ

  1. Introduction to ANSI C and UNIX
  2. Tcl/Tk


หมายเหตุ: รูปจาก http://cm.bell-labs.com/cm/cs

ถ้าใครเป็นแฟนพันธ์แท้ระบบปฏิบัติการยูนิกซ์ก็ต้องรู้จัก Brian W. Kernighan. Kernighan เป็นหนึ่งในหลายๆคนที่มีส่วนช่วยการพัฒนายูนิกซ์ให้เป็นรูปร่างในช่วงแรกๆ. เขาเป็นหนึ่งในผู้สร้างโปรแกรม awk ซึ่งอาจจะเรียกได้ว่าเป็นต้นแบบของภาษา Perl ในเวลาต่อมาเพราะ Perl ได้รับแรงบรรดาลใจมาจากโปรแกรมนี้เหมือนกัน.

หนังสือคอมฯที่มีอิทธิพลต่อข้าพเจ้าตอนที่ 3 นี้เป็นหนังสือสองเล่มที่เขียนโดย Kernighan ได้แก่

  • The Unix Programing Environment

    หนังสือเล่มนี้มีคนเขียนอีกคนหนึ่งคือ Rob Pike ซึ่งก็อยู่ Bell lab เหมือนกัน. จำได้ว่ารู้จักหนังสือเล่มนี้เพราะพี่พั้งค์แนะนำมา. ตอนนั้น (6-7 ปีที่แล้ว) หาซื้อลำบากเพราะเป็นหนังสือเก่า. ต้องสั่งเอา. เป็นหนังสือคลาสสิกอีกเล่มที่ควรมี, เนื้อหาอ่านง่าย. พูดถึงการใช้ยูนิกซ์ตั้งแต่เบื้องต้น, ระบบไฟล์, filter, เชลล์สคริปต์, การพัฒนาโปรแกรมในยูนิกซ์, การเขียนเอกสาร. เนื้อหาเก่าแล้วบางอย่างใช้ไม่ได้แต่ก็เป็นประโยชน์ดีครับ.

  • The C Programming Language, Second Edition

    เป็นหนังสือภาษา C เขียนร่วมกับ Dennis M. Ritchie ผู้สร้างภาษาซีและยูนิกซ์. ใครที่เคยเรียนภาษาซีคงจะรู้จักหนังสือเล่มนี้. ว่ากันว่าหนังสือเล่มนี้เป็นหนังสือเล่มแรกที่ใช้ตัวอย่างโปรแกรม "Hello world" เป็นโปรแกรมแนะนำภาษา. ต่อจากนั้นมาไม่ว่าจะเป็นภาษาคอมฯแบบไหนก็มักจะแนะนำโปรแกรมอันแรกด้วยโปรแกรมที่แสดงคำว่า "Hello world" กันทั้งนั้น.

    ถ้าเข้าใจไม่ผิด First edition (เคยเห็นที่ห้องสมุดของมหาวิทยาลัย) นี่ไวยกรณ์, การเขียนเรียกกันว่าสไตล์ K&R ซึ่งไม่ compatiable กัน ANSI C ในช่วงถัดมา. ดังนั้นหนังสือที่ขายกันในปัจจุบันจึงเป็น Second edition หมดแล้วซึ่งเข้ากับมาตรฐาน ANSI C. ในคำนำ (จำไม่ค่อยได้) กล่าวว่าภาษา C เป็นภาษากระทัดรัดไม่ต้องการหนังสือที่มีความหนามาก. และก็จริงที่ว่าหนังสือเล่มนี้ไม่ได้หนาเทอะทะ. เนื้อหากระทัดรัด, ได้ใจความสำคัญทุกอย่างของภาษา C ครับ.

    หนังสือเล่มนี้จำได้ว่าซื้อที่ศูนย์หนังสือจุฬาตอนปิดเทอมกลับเมืองไทย.

วันเสาร์ที่ 11 ธันวาคม พ.ศ. 2547

Firefox - ใช้ Live bookmark

RSS (Really Simple Syndication) แปลตรงตามตัวว่า "การเสนอข่าวอย่างง่าย" เป็นวิธีการที่จะให้คนอื่นรับรู้ความเคลื่อนไหว, การเปลี่ยนแปลงของเว็บโดยแสดงในรูปของไฟล์ XML. ปัจจุบัน RSS ใช้กันอย่างแพร่หลายโดยเฉพาะเว็บไซด์ที่นำเสนอข้อมูลในเชิงข่าว, หรือข้อมูลที่เปลี่ยนแปลงบ่อยๆเช่นเว็บไซด์ของ slashdot.org. คนที่ติดตามอ่านไม่จำเป็นต้องเข้าไปดูที่เว็บไซด์นั้นว่ามีอะไรใหม่, ถ้าใช้ RSS reader บันทึกไฟล์ RSS, ATOM แล้วก็อ่านจากซอฟต์แวร์นั้นๆ (ผมไม่เคยใช้ครับ).

ใน firefox มีความสามารถเกี่ยวกับ RSS นี้ด้วยเรียกว่า Live bookmarks. ถ้าเว็บไซด์ที่มีการเสนอข้อมูลแบบ RSS นี้เช่น Weblog ต่างๆจะเห็นเครื่องหมายสีส้มๆ ที่มุมขวาล่างของเบราเซอร์.

ถ้าคลิ้กดูก็มีเขียนว่า "Subscribe to ...". ถ้าสมัคร RSS นั้นไว้ firefox จะเก็บไว้ใน bookmark. ผมสร้างโฟล์เดอร์ย่อยชื่อ RSS ใน Bookmarks Toolbar Folder ไว้และเก็บ RSS ของที่ต่างๆไว้ในที่เดียวกัน. เวลาดูความเคลื่อนไหวว่ามีอะไรใหม่ก็แค่คลิ้กจาก Bookmarks ดูว่ามีอะไรใหม่บ้างตามรูปข้างล่างครับ.

หรือจะเปิด Bookmarks ที่ side bar ( "view" | "Side bar" | "Bookmarks" หรือ Ctrl+B) แล้วดูเอาก็ได้.

ถ้าต้องการ refresh เองทันทีก็คลิ้กขวา bookmark นั้นแล้วเลือก "Refresh live bookmark".

TLE 7.0 - ลงทะเลแล้ว apt-get

ลงลินุกซ์ทะเล (เลือกแพ็กเกจทั้งหมด) ไปเรียบร้อยแล้วครับในเครื่อง IBM Thinkpad R31. ทุกอย่างเป็นไปอย่างราบรื่นไม่มีปัญหาอะไร. จากที่เขียนไว้ในบันทึกการออกรุ่นว่าในลินุกซ์ทะเลไม่สามารถเผยแพร่โปรแกรมเล่นไฟล์เสียง mp3 ได้ (แพ็กเกจ xmms-mp3) เนื่องจากปัญหาสิทธิบัตร, และไม่สามารถเผยแพร่โปรแกรม flash-plugin ได้เช่นกันเนื่องจากเอกสารอนุญาตไม่อนุญาตให้ทำ. หลังจากติดตั้งลินุกซ์ทะเล 7.0 แล้วต้องติดตั้งแพ็กเกจเหล่านี้ด้วย apt-get เองผ่านทางเน็ตเวิร์ก.

ก่อนอื่นถ้าคนที่ล็อกอินอยู่ไม่ใช่ root ก็ใช้คำสั่ง su เปลี่ยนตัวเองให้เป็น root.

$ su - 
password:
#
เสร็จแล้ว apt-get update เพื่อดึงข้อมูลแพ็กเกจล่าสุดจากเครื่องแม่ข่ายของ TLE.
# apt-get update
หลังจากนั้นลองทดสอบการติดตั้งแพ็กเกจ xmms-mp3 ด้วยคำสั่ง apt-get -s install xmms-mp3
# apt-get -s install xmms-mp3
Reading Package Lists... Done
Building Dependency Tree... Done
You might want to run `apt-get -f install' to correct these:
The following packages have unmet dependencies:
  mplayer-mencoder: Depends: lame but it is not going to be installed
E: Unmet dependencies. Try 'apt-get -f install' with no packages (or specify a solution).
ปรากฏว่าเกิดข้อผิดพลาดไม่สามารถติดตั้งได้. ตามข้อความที่บอกในหน้าจอ, ดูเหมือนมีปัญหากับ dependency ของแพ็กเกจที่อยู่ในระบบว่า mplayer-mencoder ต้องการแพ็กเกจ lame แต่ไม่มีอยู่ในระบบ. อันนี้ไม่รู้ว่าเป็นบั๊กหรือเปล่าเพราะผมก็ติดตั้งลินุกซ์ทะเล 7.0 เลือกทั้งหมด.

ไม่เป็นไร, ลองติดตั้งแพ็กเกจ lame ให้ซะเลย, น่าจะหาย.

# apt-get -s install lame
Reading Package Lists... Done
Building Dependency Tree... Done
The following NEW packages will be installed:
  lame
0 upgraded, 1 newly installed, 0 removed and 3 not upgraded.
Inst lame (3.96.1-2.kit Unknown:Unknown/Unknown)
Conf lame (3.96.1-2.kit Unknown:Unknown/Unknown)
# apt-get install lame
Reading Package Lists... Done
Building Dependency Tree... Done
The following NEW packages will be installed:
  lame
0 upgraded, 1 newly installed, 0 removed and 3 not upgraded.
Need to get 605kB of archives.
After unpacking 1665kB of additional disk space will be used.
Get:1 ftp://ftp.kitty.in.th pub/kitty/7.0/main lame 3.96.1-2.kit [605kB]
Fetched 605kB in 12s (46.7kB/s)
Committing changes...
Preparing...                ########################################### [100%]
   1:lame                   ########################################### [100%]
Done.
หลังจากติดตั้งแพ็กเกจ lame แล้วก็ลงแพ็กเกจ xmms-mp3 และ flash-plugin ได้โดยไม่มีข้อผิดพลาดที่เกิดขึ้นเมื่อครู่.
# apt-get -s install xmms-mp3
Reading Package Lists... Done
Building Dependency Tree... Done
The following NEW packages will be installed:
  xmms-mp3
0 upgraded, 1 newly installed, 0 removed and 3 not upgraded.
Inst xmms-mp3 (1:1.2.10-7.kit Unknown:Unknown/Unknown)
Conf xmms-mp3 (1:1.2.10-7.kit Unknown:Unknown/Unknown)
# apt-get install xmms-mp3 flash-plugin

วันศุกร์ที่ 10 ธันวาคม พ.ศ. 2547

เชลล์สคริปต์เบื้องต้น ตอนที่ 9 - ตอนจบ

  1. เชลล์สคริปต์เบื้องต้น - สร้างและรันเชลล์สคริปต์
  2. ตัวแปร
  3. ตัวแปรสภาพแวดล้อม
  4. ตัวแปรพิเศษ
  5. ตัวแปรแถวลำดับ (array)
  6. เงื่อนไข if
  7. วงวน for
  8. วงวน while, ฟังก์ชัน
  9. ตอนจบ

ครั้งก่อนได้แนะนำการสร้างฟังก์ชันและวงวน while ไปเรียบร้อยแล้ว. ครั้งนี้เขียนเกี่ยวกับเชลล์สคริปต์เป็นครั้งสุดท้าย. เชลล์สคริปต์ที่เป็นตัวอย่างในวันนี้เป็นเชลล์จะใช้ฟังก์ชันที่สร้างไว้ในครั้งก่อนได้แก่ฟังก์ชันหาผลรวมของลำดับตั้งแต่ 1 ถึง N. เชลล์สคริปต์จะแสดงข้อความให้ผู้ใช้เติมตัวเลข, ตรวจสอบว่าสิ่งที่ผู้ใช้พิมพ์เข้ามาเป็นตัวเลขหรือไม่. ถ้าไม่ใช่ตัวเลขจำนวนเต็มก็จะให้ป้อนข้อมูลใหม่. หลังจากที่ได้ตัวเลขแล้วก็จะหาผลรวมตั้งแต่ 1 ถึงตัวเลขนั้น.

     1  #!/bin/sh
     2  function sum {
     3      s=0
     4      for i in `seq 1 $1`
     5          do
     6          s=$(($s+$i))
     7      done
     8      echo $s
     9  }
    10  while true
    11  do
    12      echo -n "ใส่ข้อมูลจำนวนเต็ม: "
    13      read input
    14      if egrep  '^[[:digit:]]+$' > /dev/null <<<$input && [ $input -ne 0 ]    
    15      then
    16          break
    17      fi
    18      echo -e "\033[31mต้องการจำนวนเต็มบวกที่มากกว่า 0 เท่านั้น.\033[0m"
    19  done
    20
    21  echo "1 + 2 + 3 + ... + $input = `sum $input`"
บรรทัดที่ 1 - 9 เป็นการนิยามฟังก์ชันชื่อ sum หาผลบวกของลำดับ 1 ถึงจำนวนเต็มบวกที่ต้องการ. รายละเอียดของฟังก์ชันนี้อธิบายไปในตอนที่แล้ว.

บรรทัดที่ 10 เป็นการเริ่มต้นวงวน while สำหรับรับข้อมูลนำเข้าจากคีย์บอร์ด. true (คำสั่งที่ตรงข้ามกับ true คือ false) เป็นคำสั่งที่ไม่ทำอะไร, ให้ค่าสถานะจบการทำงานเป็น 0. ในที่นี้ใช้ while true เพื่อให้สร้างวงวนไม่รู้จบ. ถ้าข้อมูลที่ผู้ใช้ใส่ให้สคริปต์เป็นเลขจำนวนเต็มมากกว่า 0 ก็จะออกจากวงวนโดยใช้คำสั่ง break, ถ้าไม่ใช่จำนวนเต็มก็จะถามวนไปเรื่อยๆจนกว่าจะป้อนข้อมูลที่ถูกต้อง.

บรรทัดที่ 12 เป็นการใช้คำสั่ง echo กับตัวเลือก -n ให้ไม่ต้องขึ้นบรรทัดใหม่. หลังจาก echo แล้วก็ใช้คำสั่ง read อ่านข้อมูลจากคีย์บอร์ดเก็บเข้าตัวแปรชื่อ input ในบรรทัดที่ 13.

ตรวจสอบข้อมูลด้วย egrep และ test

บรรทัดที่ 14 อาจจะเรียกได้ว่าเป็นบรรทัดที่ซับซ้อนที่สุดในสคริปต์. แต่ดูให้ดีแล้วจะรู้ว่าไม่ยากเลย. มีเงื่อนไข 2 อย่างเชื่อมด้วย && (and) ในคำสั่ง if ได้แก่
egrep  '^[[:digit:]]+$' > /dev/null <<<$input
เราใช้่คำสั่ง egrep เพื่อเช็คว่าข้อมูลที่อยู่ในตัวแปร input เป็นเลขจำนวนเต็มหรือไม่. คำสั่ง egrep เป็นคำสั่งตระกูลเดียวกับ grep, "e" หมายถึง extension สามารถใช้ regular expression เช่น "[", "]" ได้ซึ่งคำสั่ง grep ธรรมดาจะถือว่าอักษร "[", "]" เป็นอักษรธรรมดา. "^" หมายถึงต้นบรรทัด, "[[:digit:]]" หมายถึงตัวเลขใดๆตั้งแต่ 0 - 9. "+" หมายถึงมีตัวเลขอย่างน้อยหนึ่งตัวเรียงต่อกันไป. และ "$" หมายถึงจบบรรทัด.

regular expression ที่คล้ายกับ "[[:digit:]]" ยังมี

[[:alnum:]]ตัวเลขและตัวอักษร
[[:alpha:]]ตัวอักษรภาษาอังกฤษ
[[:blank:]]ช่องว่าง
[[:lower:]]ตัวอักษรภาษาอังกฤษตัวเล็ก
[[:upper:]]ตัวอักษรภาษาอังกฤษตัวใหญ่
[[:punct:]]เครื่องหมายต่างๆ

คำสั่ง egrep โดยปรกติจำใช้หาคำที่อยู่ในไฟล์โดยให้ชื่อไฟล์เป็นอาร์กิวเมนต์. แต่ในตัวอย่างจะหาคำ (เช็คคำ) ที่อยู่ในตัวแปร input โดยใช้วิธี redirection. เครื่องหมาย "<<<" เป็นการนำตัวแปรเข้าไปใน standard input เพื่อส่งต่อให้คำสั่ง egrep. มีผลเหมือนกับ egrep อ่านข้อมูลจากไฟล์.

คำสั่ง egrep จะแสดงผลสิ่งที่ค้นหาได้ทางหน้าจอ. เพื่อที่จะไม่แสดงผลดังกล่างจึง redirect ผลลัพธ์ของ egrep โดยใช้เครื่องหมาย ">" ไปที่ /dev/null. คือไม่ให้แสดงผลทางหน้าจอ.

เงื่อนไขที่สองต้องตรวจสอบดูว่าข้อมูลที่อยู่ในตัวแปร input มีค่ามากกว่า 0 หรือไม่. กรณีนี้ตรวจสอบว่าค่าของตัวแปรไม่เท่ากับ 0 ก็เพียงพอแล้ว.

[ $input -ne 0 ]
ตัวเลือก -ne หมายถึง not equal, เทียบค่าตัวแปร $input กับ 0. ถ้าเงื่อนไขทั้งสองเป็นจริงก็จะสั่งคำสั่ง break ในบรรทัดที่ 16 เพื่อออกจากวงวน while.

สีในเทอร์มินอล

บรรทัดที่ 18 จะใช้ echo แสดงข้อความให้ผู้ใช้ป้อนข้อมูลใหม่โดยแสดงข้อความเป็นสีแดง. การแสดงข้อความเป็นสีเป็นความสามารถของเทอร์มินอล. คำสั่ง echo จะใช้ตัวเลือก -e ให้สามารถใช้เครื่องหมาย "\" แทนอักขระต่างๆได้. เช่น "\0101" แสดงตัวอักษร "A". "\033" หมายถึงอักขระ ESCAPE ในชุดอักขระ ASCII. ใช้สำหรับควบคุมเทอร์มินอล.

"[31m" เป็นการเซ็ตสีของตัวอักษรให้เป็นสีแดง. ส่วน "[0m" เป็นการเซ็ตตัวอักษรให้กลับเป็นปรกติ. สีที่ใช้ได้ในเทอร์มินอลจะใช้รหัสที่เรียกว่าเรียกว่า ANSI color code.

รหัสความหมาย
[0mเซ็ตกลับให้เป็นปรกติ
[1mตัวหนา
[3mตัวเอียง
[4mขีดเส้นใต้
[7mกลับสีของฉากหลังและตัวอักษร
[30mเซ็ตสีตัวอักษรให้เป็นสีดำ
[31mเซ็ตสีตัวอักษรให้เป็นสีแดง
[32mเซ็ตสีตัวอักษรให้เป็นสีเขียว
[33mเซ็ตสีตัวอักษรให้เป็นสีเหลือง
[34mเซ็ตสีตัวอักษรให้เป็นสีฟ้า
[35mเซ็ตสีตัวอักษรให้เป็นสีม่วงแดง (magenta)
[36mเซ็ตสีตัวอักษรให้เป็นสีฟ้าอมเขียว (cyan)
[37mเซ็ตสีตัวอักษรให้เป็นสีขาว
[39mเซ็ตสีของฉากหลังให้เป็นสีปริยาย (สีขาว)
[40mเซ็ตสีของฉากหลังให้เป็นสีดำ
[41mเซ็ตสีของฉากหลังให้เป็นสีแดง
[42mเซ็ตสีของฉากหลังให้เป็นสีเขียว
[43mเซ็ตสีของฉากหลังให้เป็นสีเหลือง
[44mเซ็ตสีของฉากหลังให้เป็นสีน้ำเงิน
[45mเซ็ตสีของฉากหลังให้เป็นสีม่วงแดง
[46mเซ็ตสีของฉากหลังให้เป็นสีฟ้าแกมเขียว

บรรทัดที่ 21 เป็นการแสดงผลลัพธ์และเรียกใช้ฟังก์ชัน sum.

ต่อจากนี้

เว็บล็อกนี้เป็นบันทึกสุดท้ายที่เกี่ยวกับเชลล์สคริปต์เบื้องต้น. สำหรับผู้ที่สนใจเรียนรู้เชลล์สคริปต์อย่างจริงๆสามารถอ่านเพิ่มเติมได้จาก. และก็ใช้บรรทัดคำสั่งให้มากจะได้รู้จักคำสั่ง, โปรแกรมคำสั่งต่างๆมากขึ้นครับ.

สร้างแผ่น DVD LinuxTLE 7.0 จากไฟล์ .iso

หลังจากที่ประกาศการออกตัวของลินุกซ์ทะเล รุ่น 7.0 ที่มีชื่อรหัสพัฒนาว่า "หว้ากอ" ไปแล้วเมื่อวาน. ตอนนี้เท่าที่ทราบมี mirror แล้วอยู่ 3 แห่ง

เพิ่มเติม: ดูที่ดาว์นโหลด mirror ล่าสุดได้จาก opentle.org. และวิธีการเขียน CD ก็ดูจากที่เดียวกันครับ.

ตั้งแต่เมื่อวานดาว์นโหลดได้อิมเมจแบบ DVD มาจาก nectec กับเกษตร (เร็วดี). ว่าแล้วก็ใช้ prozilla (download accelerator) ที่มีคุณเทพแนะนำตอบคำถามที่มีคนถามมาว่ามี ftp client ไหนที่เหมือน flashget ที่ดาว์นโหลดหลายๆเซสชั่นได้.

ดาว์นโหลดเสร็จก็ใช้คำสั่ง md5sum ตรวจสอบดูก่อนว่าไฟล์ที่ดาว์นโหลดมาถูกต้องหรือไม่.

$ md5sum waghor-7.0-i386-bin-DVD.iso
29694948005e36b1c3f56e0867555d35  waghor-7.0-i386-bin-DVD.iso
เอาเลขผลลัพธ์ของคำสั่งไปเทียบกับเลขที่เขียนไว้ในไฟล์ MD5SUMS. หรือถ้ามีไฟล์ MD5SUMS ซึ่งเป็นไฟล์เท็กซ์ดาว์นโหลดมาด้วยก็สั่งคำสั่งแบบนี้ครับ.
$ md5sum -c MD5SUMS
md5sum -c MD5SUMS
waghor-7.0-i386-bin-DVD.iso: OK
md5sum: waghor-7.0-i386-cd1.iso: No such file or directory
waghor-7.0-i386-cd1.iso: FAILED open or read
md5sum: waghor-7.0-i386-cd2.iso: No such file or directory
waghor-7.0-i386-cd2.iso: FAILED open or read
md5sum: waghor-7.0-i386-cd3.iso: No such file or directory
waghor-7.0-i386-cd3.iso: FAILED open or read
md5sum: waghor-7.0-src-cd4.iso: No such file or directory
waghor-7.0-src-cd4.iso: FAILED open or read
md5sum: waghor-7.0-src-cd5.iso: No such file or directory
waghor-7.0-src-cd5.iso: FAILED open or read
md5sum: waghor-7.0-src-cd6.iso: No such file or directory
waghor-7.0-src-cd6.iso: FAILED open or read
md5sum: waghor-7.0-src-cd7.iso: No such file or directory
waghor-7.0-src-cd7.iso: FAILED open or read
md5sum: WARNING: 7 of 8 listed files could not be read
เพราะว่าผมไม่ได้ดาว์นโหลดไฟล์ CD ที่เหลือ, คำสั่ง md5sum เลยเกิด error แต่ไม่เป็นไรเพราะเช็คข้อมูลอิมเมจของ DVD ว่าถูกต้อง (OK). เสร็จแล้วก็ใช้ growisofs ย่างแผ่น DVD จากเครื่อง Gentoo.
# growisofs -dvd-compat -Z /dev/cdroms/cdrom0=waghor-7.0-i386-bin-DVD.iso
Executing 'builtin_dd if=waghor-7.0-i386-bin-DVD.iso of=/dev/cdroms/cdrom0 obs=32k seek=0'
/dev/cdroms/cdrom0: "Current Write Speed" is 4.0x1385KBps.
         0/2109435904 ( 0.0%) @0x, remaining ??:??
         0/2109435904 ( 0.0%) @0x, remaining ??:??
   1638400/2109435904 ( 0.1%) @0.3x, remaining 300:10
...
2081357824/2109435904 (98.7%) @4.0x, remaining 0:05
2100396032/2109435904 (99.6%) @4.0x, remaining 0:01
builtin_dd: 1030000*2KB out @ average 3.8x1385KBps
/dev/cdroms/cdrom0: flushing cache
/dev/cdroms/cdrom0: closing track
/dev/cdroms/cdrom0: closing disc
/dev/cdroms/cdrom0: reloading tray
ใช้เวลาประมาณ 7 นาทีครับ. แล้วจะเอาไปลงโน็ตบุค.

วันพฤหัสบดีที่ 9 ธันวาคม พ.ศ. 2547

เชลล์สคริปต์เบื้องต้น ตอนที่ 8 - วงวน while, ฟังก์ชัน

เขียนมาเป็นตอนที่ 8 แล้วขอสรุปตอนที่ผ่านๆมาเป็นสารบัญครับ.
  1. เชลล์สคริปต์เบื้องต้น - สร้างและรันเชลล์สคริปต์
  2. ตัวแปร
  3. ตัวแปรสภาพแวดล้อม
  4. ตัวแปรพิเศษ
  5. ตัวแปรแถวลำดับ (array)
  6. เงื่อนไข if
  7. วงวน for
  8. วงวน while, ฟังก์ชัน
  9. ตอนจบ

ตอนนี้จะแนะวงวน while ซึ่งเป็นวงวนที่มีใช้ในภาษาคอมพิวเตอร์ทั่วไปมีรูปแบบการใช้งานดังนี้.
while command
do
    command
    ...
done
หรือจะเขียนเป็นบรรทัดเดียวก็ได้
while command; do command; ...; done
คำสั่งที่อยู่หลัง while มักจะเป็นคำสั่ง test (อย่าลืมว่า [ ก็คือคำสั่ง test) หรือจะเป็นคำสั่งอะไรก็ได้. เราลองมาดูตัวอย่างการคำนวณผลบวกของอนุกรม 1, 2, 3, ..., n สามารถเขียนในบรรทัดคำสั่งได้ดังนี้.
$ sum=0; n=10; i=1
$  while [ $i -le $n ]
> do
> sum=$(($sum+$i))
> ((i++))
> done
$ echo $sum
55
ตัวอย่างเป็นการหาผลบวกของ 1 ถึง 10. ตอนแรกสร้างตัวแปร sum, n และ i ให้มีค่าเป็น 0, 10 และ 1. ต่อจากนั้นก็เข้าวงวน while โดยมีเงื่อนไขว่า $i น้อยกว่าหรือเท่ากับ $n. -le เป็นตัวเลือกของคำสั่ง test หมายถึง less or equal than.

เนื้อหาของวงวนเป็นการบวกค่า sum ไปเรื่อยๆและเพิ่มค่า i ก่อนออกจากวงวนแต่ละรอบ. เมื่อคำนวณเสร็จแล้วก็จะได้ผลลัพธ์เก็บไว้ในตัวแปร sum.

ทำนองเดียวกันถ้าจะใช้วงวน for เขียนแทนก็สามารถเขียนได้แบบนี้ (หนึ่งในหลายแบบ)

$ sum=0; for i in `seq 1 10`; do sum=$(($sum+$i)); done
$ echo $sum
55
ในกรณีนี้จะใช้ผลลัพธ์ของคำสั่ง seq ซึ่งจะแสดงค่าตั้งแต่ 1 ถึง 10.
$ seq 1 10
1
2
3
4
5
6
7
8
9
10

ฟังก์ชัน

เราสามารถสร้างฟังก์ชันในเชลล์ด้วยคำสั่ง function มีรูปแบบดังนี้.
function name {
   command
   ...
}
คำที่อยู่หลัง function เป็นชื่อของฟังก์ชันที่ตั้งเองได้. ฟังก์ชันสามารถรับอาร์กิวเมนต์ได้โดยอ้างอิงตัวแปรตำแหน่ง $1, $2, $3 ... หมายถึงอาร์กิวเมนต์ตัวที่ 1, 2, 3 ฯลฯ.

ตัวอย่างต่อไปนี้เป็นการสร้างฟังก์ชันหาผลลัพธ์ของผลบวกตั้งแต่ 1 ถึงจำนวนเต็มที่ต้องการโดยให้จำนวนเต็มที่ต้องการเป็นอาร์กิวเมนต์ของฟังก์ชัน.

#!/bin/sh

function sum {
    s=0
    for i in `seq 1 $1`
        do
        s=$(($s+$i))
    done
    echo $s
}

sum 100
หลังจากที่นิยามฟังก์ชันแล้วก็สามารถใช้ชื่อฟังก์ชันนั้นเป็นคำสั่งเหมือนคำสั่งอื่นๆ. ในความเป็นจริงแล้วเราสามารถละเว้นคำว่า function จะไม่เขียนก็ได้. สมมติว่าไฟล์นี้ชื่อ sum.sh, เมื่อรันแล้วจะได้ผลลัพธ์เป็น 5050.
$ ./sum.sh
5050

การกำหนดฟังก์ชันมีประโยชน์สามารถนิยามการกระทำต่างๆไว้ได้ในไฟล์ ~/.bashrc หรือ ~/.bash_profile. เมื่อล็อกอินหรือใช้เชลล์จากเทอร์มินอล, ไฟล์เหล่านี้จะถูกอ่านเข้าไปในเชลล์, ซึ่งจะเป็นการนิยามฟังก์ชันที่เตรียมไว้. หลังจากนั้นจะเรียกใช้เมื่อไหร่ก็ได้.

วันพุธที่ 8 ธันวาคม พ.ศ. 2547

เห็นภาพประกอบแล้วตลกดี

ดูโน่นดูนี่มาไปเจอเว็บพูดถึงสองเรื่องเกี่ยวกับการใช้คำสั่งในเชลล์ได้แก่ ดูภาพประกอบในนั้นเป็นภาพเอาจากหนังจีนไหนไม่รู้. ดูแล้วตลกดีครับ. ผมว่าเหมาะกับชื่อ Useless use of ... ให้ความรู้สึกว่า "อย่าทำอย่างนี้นะ".

สรุปสั้น

  • อย่าใช้ cat แบบนี้
    $ cat file | wc -l
    
    เพราะ wc อ่านข้อมูลจากไฟล์ได้เลย.
  • อย่าใช้ kill แบบนี้
    $ kill -9 2345
    
    เพราะ SIGKILL อันตรายเกินไป, ควรใช้เมื่อจำเป็นเท่านั้น.
ข้อมูลเพิ่มเติมดูจากลิงค์ข้างบนครับ.

เชลล์สคริปต์เบื้องต้น ตอนที่ 7 - วงวน for

สารบัญ

  1. เชลล์สคริปต์เบื้องต้น - สร้างและรันเชลล์สคริปต์
  2. ตัวแปร
  3. ตัวแปรสภาพแวดล้อม
  4. ตัวแปรพิเศษ
  5. ตัวแปรแถวลำดับ (array)
  6. เงื่อนไข if
  7. วงวน for
  8. วงวน while, ฟังก์ชัน
  9. ตอนจบ

วงวนที่ใช้บ่อยในเชลล์คงจะหนีไม่พ้น for. จริงๆแล้ว for ก็เป็นคำสั่ง (ประกอบภายใน) เหมือนกับคำสั่งทั่วๆไปแต่จะเรียกคำสั่งแบบนี้ว่า compound command. ไวยกรณ์ของคำสั่งได้แก่
for name in words 
do
    command
    ...
done
เนื่องจากคำสั่งต่างๆที่สามารถใช้ ; คั่นแทนการขึ้นบรรทัดได้, ถ้าจะเขียนเป็นบรรทัดเดียวก็จะเป็นแบบนี้.
for name in words; do commands; ...; done
ตัวอย่างต่อไปนี้เป็นการแปลงไฟล์ .gif ทุกไฟล์ที่อยู่ในไดเรกทอรีที่ทำงานอยู่ให้เป็น .png ด้วยบรรทัดคำสั่งบรรทัดเดียว.
$ for i in *gif; do convert $i `basename $i .gif`.png; done
ในวงวนแต่และรอบจะมีตัวแปร i ที่กำหนดไว้ในช่วงต้นของคำสั่ง (for i in *gif) และค่าของตัวแปรนี้จะนำมาจากรายการคำที่อยู่หลังคำว่า in. ในที่นี้สมมติว่าในไดเรกทอรีที่ทำงานอยู่มีไฟล์ a.gif, b.gif, c.gif, ค่าของตัวแปร i ในแต่ละรอบก็เปลี่ยนไปเรื่อยๆจาก a.gif, b.gif, ...

basename เป็นคำสั่งสำหรับสกัดเอาชื่อไฟล์ไฟล์โดยตัดส่วนขยายชื่อไฟล์ (file extension) .gif ออก. เช่นถ้าชื่อไฟล์เป็น a.gif, basename a.gif .gif ก็จะให้ผลเป็น a. และในตัวอย่างเติม .png เข้าไป. นอกจากจะใช้สกัดเอาส่วนที่เป็นชื่อไฟล์ที่ไม่มีส่วนขยายชื่อไฟล์แล้ว, โดยปรกติจะใช้ตัดส่วนที่เป็นไดเรกทอรีที่อยู่หน้าชื่อไฟล์ออกไป. เช่น /usr/bin/perl ถ้าเอาไปเป็นอาร์กิวเมนต์ของคำสั่ง basename ก็จะได้ผลเป็น perl.

คำสั่ง convert เป็นคำสั่งหนึ่งในแพกเกจ ImageMagick ใช้สำหรับเปลี่ยนรูปจากฟอร์แมตหนึ่งไปเป็นอีกฟอร์แมตหนึ่ง. คำสั่งนี้จะใช้ส่วนขยายชื่อไฟล์เช่น .png ประกอบว่าการพิจารณาว่าเราต้องการเปลี่ยนจาก .gif ไปเป็นฟอร์แมตอะไร. ถ้าต้องการเปลี่ยนเป็นฟอร์แมต .jpg ก็แค่เปลี่ยนอาร์กิวเมนต์ของคำสั่งให้มีส่วนขยายชื่อไฟล์เป็น .jpg. นอกจากคำสั่ง convert ที่ใช้แปลงฟอร์แมตรูปจากบรรทัดคำสั่งแล้ว, ยังมีคำสั่งอื่นๆสำหรับหมุนรูป, สร้างรูป ฯลฯ ด้วย.

ถ้ารู้สึกว่าเขียนคำสั่งบรรทัดเดียวแล้วอาจจะงง, จะเขียนเป็นหลายบรรทัดก็ได้.

$ for i in *gif
> do convert $i `basename $i .jpg`.png
> done
ในกรณีที่เป็นวงวนซับซ้อนจะดูเข้าใจง่ายกว่าบรรทัดเดียว. เครื่องหมาย ">" คือพรอมต์รอง (secondary prompt) โดยปริยายซึ่งสามารถตั้งค่าให้เป็นตัวอักษรอื่นๆได้ถ้าไม่ชอบด้วยตัวแปรสภาพแวดล้อม $PS2.

วันนี้ขอจบเท่านี้แล้วกันครับ.

วันอังคารที่ 7 ธันวาคม พ.ศ. 2547

โฉมหน้าใหม่ของ Google Group (Beta)

เพิ่งสังเกตเห็นวันนี้ว่า Google group เปลี่ยนโฉมหน้าไปจากเดิม. อ่านจาก What's New เขาบอกว่า
  • เข้าร่วมหรือสร้าง group ขึ้นเองได้. อันนี้คงเหมือนกับ Yahoo groups (?).
  • ติดตามหัวข้อที่สนใจอยู่ได้โดยการสร้างเครื่องหมายดาวไปแปะติดไว้.
งานนี้ไม่รู้ว่า Yahoo group จะว่าอย่างไรที่ได้คู่แข่งตัวฉกาด. ตามที่ดูถ้าสร้าง group ใหม่เช่น tlwg ก็จะได้อีเมลล์เป็น tlwg@googlegroups.com และมี URL ให้ด้วยเป็น groups-beta.google.com/tlwg. ส่วนที่เป็น newsgroup อยู่แล้วจะไปอยู่ใต้ groups-beta.google.com/group/[ชื่อนิวส์กรุป].


หมายเหตุ: รูปจาก groups-beta.google.com

มีอินเทอร์เฟสเวลาอ่านคล้าย gmail เลย.


หมายเหตุ: รูปจาก groups-beta.google.com

ว่าไปแล้วคุ้นๆเหมือนกับขยายจาก gmail มาเป็นบริการใหม่. TLWG สนใจย้าย mailing จาก Yahoo ไป Google Group ไหมเนี่ย.

สุดท้ายเอาหน้าจอของ th.pubnet.linux ซึ่งก็คือเว็บบอร์ดของ TLWG มาให้ดูว่าเป็นแบบนี้ครับ.

มี atom feed ด้วย

เชลล์สคริปต์เบื้องต้น ตอนที่ 6 - เงื่อนไข if

สารบัญ

  1. เชลล์สคริปต์เบื้องต้น - สร้างและรันเชลล์สคริปต์
  2. ตัวแปร
  3. ตัวแปรสภาพแวดล้อม
  4. ตัวแปรพิเศษ
  5. ตัวแปรแถวลำดับ (array)
  6. เงื่อนไข if
  7. วงวน for
  8. วงวน while, ฟังก์ชัน
  9. ตอนจบ

หลังจากที่เรียนรู้ตัวแปรแล้วก็เข้าเรื่องการใช้เงื่อนไข if. คำสั่งสำหรับสร้างเงื่อนไขมีรูปแบบดังนี้
if command
then
    command
    ...
elif command
then
    command
    ...
else
    command
    ...
fi
command ที่อยู่หลัง if หรือ elif มักจะเป็นคำสั่งสำหรับตรวจสอบเช่นตรวจสอบตัวแปรว่ามีการตั้งค่าหรือไม่, ตรวจสอบไฟล์ว่ามีจริงหรือไม่ เป็นต้น. ส่วน command ที่อยู่หลัง then หรือ else คือคำสั่งเมื่อเงื่อนไขเป็นจริงหรือไม่จริงแล้วแต่กรณี.

คำสั่งที่ใช้ตรวจสอบใช้ร่วมกับ if คือคำสั่ง test. เรามาดูตัวอย่างเลยดีกว่า.

$ if test `whoami` = root
> then
> echo Yes, you are root.
> else
> echo No, your are `whoami`.
> fi
No, you are poonlap.
ให้สังเกตว่าก่อนและหน้าเครื่องหมาย = มีข่องว่างอยู่, หมายความว่าอาร์กิวเมนต์ของคำสั่ง test ในกรณีนี้มีอยู่สามตัวคือ "`whoami`", "=" และ "root". whoami เป็นคำสั่งแสดงชื่อล็อกอินของผู้ใช้ในปัจจุบัน. ดังนั้น `whoami` จะถูกแทนที่ด้วยผลลัพธ์เวลาสั่งคำสั่ง. ส่วน "=" เป็นอาร์กิวเมนต์ของคำสั่ง test ใช้เปรียบเทียบคำที่อยู่ข้างหน้าและหลังว่าเหมือนกันหรือไม่. ถ้าเราลองสั่งคำสั่ง
$ test poonlap = root
จะเห็นว่าไม่มีอะไรเกิดขึ้น. แต่จริงๆแล้วคำสั่ง test ส่งสถานะการจบการทำงานให้เชลล์รับรู้ว่า poonlap = root นั้นเป็นเท็จ. ค่าจบสถานะการทำงานดูได้จากตัวแปรพิเศษ $?. เชลล์ใช้ค่านี้ดูว่าจริงหรือเท็จ. ถ้าเป็น 0 จะหมายถึงคำสั่งจบการทำงานถูกต้อง, เป็นจริง. ถ้าเป็นค่าอื่นๆที่ไม่ใช่ 0 จะหมายถึงจบการทำงานอย่างไม่ถูกต้อง, คือเป็นเท็จ. ดังนั้นถ้า test `whoami` = root เป็นจริงก็จะรันคำสั่งที่อยู่หลัง then ถ้าเป็นเท็จก็จะรันคำสั่งที่อยู่หลัง else. ส่วนคำว่า fi เป็นการบอกเชลล์ว่าจบคำสั่ง if.

คำสั่ง test เวลาใช้กับ if จะดูพิกลไม่เหมือนภาษาคอมพิวเตอร์, เราสามารถเขียนสคริปต์ที่ให้ผลเหมือนกันที่ดูเข้าท่ากว่าได้แบบนี้.

if [ `whoami` = root ]
then
echo Yes, you are root.
else
echo No, your are `whoami`.
fi
No, you are poonlap.
จริงๆแล้ว "[" ก็คือคำสั่ง test แต่เป็นคำสั่งพิเศษคืออาร์กิวเมนต์ตัดสุดท้ายต้องเป็น "]" จะได้ดูเหมือนภาษาคอมพิวเตอร์ทั่วไป. คำสั่ง test สามารถตรวจสอบอะไรได้หลายอย่างเช่น ไฟล์มีตัวตนจริงหรือไม่ (-f), ตัวแปรที่ระบุมีค่าหรือไม่ (-z) ฯลฯ. ให้อ่านรายละเอียดจาก man test เองแล้วกันครับ.

วันจันทร์ที่ 6 ธันวาคม พ.ศ. 2547

หนังสือ (คอมฯ) ที่มีอิทธิพลต่อข้าพเจ้า - ตอนที่ 2 - tcl/tk

จำได้ว่าตอนเรียนอยู่ปีสามหรือปีสี่, อยากเขียนโปรแกรมบนยูนิกซ์แบบ GUI แต่จะให้เขียนด้วย C ในตอนนั้นก็ไม่ไหวคงต้องใช้เวลาเยอะพอสมควร. เผอิญตอนนั้นไปรู้จัก Tcl/Tk ได้อย่างไรจำไม่ได้. แต่จำได้ว่าใช้ง่ายเพราะเป็นสคริปต์และมี GUI ประกอบด้วย. ภาษา Tcl/Tk (ทิคเกิ้ลทีเค) แยกเป็นสองส่วนคือ Tcl คล้ายกับเชลล์เป็นตัวแปลคำสั่ง. โปรแกรมแปรคำสั่ง (ภาษา) คือโปรแกรม tclsh. ส่วนที่ใช้สร้าง GUI ได้เรียว่า Tk (Toolkit) จะใช้ตัวแปรภาษาชื่อ wish. หนังสือที่อ่านตอนนั้นมีสองเล่มคือ

  • Graphical Applications with Tcl & TK โดย Eric Foster-Johnson เล่มนี้เขียนดีอ่านเข้าใจง่าย. เลยชอบหนังสือที่ Eric Foster-Johnson เขียนตั้งแต่นั้นเป็นต้นมา. แนวคิดเบื้องต้นเกี่ยวกับ GUI กับ regular expression ได้มาจากหนังสือเล่มนี้.
  • Practical Programming in Tcl and Tk โดย Brent Welch. เป็นหนังสือเล่มที่สองเกี่ยวกับ Tcl/Tk ที่อ่าน, ลึกซึ้งกว่าเล่มแรก. ตอนที่อ่านเป็น second edition ตอนนี้เป็น fourth edition แล้วแถมมีคนช่วยเขียนเพิ่มอีก. นาย Brent Welch นี่เป็นลูกศิษย์ของ John K. Ousterhout ซึ่งเป็นผู้สร้าง Tcl/Tk สมัยที่อยู่มหาวิทยาลัย U.C. Berkeley. จากหนังสือเล่มนี้ทำให้รู้จักการ binding ภาษาต่างๆเข้าด้วยกัน. เช่นสร้างคำสั่งใน Tcl/Tk ใหม่จากภาษาซี (เขียนภาษาซีแล้วฝังคำสั่งใหม่ที่ implement ด้วยภาษาซีในตัวแปรคำสั่ง tcl).
ตัวอย่างง่ายๆของ Tcl/Tk เป็นแบบนี้ครับ.
#!/usr/bin/wish
button .b -text "Hello world" -command exit
pack .b
รันสคริปต์นี้แล้วได้ผลเป็นแบบนี้ครับ.

เทียบกับเขียนด้วย Qt ที่แนะนำไปแล้ว, เป็นสคริปต์ง่ายกว่าเยอะ. ผมใช้ Tcl/Tk เขียนงานวิทยานิพนธ์ตอนปีสี่และใช้เขียนโปรแกรมง่ายๆเปลี่ยนคีย์บอร์ดภาษาไทยกับเป็นบรรณาธิกรณ์สมัยที่ คีย์บอร์ดภาษาไทยกับ xkb ยังไม่เฟื่องฟูเหมือนปัจจุบัน. โปรแกรมชื่อ xzthai, ล้าสมัยไปแล้ว.

เมื่อครู่อ่าน slashdot (RAD, Rapid Application Development) เห็นมีเขียนถึง Qt binding ด้วย Ruby ก็ไม่เลวครับ. น่าใช้ยิ่ง.

หมายเหตุ: รูปหน้าปกหนังสือจาก amazon.com

ตัดคำในบล็อกด้วยโค้ด thaiwrap

อาทิตย์ก่อนคุณ bact' เขียนโค้ด javascript สำหรับตัดคำในโฮมเพจชื่อ ThaiWrap Bookmarklet ไว้. วันนี้เลยเอามาเป็นลิงค์ในบล็อกนี้. ถ้าเป็นบราวเซอร์ที่ตัดคำไม่ได้ก็ลองคลิ้กลิงค์ที่อยู่ทางขวามือดูครับ.

GTK+ input - เขียนอักขระด้วยตัวเลข

วันนี้อ่านเมลลิ่งลิสของ gtk+ เพิ่งรู้ว่าโปรแกรมที่ใช้ไลบรารี gtk+ สามารถเขียนอักขระอะไรก็ได้ด้วยตัวเลขค่ายูนิโค้ด (unicode) ของอักขระ.

การป้อนข้อมูลทำได้โดยกดคีย์ Ctrl+Shift+Alt พร้อมๆกันแล้วพิมพ์เลขค่ายูนิโค้ดที่ต้องการ. เช่นตัวอักษรไทย "ข" มีค่าเป็น 0x0E02 ก็พิมพ์ "e02" ในโปรแกรมที่ใช้ gtk+ (ในรูปคือ gedit). ตัวเลขฐานสิบหกที่พิมพ์จะมีเส้นใต้กำกับ. หลังจากที่ปล่อยคีย์ Ctrl+Shift+Alt เรียบร้อยแล้ว, โปรแกรมก็จะเปลี่ยนเป็นอักขระที่มีค่ายูนิโค้ดนั้น.

ทดสอบกับ gedit ที่รันในสภาพแวดล้อม locale ภาษาไทยใช้วิธีนี้ไม่ได้. อีกอย่างหนึ่งคือขึ้นอยู่กับโปรแกรมด้วย, ถ้าโปรแกรมดักคีย์พวกนั้นก่อน gtk+ ก็ใช้ไม่ได้.

วันศุกร์ที่ 3 ธันวาคม พ.ศ. 2547

เชลล์สคริปต์เบื้องต้น ตอนที่ 5 - ตัวแปรแถวลำดับ, +-*/

สารบัญ

  1. เชลล์สคริปต์เบื้องต้น - สร้างและรันเชลล์สคริปต์
  2. ตัวแปร
  3. ตัวแปรสภาพแวดล้อม
  4. ตัวแปรพิเศษ
  5. ตัวแปรแถวลำดับ (array)
  6. เงื่อนไข if
  7. วงวน for
  8. วงวน while, ฟังก์ชัน
  9. ตอนจบ

ไม่ได้เขียนหลายวัน, ยังไม่ลืมว่าจะเขียนคอลัมน์นี้ให้จบ.

ตัวแปรแถวลำดับ (array variable)

ตัวแปรแถวลำดับ (array) ที่ใช้ในเชลล์สร้างด้วยไวยกรณ์
name[index]=value
index เป็นตัวเลขเริ่มตั้งแต่ 0 เหมือนตัวแปรแถวลำดับของภาษาอื่นๆ. แต่เวลาสร้างจะข้ามไปก็ได้เช่น
$ arr[1]=var1
ในกรณีนี้ข้ามนิยามตัวแปรแถวลำดับตัวแรก (arr[0]) ไปนิยามตัวที่สองเลย. เวลาอ้างอิงค่าที่อยู่ในตัวแปรให้ใช้เครื่องหมายปีกกาช่วยจับกลุ่มดังนี้.
$ echo ${arr[0]} ${arr[1]} ${arr[2]}
var1
ตัวแปรที่ไม่ได้นิยามไว้เช่น arr[0], arr[2] ก็จะไม่แสดงอะไรและไม่มี error ด้วย. วิธีการสร้างตัวแปรแถวลำดับและตั้งค่าไปในตัวมีไวยกรณ์ดังนี้
name=(value1 ... valuen)
ตัวอย่างเช่น
$ day=(อาทิตย์ จันทร์ อังคาร พุธ พฤหัสบดี ศุกร์ เสาร์)
$ echo วันนี้เป็นวัน${day[`date +%w`]}
วันนี้เป็นวันศุกร์

นึกตัวอย่างการใช้ตัวแปรลำดับในเชลล์ดีๆไม่ออกเพราะไม่ค่อยได้ใช้ครับ.

การแทนค่าคำสั่ง

ในตัวอย่าง
echo วันนี้เป็นวัน${day[`date +%w`]}
มีการนำผลลัพธ์ของคำสั่งมาแทนในตำแหน่งที่ต้องการโดยใช้เครื่องหมาย back quote (`). วิธีการนี้เรียกว่า command substitution. คำสั่ง date +%w จะเป็นการจัดรูปแบบการแสดงผลเลือกแสดงวันเวลาเฉพาะวันในสัปดาห์ (+%w) และแสดงเป็นตัวเลขโดยที่เริ่มจากวันอาทิตย์ให้เป็นเลข 0. และใช้ back quote เพื่อเอาค่า 0 นี้ไปใช้ใน index ของตัวแปรแถวลำดับต่อไป.

ปรกติจะนิยมใช้ back quote เพราะมีความเข้ากันได้กับเชลล์อื่นหรือเชลล์เก่าๆ. ใน bash มีไวยกรณ์ต่างหากที่ให้ผลเหมือนกับการใช้ back quote ด้วยได้แก่

$(command)
$ echo วันนี้เป็นวัน${day[$(date +%w)]}
วันนี้เป็นวันศุกร์
ไวยกรณ์ที่เกี่ยวข้องกับการแทนค่าแบบ $(command) ได้แก่การกระจายนิพจน์คณิตศาตร์ (Arithmetic Expansion) มีไวยกรณ์ดังนี้
$((expression))
เรามาดูตัวอย่างกันเลยเข้าใจง่ายกว่าว่าเอาไว้ทำอะไร.
$ echo $((2*2+3*3))
13
$ echo $((1/2))
0
การกระจายนิพจน์คณิตศาตร์เอาไว้ในสคริปต์ที่ต้องการคำนวณบวกลบคูณหารง่ายๆ. ให้สังเกตว่าใช้ได้แค่จำนวนเต็มเท่านั้น. ดังนั้นถ้าผลลัพธ์ของ 1 หาร 2 ได้ 0. ถ้าจะใช้คำนวณทศนิยมด้วยคงต้องใช้คำสั่ง bc แทนตัวอย่างเช่น.
$ echo `bc -l<<<'(1/2)'`
.50000000000000000000
คำสั่ง bc เป็นโปรแกรมเครื่องคิดเลขแบบบรรทัดคำสั่ง. สร้างฟังก์ชัน, คำนวณคณิตศาสตร์ชั้นสูงเช่นยกกำลัง, sine, tan ฯลฯ ได้ด้วย. ปรกติคำสั่ง bc จะรับข้อมูลจาก standard input เป็นแบบโต้ตอบ. ในกรณีนี้ใช้เทคนิคให้ bc อ่านข้อมูลจากคำที่อยู่หลังเครื่องหมาย "<<<" เหมือนกับอ่านข้อมูลจากไฟล์.

รายการ TV ญี่ปุ่นที่ดูประจำ

รายการ TV ที่ผมดูอยู่ประจำมีดังนี้ครับ.
  • えぐら開運動 ช่อง 12 TV Tokyo ทุกวันเสาร์ (ศุกร์กลางคืน) 0:09. เป็นรายการปรึกษาปัญหาชีวิตของคนทั่วไปโดย Ehara Hiroyuki เขาเป็นมีความสามารถพิเศษ, ภาษาไทยเรียกว่ามีตาทิพย์. มองเห็นอดีต, วิญญาณต่างๆ, อนาคตของคนที่ขอคำปรึกษา. ฟังดูแรกๆแล้วเหมือนกับหลอกลวง, จริงหรือเปล่าไม่น่าเชื่อถือ. แต่ยิ่งดูยิ่งนับถือ Ehara-san ว่าแกเป็นคนดีจริงๆ. แนะนำการดำเนินชีวิต, ชี้ทางสว่างให้กับคนที่มีปัญหาล้นอกล้นใจ, คนคิดสั้น, สามีมีชู้, ถูกผีเข้า (ส่วนใหญ่คิดไปเอง, อะไรๆก็โทษผีวิญญาณไปซะหมด. เขาบอกว่าคนน่ากลัวกว่าผีเพราะไม่รู้จิตใจที่แท้จริงว่าเป็นอย่างไร, ผีวิญญาณนี่คิดอย่างไรปฏิบัติอย่างนั้นไม่หลอกลวง). บางคนก็มาขอคำปรึกษาแต่ไม่น่าช่วยเหลือเลย (พวกทำตัวไม่ดีเอง). Ehara-san เขาบอกว่าทุกคนจะมีวิญญาณที่คุ้มครองตัวเองอยู่เสมอ (ภาษาอังกฤษคือ Guardian agel) ไม่ได้ช่วยทำโน่นทำนี่สร้างปาฏิหารให้เราหรอกเพียงแต่มีความห่วงใย, ให้ความรัก, คอยดูแลเราตลอดตั้งแต่เกิด. ในยามที่ถึงฆาตจริงๆก็อาจจะช่วยบ้าง. ถ้าคนมาปรึกษาสมควรที่จะให้คำแนะนำก็จะถ่ายทอดข้อความ (message) จากวิญญาณที่คุ้มครองคนนั้นให้ฟัง. สิบคนสิบปัญหา, แต่ละคนมีปัญหาครุ่นใจคนละอย่าง. ฟังคำแนะนำของแล้วถึงไม่เกี่ยวกับตัวเองแต่ก็เป็นข้อคิดที่ดีครับ.
  • 食彩の王国 ช่อง 10 Asahi TV ทุกเช้า 9:55 เป็นรายการสารคดีเสนอวัตถุดิบอาหารต่างๆเช่น ข้าว, มะเขือเทศ, มัน, ปลา ฯลฯ เล่าประวัติความเป็นมาของวัตถุดิบอาหารต่างๆเชื่อมโยงกับวัฒนธรรมของญี่ปุ่นว่าเกี่ยวข้องกันอบ่างไร, เริ่มกินกันเมื่อไหร่. ชอบรายการนี้มาก. เรียนรู้วัฒนธรรมจากวัตถุดิบและอาหารครับ. อยากให้เมืองไทยมีรายการอย่างนี้บ้างจัง (หรือมีแล้ว).
  • 所さんの目が点 ช่อง 4 Nihon TV ทุกเช้า 7:00 วันอาทิตย์. เป็นรายการสำหรับเด็ก (? เพราะมาเช้าเหลือเกินอัดวีดีโอทุกที) นำเสนอสัพเพเหระแนววิทยาศาตร์. จะมีประเด็นต่างๆทุกอาทิตย์เช่นนกฟลามิงโก้. ทำไมมันชอบยืนขาเดียว? ทำไมมันมีสีแดง? ทำการทดลองโน่นทดลองนี่. ได้ความรู้ใหม่ทุกสัปดาห์ครับ. อย่างสัปดาห์ที่ผ่านมาเพิ่งรู้ว่าเห็ดหอมแห้งนี่อร่อยกว่าเห็ดหอมสด. มิน่าเมืองจีนหรือญี่ปุ่นมักจะมีขายเห็ดหอมแห้ง. ทางรายการก็แสดงข้อมูลสารเคมีที่บอกความอร่อยว่าถ้าเทียบเห็ดที่ตากแห้งกับไม่ตากแห้ง. เห็ดที่แตกแห้งจะทำให้สารนั้นเพิ่มขึ้น. อะไรทำนองนี้ครับ.
ใครที่อยู่ญี่ปุ่นก็อย่าดูแต่ละคร, หรือการ์ตูนแล้วกัน. มีรายการดีๆให้ดูอีกเยอะ.

วันพฤหัสบดีที่ 2 ธันวาคม พ.ศ. 2547

Qt way - ทดสอบ Label ภาษาไทย

ต่อจากวันก่อนเรื่องเขียนโปรแกรม GUI ด้วย Qt ครับ. วันนี้จะใช้วิธี subclass สืบทอด (inherit) คลาสของวิดเจดที่มีอยู่แล้วเพิ่มเติมความสามารถที่ต้องการในคลาสใหม่ที่สร้างเอง.

ในตัวอย่างจะสร้างวิดเจด (คลาส) ใหม่ชื่อ MyLabel โดยสืบเชื้อสายมาจาก QLabel และใช้ฟอนต์ภาษาไทย (ฟอนต์ Loma). ไฟล์ mylabel.h ซึ่งเป็นไฟล์ header มีรายละเอียดดังต่อไปนี้.

     1  #ifndef MY_LABEL_H
     2  #define MY_LABEL_H
     3
     4  #include <qlabel.h>
     5  class QWidget;
     6  class QFont;
     7  class QString;
     8
     9  class MyLabel : public QLabel
    10  {
    11  public:
    12          MyLabel( const QString &text, QWidget *parent, char *name);
    13  private:
    14          QFont font;
    15  };
    16  #endif

บรรทัดที่ 1-2 ใช้ C preprocessor #ifndef เพื่อป้องกันการอ่านไฟล์นี้ซ้ำ. บรรทัดที่ 4 รวมเอาไฟล์ header "qlabel.h" เข้าไว้ในนี้ด้วย. ส่วนบรรทัดที่ 5-7 เรียกว่า forward declaration คือประกาศว่า QWidget, QFont และ QString เป็นคลาสให้คอมไพเลอร์รับรู้แต่ไม่ต้อง include. จะทำให้คอมไพล์ได้เร็วขึ้น (หนังสือบอกไว้ว่าอย่างนั้น).

บรรทัดที่ 9 ประกาศชื่อวิดเจด (คลาส) ที่ต้องการสร้างชื่อ MyLabel และสืบเชื้อสายมาจาก QLabel.

บรรทัด 12 เป็นการประกาศ constructor รับ QString, QWidget และสายอักขระเป็นอาร์กิวเมนต์. constructor นี้เป็นการ overload constructor ของ QLabel.

บรรทัดที่ 14 สร้างตัวแปรเก็บข้อมูลฟอนต์ชื่อ font ไว้.

เมื่อสร้างไฟล์ mylabel.h แล้วเก็บไว้ในไดเรกทอรีชื่อ label. แล้วสร้างไฟล์ mylabel.cpp ซึ่งเป็นไฟล์ที่จะเขียนโค้ดของวิดเจดนี้ต่อไป.

     1  #include "mylabel.h"
     2
     3  MyLabel::MyLabel( const QString& s, QWidget *parent, char *name)
     4          : QLabel( s, parent, name)
     5  {
     6          font.setFamily( "loma");
     7          setFont( font);
     8
     9  }

บรรทัดที่ 1 เป็นการ include ไฟล์ mylabel.h ซึ่งอยู่ในไดเรกทอรีเดียวกัน. บรรทัดที่ 3 เป็นโค้ดของ constructor จะส่งอาร์กิวเมนต์ให้กับ constrictor ของคลาสที่พ่อแม่คือ QLabel.

บรรทัดที่ 6 เรียก method "setFamily" ของอ็อปเจค font เพื่อตั้งค่า Font Family เป็นฟอนต์ Loma. และบรรทัดที่ 7 เรียกฟังก์ชัน setFont ซึ่งสืบทอดมาจาก QLabel.

ถึงตอนนี้เราได้สร้างวิดเจดซึ่งสืบเชื้อสายมาจาก QLabel เรียบร้อยแล้ว. ต่อจากนี้จะเป็นการใช้วิดเจดที่สร้างใหม่. ให้สร้างไฟล์ main.cpp ซึ่งมีภาษาไทยอยู่ข้างในไฟล์บันทึก encoding แบบ UTF-8.

     1  #include <qapplication.h>
     2  #include "mylabel.h"
     3
     4  int main( int argc, char *argv[])
     5  {
     6          QApplication app( argc, argv);
     7          MyLabel *label = new MyLabel( 
    QString::fromUtf8( "สวัสดีครับพี่น้องทุกท่าน"), 0 , "label");
     8          app.setMainWidget(label);
     9          label->show();
    10          app.exec();
    11
    12  }
ไฟล์นี้คล้ายกับตัวอย่าง main.cpp ที่แล้วแบบเดียวกัน. คือสร้าง QApplication, สร้างวิดเจด, เซ็ตวิดเจดหลักแล้วเข้า exec(). บรรทัดที่ 7 จะเป็นการสร้างวิดเจดที่เราสร้างใหม่. อาร์กิวเมนต์ตัวแรกเป็นการเรียกใช้ static function ของคลาส QString ให้แปลงอักขระ UTF-8 เป็น QString. ข้อมูลที่เก็บอยู่ใน QString จะเป็น unicode 16 bit ดังนั้นจะเขียน
MyLabel *label = new MyLabel( "ภาษาไทย", 0 , "label");
ดื้อๆไม่ได้. ส่วนคำว่า "label" นั้นเป็นชื่อที่ตั้งเอง, จะเป็นอะไรก็ได้.

คอมไพล์และแสดงผล

ตอนนี้เรามีไฟล์ mylabel.h, mylabel.cpp และ main.cpp เก็บไว้อยู่ในไดเรกทอรี label. ให้สั่งคำสั่งต่อไปนี้เพื่อสร้างโปรแกรม.
$ ls
main.cpp  mylabel.cpp  mylabel.h
$ qmake -project
$ ls
label.pro  main.cpp  mylabel.cpp  mylabel.h
$ qmake label.pro
$ ls
Makefile  label.pro  main.cpp  mylabel.cpp  mylabel.h
$ make
g++ -c -pipe -Wall -W -O3 -march=pentium4 -fprefetch-loop-arrays -pipe  -DQT_NO_DEBUG
 -I/usr/qt/3/mkspecs/linux-g++ -I. -I. -I/usr/qt/3/include -o main.o main.cpp
g++ -c -pipe -Wall -W -O3 -march=pentium4 -fprefetch-loop-arrays -pipe  -DQT_NO_DEBUG 
-I/usr/qt/3/mkspecs/linux-g++ -I. -I. -I/usr/qt/3/include -o mylabel.o mylabel.cpp
g++ -Wl,-rpath,/usr/qt/3/lib -o label main.o mylabel.o    -L/usr/qt/3/lib 
-L/usr/X11R6/lib -lqt -lXext -lX11 -lm
$ ls
Makefile  label.pro  main.o       mylabel.h
label*    main.cpp   mylabel.cpp  mylabel.o
$ ./label
ถ้าไม่มีข้อผิดพลาดอะไรก็จะได้ไฟล์ label รันแล้วได้ผลดังนี้.

ตัวอักษรที่แสดงใน label มีการเรียงวรรณยุกต์ถูกต้อง (ตกใจเล็กน้อย), สระไม่ลอย. อันนี้ไม่แน่ใจว่าเป็นเพราะ scribe หรือเปล่า.

สายอักษรที่แสดงใน QLabel นี้สามารถใช้ไวย์กรณ์ของ HTML ได้ด้วยเช่นเปลี่ยนสีให้เป็นสีแดง, เขียนได้แบบนี้.

MyLabel *label = new MyLabel( QString::fromUtf8( 
  "<font color=\"red\">สวัสดีครับพี่น้องทุกท่าน</font>"), 0 , "label");
คอมไพล์ใหม่แล้วจะได้ label ดังนี้.

คราวหน้าเข้าเรื่อง designer ครับ.

หมายเหตุ: เพิ่มลิงค์ไปหา doc.trolltech.com ให้อ้างอิงคลาสต่างๆได้สะดวกขึ้น.

Act Against AIDS

เมื่อวานวันที่ 1 ธันวาคมเป็นวันเอดส์สากล (AIDS day). ข่าวทางทีวีที่ผมดูพูดถึงคอลัมน์ในหนังสือพิมพ์ญี่ปุ่นเขียนไว้ว่า 「タイに学ぶ」 แปลว่า "ให้เรียนจากประเทศไทย". ในคอลัมน์ข่าวพูดถึงสถานการณ์โรคเอดส์ในปัจจุบันของประเทศญี่ปุ่นว่ามีแนวโน้มเพิ่มขึ้นทุกที. คนทั่วไปโดยเฉพาะวัยรุ่นไม่ค่อยมีความเข้าใจที่ถูกต้องเกี่ยวกับโรคเอดส์, ตั้งแต่การป้องกันและการปฏิบัติที่ถูกต้องกับคนที่เป็นโรคเอดส์. ในหนึ่งสือพิมพ์ (จำชื่อหนังสือพิมพ์ไม่ได้) มีรูปจากเมืองไทยเป็นรูปในโรงเรียน (หรือโรงงาน ไม่แน่ชัด) คนหลายคนใช้เข็มฉีดยาและบีกเกอร์ทดลองวิทยาศาสตร์สาธิตการติดต่อของโรคเอดส์. โฆษกบอกว่าการสาธิตนี้แจกบีกเกอร์ใส่น้ำให้ทุกคนถือและหลอดฉีดยาซึ่งมีของเหลวอยู่ข้างใน. เสร็จแล้วก็ให้นักเรียน (?) เอาหลอดฉีดยานั้นไปจุ่มในบีกเกอร์ของเพื่อนๆไปเรื่อยๆ. ในหลอดฉีดยาบางหลอดมีสารเคมีอะไรสักที่จะทำปฏิกริยากับสารเคมีตัวอื่นแล้วมีสีเกิดขึ้น. หลอดฉีดยาที่แจกให้นักเรียนทุกคนมีบางหลอดเท่านั้นที่มีสารนี้อยู่. แต่หลังจากที่จบการทดลองแล้วตรวจสอบน้ำในบีกเกอร์ปรากฏว่าบีกเกอร์เกือบทั้งหมดทำปฏิกริยากับสารเคมีดังกล่าว. ถึงจุดนี้ก็เป็นหน้าที่ของครูที่สอนเด็กให้รู้ว่าโรคเอดส์ก็มีวิธีการติดต่อที่คล้ายๆกับที่สาธิตไป.

โฆษกรายการเขาก็บอกว่าญี่ปุ่นน่าจะสอนให้คนรู้จักกับโรคเอดส์มากกว่าปัจจุบัน. เหมือนเมืองไทย. คือเขาชมเมืองไทยว่ารณรงค์ต่อต้านโรคเอดส์ได้ดี.

เข้าเรื่องที่เกี่ยวข้องคือเมื่อวานไปดูคอนเสริตร์การกุศล Act Against AIDS รายได้นำไปช่วยเหลือเด็กกำพร้าที่พ่อแม่เสียชีวิตด้วยโรคเอดส์และเด็กเหล่านั้นบ้างก็เป็นโรคเอดส์ด้วย. คอนเสิร์ตการกุศลนี้จัดทุกปีมีมา 12 ปีแล้ว. มีจัดหลายที่ในโตเกียว. ที่ที่ผมไปอยู่ที่ Shibuya Bunkamura Orchard Hall. เป็นคอนเสิตร์ Jazz โดยศิลปินญี่ปุ่นนำโดย Ryoko Moriyama และแขกรับเชิญอื่น Akiko Yano, Anri, Naoko Terai และ Ayaka Hirahara (ที่เคยบอกว่าจะไปดูคอนเสิร์ตของเธอก็คืออันนี้แหละ). ในชีวิตนี้เคยดูคอนเสิร์ตนับได้ 4 ครั้ง. ครั้งนี้ประทับใจมาก. น้ำเสียงของทุกคนมีเอกลักษณ์และคุณภาพดีจริงๆ. จบคอนเสิร์ตเลยซื้อ CD อัลบัมใหม่ของ Ayaka มาฟัง.

หมายเหตุ: เมื่อวานเข้า Blogger มาเขียน Blog ไม่ได้รู้สึกจะเป็น maintenance.