2007/Jul/08

วันนี้เราจะมาว่ากันด้วยเรื่องการของการทำอะไร ซ้ำๆ ใน ChucK นะครับ


เท่าที่ผ่านมาที่พวกเราทดลองชักเล่นกันเนี่ย เอ๊ย ผิดๆ เล่นชัก
ลักษณะการทำงานของโปรแกรมต่างๆที่เขียน
ก็จะทำงานเรียงลำดับ จากบรรทัด บน ลงมาบรรทัดล่าง ไปเรื่อยๆ
เหมือนเวลาเราอ่านคำอธิบายอะไรซักอย่างแล้วทำตามเป็นขั้นๆ

แต่ถ้า เราอยากทำอะไรซ้ำๆล่ะ?
คนที่เขียนเขียน C, C++, Java ก็คงพูดเลยว่า อ๋อๆ มีวิธีการทำให้อะไรมันทำงานซ้ำๆกัน ก็คือ การวนลูป นี่เอง
การวนลูปนี่ก็มีหลายอย่าง แต่ปกติแล้ว การวน ก็จะวนแบบ มีเงื่อนไข
ที่บอกว่ามีเงื่อนไขก็คือ จะทำการวนก็ต่อเมื่อ อะไรบางอย่างมันเป็นจริง
(หรือในทางกลับกันก็คือ จะหยุดวน ก็ต่อเมื่อ อะไรบางอย่างไม่เป็นจริง)

ก่อนที่เรากำลังจะทำการวน ผมจะแนะนำให้รู้จักสัญลักษณ์อย่างหนึ่ง
ที่ผ่านมา เราอาจจะเคยเห็น วงเล็บ แล้วใช้ในการคำนวณ
เช่น.. (1 + 2) * 3 แปลว่า เอา 1 ไปบวก 2 ก่อน (ได้ 3) แล้วค่อยคูณด้วย 3 (ได้ 9)
แต่ 1 + 2 * 3 แปลว่า เอา 2 ไปคูณ 3 ก่อน (ได้ 6) แล้วค่อยบวกกับ 1 (เพราะการ บวก ให้ทำทีหลังคูณ)

ก็คือ วงเล็บนี่ เป็นตัวบังคับว่า ให้ทำอะไรก่อน หลัง

แต่ในการสั่งคำสั่งบรรทัดต่างๆ
เราสามารถกำหนดให้คำสั่งต่างๆ รวมกันเป็นเสมือนประโยคเดียวกัน หรือเรียกว่า เป็น expression เดียวกัน

expression นี่ ก็คือ พวกประโยคต่างๆที่มีการคำนวณ มีค่า อะไรก็แล้วแต่ แต่ทำให้เกิดการคำนวณขึ้นมา
เช่น 1 ก็เป็น expression
1 + 2 ก็เป็น expression
SinOsc s => dac
ก็เป็น expression ชนิดหนึ่ง
expression ต่างๆ จะจบด้วยเครื่องหมาย ;
คราวนี้ ถ้าเราอยากเขียนเสมือน หนึ่ง expression
แต่อยากเขียนหลายๆ expression เรียงต่อกันล่ะ?

ก็ต้องใช้ วงเล็บปีกกา หรือ { } ในการจัดกลุ่ม expression เหล่านั้น
ให้รวมเสมือนเป็นหนึ่ง expression

โดยมีกฎเกณฑ์อย่างหนึ่งก็คือ สิ่งที่อยู่ภายใน { } เดียวกัน สามารถประกาศ ตัวแปร (และสิ่งอื่นๆ เช่น ฟังก์ชั่น, คลาส) ของมันเองได้
แต่เมื่อออกไปนอก { } แล้ว หรือเสร็จจากการทำงานใน { } นั้นๆแล้ว
ตัวแปรเหล่านั้น ก็จะไม่มีอีกต่อไป

มันเหมือนกับว่า เป็นตัวแปรส่วนตัวของขอบเขตนั้นๆ (เรียกว่าเป็น local)

เอ้า เราลองมาเขียนโปรแกรมนี้กันดู
ไหนๆก็ใกล้จะง่วงแล้ว ก็เอารูปมาให้ดูกัน จะได้ตื่นขึ้นมา อาห์..



เอ้า! พิมพ์ตามได้เลย

อะไรกัน! ขี้เกียจพิมพ์เหรอ งั้นเอานี่ไปกิน!

1 => int a;
2.0 => float b;
2::second => dur c;
<<< "outside { }" >>>;
<<< a >>>;
<<< b >>>;
<<< c >>>;

{
<<< "inside { }" >>>;
<<< a >>>;
<<< b >>>;
<<< c >>>;
 
<<< "initialize a and b:" >>>;
3 => int a;
4 => int b;
<<< a >>>;
<<< b >>>;
<<< c >>>;
}

<<< "outside { } (We're back!)" >>>;
<<< a >>>;
<<< b >>>;
<<< c >>>;

พอรันแล้วได้ผลดังนี้

"outside { }" : (string)
1 :(int)
2.000000 :(float)
88200.000000 :(dur)
"inside { }" : (string)
1 :(int)
2.000000 :(float)
88200.000000 :(dur)
"initialize a and b:" : (string)
3 :(int)
4 :(int)
88200.000000 :(dur)
"outside { } (We're back!)" : (string)
1 :(int)
2.000000 :(float)
88200.000000 :(dur)

เอามาลองวิเคราะห์ดูเล่นๆอ่ะ

ตอนแรกพวกเราสร้าง ตัวแปรขึ้นสามประเภท
เป็นชื่อ a, b, c ตามลำดับ
ตัวแรกเป็น integer (int), ตัวที่สองเป็น float, ตัวที่สามเป็น duration (dur)
ฮั่นแน่ อันนี้ยังไม่เคยเห็นละเซ่ เอามาโชว์ก่อน
duration แปลว่า "ระยะเวลา"
อย่างเช่น "ระยะเวลา 2 วินาที" ที่เรากำลังให้ C ไง
แต่ข้ามเรื่องนี้ไปก่อน (เอามาให้ดูรู้จักกันไว้ก่อน วันหลังเผื่อเป็นเพื่อนกัน อิอิ)
(หรือเป็นกิ๊กดีนะ!!)


ตอนแรก เราอยู่นอก { } (ก่อนจะเข้า { .. )
มันก็สามารถพิมพ์ ค่า a, b, c ตามลำดับได้ตามปกติ
แถมพิมพ์ค่า dur ออกมาซะสวยหรูเลย เป็นเลข 88200 (ไว้จะอธิบายอีกที)

แต่แล้ว เราก็เข้าไปใน { }
ซึ่งในนั้นน่ะ จริงๆแล้ว เขาก็รู้จัก a, b, c ที่พวกเรามีอยู่
แต่ทันใดนั้น ภายใน { } นั้นเอง ก็ได้มีการกำหนด a, b, c ขึ้นมาใหม่
แถมยังเป็นตัวแปรคนละชนิดกันเสียด้วย!
แล้วเมื่อแสดงค่าออกมา จะเห็นว่า a, b ที่ถูก ประกาศ ออกมาใหม่นั้น
ได้เปลี่ยนไปเป็นคนอื่นไปซะแล้ว!!

แต่ไม่ต้องตกใจ เมื่ออกจาก { } (จบ } .. ออกมา)
พอลองพิมพ์ค่า a, b, c
ค่า a, b, c ดั้งเดิม ก็ยังอยู่เหมือนเดิมไม่เปลี่ยนแปลง
คล้ายๆกับว่า ได้หมดภารกิจ ของ a, b ใน { } อันนั้นแล้ว
ก็ปลดประจำการไป


หวังว่า จะเข้าใจเรื่องการใช้ { } กันมาหนึ่งระดับละ
ต่อไปเราจะมาชักซ้ำๆกัน


เริ่มมาจากคำสั่งกลุ่มแรกก่อน
"ทำ"

ฮ่าๆๆ หมายถึงคำว่า do นี่แหละ
เวลาเราอยากสั่งให้ใครทำอะไรซ้ำๆ
ก็บอกว่า
ทำ (สิ่งที่อยากจะให้ทำ) ในขณะที่ (สิ่งที่จะทดสอบว่าจริงหรือไม่)

รูปแบบของการใช้ do ก็คือ
do { สิ่งที่สั่งให้มันทำ }
while (เงื่อนไข .. ถ้าเป็นจริง มันจะยังทำซ้ำอีก);

เช่น เราอยากสั่งเครื่องให้ "เพิ่มค่า i ไปเรื่อยๆ ทีละ 1" ในขณะที่ "i น้อยกว่า 10"
ลองพิมพ์โปรแกรมตามนี้เลย

0 => int i;
<<< "i starts from", i >>>;
do i + 1 => i;
while (i < 10);
<<< "now i =", i >>>;

ได้ผลลัพท์ออกมาดังนี้:
i starts from 0
now i = 10

โห อะไรจะเรียบง่ายขนาดนั้น
สิ่งที่เกิดขึ้นก็คือ ตอนแรก i เท่ากับ 0 อยู่ดีๆ
เข้าไปใน ลูปชนิด do ก็เลยต้องทำตามที่เขาสั่ง
ก็คือสั่งให้เอาค่า i ไปบวกด้วย 1 แล้ว ชัก เก็บเข้าไปไว้ที่ i เช่นเดิม
(มันก็เหมือนกับการ เพิ่ม i ขึ้นทีละ 1 นี่แหละ)
แต่มีเงื่อนไขว่า ให้ทำไปเรื่อยๆ ในขณะที่ i < 10
ดังนั้น พอครั้งที่ i เป็น 9
แล้วโดนเพิ่มเป็น 10
พอทดสอบว่า while (i < 10);
ไอ้ตอนนั้นเนี่ย i มันดัน ไม่น้อยกว่า 10 แล้ว มันดันเท่ากับ 10 พอดี
การทำงานก็เลย ออกมาจาก วังวนนั้น
ค่า i ก็เลยเป็น 10 อยู่ เห็นภาพแล้วบ่ ? งั้นมาทำภาพให้ดูชัดๆกว่านี้
แก้โค้ดเป็นนี่เลยครับ

0 => int i;
<<< "i starts from", i >>>;
do
{
i + 1 => i;
<<< "now i =", i >>>;
}
while (i < 10);
<<< "finally.. i =", i >>>;

ได้ผลลัพท์ออกมาเป็น
i starts from 0
now i = 1
now i = 2
now i = 3
now i = 4
now i = 5
now i = 6
now i = 7
now i = 8
now i = 9
now i = 10
finally.. i = 10

นี่เลย ตอนนี้ แทนที่เราจะสั่งให้โปรแกรมมัน do แค่คำสั่งเดียว
ก็จัดการสั่งมันเป็นชุดเลย คือทั้ง เพิ่มค่า i แล้วยังจะ พิมพ์ออกมาในแต่ละรอบอีกด้วยแหนะ

จุดเด่นของ do ก็คือ เมื่อเริ่มต้นมาเนี่ย
ไม่ได้มีการทดสอบใดๆเลย เริ่มต้นมาก็ต้อง ทำตามที่มันกำหนดใน do ก่อน
แต่ถ้าเงื่อนไขยังถูกต้องตาม while ก็ ทำซ้ำต่อไป
ถ้าเงื่อนไขไม่ถูกต้องแล้ว ก็หลุดออกมาจาก while
ดังนั้น .. ย้ำ ครับ .. เข้าไปใน do สิ่งที่กำหนดไว้หลัง do จะถูกกระทำก่อนที่จะมีการทดสอบด้วย while

แต่ถ้าเราอยากทดสอบก่อนเลยล่ะ?
แบบว่า ถ้าทดสอบแล้วไม่ได้ตามเงื่อนไข
ก็ไม่ต้องไปทำตามมันเลย

แบบนี้ เขาให้ใช้ while นี่แหละครับ เรียกว่าเป็น ลูป ชนิด while
รูปแบบการใช้ while มีลักษณะแบบนี้
while (เงื่อนไข .. ถ้าเป็นจริง มันจะให้ทำสิ่งต่อไปนี้..) { สิ่งที่จะให้ทำ }

สมมติ ว่าเรามีลูกเต๋า 2 อัน (ลูกเต๋า 6 หน้า.. มีเลขตั้งแต่ 1 .. 6)
เราอยากจะสร้างค่าที่มาจากลูกเต๋าสองอัน
แต่มีเงื่อนไขว่า ลูกเต๋าสองอัน ห้ามเป็นเลขซ้ำกัน
อันนี้ผมจะแนะนำให้รู้จัก ฟังก์ชั่นมาตรฐานหนึ่งใน ChucK ด้วย
เขาชื่อ Std.rand2()
(Standard Function: Random สำหรับ ระหว่าง int 2 ตัว)

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

Std.rand2(1, 6) => int dice1;
<<< dice1 >>>;

ลองรันดูครับ (อ้อ รัน นี่ผมหมายถึง Add Shred)
แล้วก็ รันหลายๆครั้งด้วย แล้วลองดูค่าใน Console Monitor
จะเห็นว่า มันขึ้นมาอย่างกับทอยลูกเต๋าจริงๆเลยล่ะ
เป็นอย่างที่พวกเราคาดไว้
งั้นต่อไป พวกเรามาลอง ทอยลูกเต๋าสองอัน

Std.rand2(1, 6) => int dice1;
Std.rand2(1, 6) => int dice2;
<<< dice1, dice2 >>>;
if (dice1 == dice2) <<< "Both dice has the same number." >>>;

ลองรันไปเรื่อยๆครับ หลายๆที
น่าระทึกใจจริงๆ

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

(ประโยค if (เงื่อนไขที่จะทดสอบ) { สิ่งที่จะทำเมื่อเป็นจริง } ผมก็เลย แอบเอามาแนะนำในที่นี่ด้วยฮ่าๆ
อ่านแล้วก็เข้าใจไม่ยากหรอกน่า อ่านง่ายๆ
ถ้างง ก็คงจะเครื่องหมาย == เครื่องหมายนี้ เอาไว้ทดสอบว่า เท่ากัน หรือเปล่า)

แล้วทำยังไงล่ะ ถึงจะแน่ใจได้ว่า ลูกเต๋าอีกอัน ต้องออกหน้าไม่ตรงกับของเดิม?
เรามาทดสอบกันดีกว่า

Std.rand2(1, 6) => int dice1;
Std.rand2(1, 6) => int dice2;
while(dice1 != dice2) Std.rand2(1, 6) => dice2;
<<< dice1, dice2 >>>;

คราวนี้ ไม่ว่าจะ add shred เท่าไหร่
ลูกเต๋าก็ไม่มีหน้าเท่ากันอีกแล้ว
เพราะถ้าหน้าเท่ากันเมื่อไหร่ มันจะทำการทอยลูกเต๋าใหม่ไปเรื่อยๆ
จนกว่า จะเจอครั้งที่หน้าไม่เท่ากัน

อีกอย่างที่จะนำเสนอในวันนี้ ก็คือ ลูปที่ชื่อ for

for นี่จะทำงานสี่อย่างด้วยการ
1. กำหนดการกระทำเริ่มต้น (เตรียมตัวเข้าลูป)
2. ทำงานที่กำหนดให้กระทำ
3. ดำเนินการเปลี่ยนค่านแต่ละรอบ
4. ทดสอบเงื่อนไขว่าเป็นจริงหรือไม่ ถ้าจริงให้วนต่อไป ไม่จริงก็จบการทำงาน

มีรูปแบบดังนี้
for (เตรียมตัว; ทดสอบเงื่อนไข; การเปลี่ยนค่าในแต่ละรอบ) {งานที่กำหนดให้กระทำ}

เช่น
for (เตรียมหัวหอมและเตรียมมีด; หั่นยังไม่เสร็จ; ยกมีดและขยับมีด) หั่น;

แปลว่า
เตรียมหัวหอมและเตรียมมีด -> หั่น -> ยกมีดและขยับมีด -> ตรวจสอบว่า หั่นเสร็จแล้วยัง ถ้าไม่เสร็จ ให้กลับไปหั่นใหม่ซะ

ยกตัวอย่าง
for (0 => int i; i < 10; i + 1 => i) <<< i >>>;
จะแปลว่าอะไรล่ะ?

อ้อ ก็แปลว่า
เตรียมค่า i โดยเริ่มต้นจาก 0
พิมพ์ค่า i ออกมา
เอา i มาบวกด้วย 1 แล้วเก็บกลับเข้าไปที่ i
i ยังน้อยกว่า 10 อีกหรือเปล่า? ถ้าน้อยกว่าให้กลับไป พิมพ์ ใหม่

เอ้า ลองรันดู ได้ผลลัพท์ออกมาดังนี้:
0 :(int)
1 :(int)
2 :(int)
3 :(int)
4 :(int)
5 :(int)
6 :(int)
7 :(int)
8 :(int)
9 :(int)

อืมๆ นี่มันทำครบสิบรอบพอดีเลย!
แล้วอาจจะงงว่า แล้วทำไมมันไม่พิมพ์เลข 10 ด้วย?

อ๋อ ก็เพราะว่า ตอนที่มันเท่ากับ 10 แล้ว
พอทำการทดสอบ อ่าว มึงไม่น้อยกว่า 10 ซะแล้ว
ก็เลย หยุดการทำงานแต่เพียงเท่านั้น

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

ทบทวน:
do { งานที่ให้ทำ }
while ( ทดสอบ .. ถ้าเป็นจริง ให้กลับไปทำงานนั้นต่อไป );

while ( ทดสอบ .. ถ้าเป็นจริง ให้ทำงานนี้ ) { ก็ไอ้งานที่จะให้ทำนี่แหละ }

for ( เตรียมตัว; ทดสอบว่าเป็นจริงไหม ถ้ายังเป็นจริง ให้ทำเรื่อยๆ; ทำเสร็จแล้วให้เปลี่ยนแปลงค่าอย่างไร ) { งานที่จะให้ทำ }

ไอ้ for นี่อ่านแล้วมันจะงงนิดๆ แต่พอใช้ไปใช้มา มันจะเข้าใจง่าย

คราวนี้ เรามาทบทวนอย่างอื่นบ้าง
ข้อมูลชนิด dur (ย่อมาจาก duration) คือ ระยะเวลา..
เช่น ms (มิลิวินาที), second, minute, hour, day, week พวกนี้เป็น dur ทั้งนั้น
หรือมีกำหนดละเอียดขึ้นเช่น 1.2::second แปลว่า 1.2 วินาที
พวกนี้เป็น dur ทั้งนั้น

Std.rand2(จำนวนเต็ม, จำนวนเต็ม) เป็นฟังก์ชั่นที่ให้ผลออกมาเป็น ค่าสุ่ม จำนวนเต็ม ระหว่างค่าที่กำหนดทั้งสอง

นอกนั้น มีสัญลักษณ์เกี่ยวกับการทดสอบค่าต่างๆอีกเช่น

== แปลว่า "เท่ากันหรือเปล่า"
< แปลว่า "น้อยกว่าหรือเปล่า"
> แปลว่า "มากกว่าหรือเปล่า"
<= แปลว่า "น้อยกว่า หรือ เท่ากับ หรือเปล่า"
>= แปลว่า "มากกว่า หรือ เท่ากับ หรือเปล่า"
!= แปลว่า "ไม่เท่ากับหรือเปล่า"

สำหรับข้อมูลชนิด int นี่
เรายังมีวิธีการในการ บวก หรือ ลบ มันทีละหนึ่งง่ายๆ
โดยการใช้สัญลักษณ์ ++ และ --

เช่น

i + 1 => i;

เขียนเป็น i++; ได้เลย ให้ผลกับ i อย่างเดียวกัน
คือเป็นการเพิ่ม i ทีละ 1

ผมไปพักผ่อนก่อนละนะ ทุกคน อย่าเพิ่งหลับดิ่วะ -_-"
เขียนยาวหน่อยทำหลับ
บอกแล้วให้ลองเล่นชัก มันสนุก -_-
อ่านอย่างเดียวง่วงตายชัก

Comment

Comment:

Tweet


http://www.okey-oyna.com

ลองรันไปเรื่อยๆครับ หลายๆที
น่าระทึกใจจริงๆ
#4 by Okey Oyna (78.171.240.61) At 2011-04-13 02:33,
<a href="http://ravpoifnkpgirzs.com">uomnqklvownchfl</a> http://iorzkpkmcsrizyh.com [url=http://umkycxdvxjbhktj.com]slvocyxcvaasqll[/url]
#3 by cosoifzgqb (94.102.52.87) At 2010-06-14 08:55,
นึกออกเป็นภาพ
'จารย์ กำลังยืนสอน บลา~~~~
แล้วกุ ก้อหลับ อยุ่เลยแฮะ
#2 by sleepingforest (61.7.165.209) At 2007-07-31 13:00,
งงหลายๆๆ วันหลังวานทำการบ้านมั่งได้ป่าวค่า
#1 by Paa orKant At 2007-07-08 09:38,

กิจจาศักดิ์ ตริยานนท์
View full profile