ทำไม Array ถึงเริ่มจาก 0 ?

Lek Noi
4 min readJan 3, 2021

--

ผมได้ยินคำถามนี้ในหมู่โปรแกรมเมอร์รุ่นใหม่จำนวนมาก ว่าทำไม Array เริ่มจาก 0 ทำไม ไม่เริ่มจาก 1 ที่เป็นเลขตัวแรกของจำนวนนับละ ? จะทำให้สับสนไปทำไม ?

Array คืออะไร ?

Array คือ ตัวแปล (Variable) ที่นำเอาข้อมูลที่ชนิด หรือประเภทเดียวกันมาเรียงต่อกัน มีลำดับที่ชัดเจนกัน เช่น คะแนนวิชาเลขของนักเรียนห้อง 3/1 จำนวน 40 คน จะเห็นว่าข้อมูลเป็นคะแนนเหมือนกันทั้ง 40 คน และมีลำดับอ้างถึงได้ (Index หรือ Array Index) ตามลำดับของนักเรียนในห้องนั้น

Array ใช้ทำอะไร ?

ในหลักการเขียนโปรแกรม เราใช้ Array เพื่อช่วยลดเวลาในการทำงานลง กล่าวคือถ้านักเรียนห้อง 3/1 มี 40 คนเราต้องประกาศตัวแปล 40 ตัวแปลเพื่อให้สามารถนำข้อมูลคะแนนของทุกคนมาทำการเปรียบเทียบ เช่นการหาค่ามากสุด ค่าน้อยสุด การเรียงลำดับ การหาค่าเฉลี่ย เป็นต้น แล้วถ้าเราต้องเปรียบเทียบนักเรียนทั้งชั้นปีซึ่งอาจจะมีมากกว่า 500 คนละ เราจะประกาศตัวแปลแบบไหนให้ทำงานง่ายสุด การใช้ Array ช่วยเราประกาศตัวแปล Array ขนาด 40 ช่องเพียงครั้งเดียว ซึ่งทำให้ง่ายต่อการเขียนโปรแกรมมากขึ้น อันนี้เป็นตัวอย่างของการใช้ Array ซึ่งยังมีประโยชน์อีกมาก เอาไว้อธิบายทีหลังละกัน (ถ้ามีเวลา)

Array ประกาศยังไง ?

เราเริ่มต้นใช้งาน Array โดยการประกาศตัวแปล โดยมีสองเงื่อนไขคือ 1. ชนิดข้อมูล 2. ขนาดของ Array เช่นในภาษี C มีรูปแบบการสั่งงานแบบนี้

บรรทัดที่ 4 คือการประกาศ Array มีชนิดข้อมูลเป็น Integer ขนาด 5 Index

บรรทัดที่ 6 คือการใช้งานตัวแปล Array โดยอ่านค่าจากลำดับ (Array Index) แรกสุดคือ 0 ไปจนถึงลำดับสุดท้าย คือ 4 และสั่งพิมพ์ออกมาทางหน้าจอ

ผลลัพธ์ที่ได้จะเป็นดังนี้

ตัวอย่างนี้เป็นแบบง่ายนะครับ เพื่อให้เห็นภาพการทำงานเบื้องต้นก่อน

ทำไม Array Index ต้องเริ่มจาก 0 ?

คำตอบนี้ ผมแยกได้เป็นสองคำตอบคือ

  • ประสิทธิภาพ (Efficiency) และ ประสิทธิผล (Effectiveness)
  • การออกแบบ (Design) และ ความขี้เกียจ (Laciness)

สำหรับคนที่เขียนโปรแกรมมาหลากหลายจะเห็นว่าหลายภาษา Array Index เริ่มจาก 0 เช่น Basic, Visual Basic, C, C++, C#, Objective-C, Go, Java, JavaScript, Perl, PHP, Python, และ Swift แต่ก็มีภาษาอีกไม่น้อยที่ Array Index เริ่มจาก 1 เช่น AWK, COBOL, Fortran, FoxPro, Lingo, MATLAB, RPG, R, XQuery ผมขอยกตัวอย่างแค่นี้ละกัน เพราะนี่ถือภาษาที่ผมเคยเขียนมาทั้งหมด ทำให้มีความเข้าใจการทำงานบ้างพอสมควร นอกจากนี้ยังมีภาษาคอมพิวเตอร์เยอะมาก มีรูปแบบ Array ที่ต่างกันออกไป แต่ผมไม่เคยเขียนมาก่อน จึงไม่อยากระบุลงไป เพราะ….

Array Index ไม่จำเป็นต้องเป็น 0 หรือ 1 เสมอไปก็ได้ เช่นภาษา Pascal (รวมถึงภาษาในตระกูล Pascal) ภาษา Pascal สามารถกำหนด Array Index ได้ตามต้องการ ตัวอย่างเช่น

ผลลัพธ์ที่ได้คือ

จากตัวอย่างที่ยกมาทั้งหมด เพื่อให้เข้าใจว่า Array ไม่จำเป็น 0 หรือ 1 หรือไม่จำเป็นต้องเป็นตัวเลข ให้เป็นตัวหนังสือก็ย่อมได้เช่นกัน ทั้งนี้ขึ้นอยู่กับการออกแบบของภาษานั้นๆ ว่ามีเป้าหมายอะไร

Array Index เริ่มจาก 0 ดียังไง ?

ในการเขียนโปรแกรม เรากำหนดตัวแปลเพื่อใช้งานซ้ำๆ กันถูกมั๊ยครับ แต่คอมพิวเตอร์ รู้จักตัวแปลตัวนั้นหรือไม่ครับ ? ถ้าเราบอกว่าเราประกาศตัวแปลชื่อ arr แล้วคอมจะรู้จักตัวแปล arr เหมือนที่เรารู้จักหรือไม่ ? การตั้งชื่อตัวแปลที่มีความยาวต่างกัน เช่น a, arr, array, arrayofint แบบนี้จะมีผลต่อการทำงานของคอมพิวเตอร์หรือไม่ ?

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

  • คำสั่งคือรูปแบบการทำงาน โดยเป็นคำสั่งแบบเรียงลำดับถูกเก็บไว้ในหน่วยความจำ (Memory)
  • ข้อมูล คือค่าต่างๆ ที่จะถูกนำมาคำนวณโดยคำสั่ง ถูกจัดเก็บในหน่วยความจำเช่นเดียวกัน

ดังนั้นเวลาทำงานคอมไพเลอร์ (compiler) จะแปลงภาษาคอมพิวเตอร์ (Computer Language) โดยนำโครงสร้างภาษา (Syntax) แปลงให้เป็น Instruction code และแปลงตัวแปล (Variable) ไปเป็นตำแหน่งในหน่วยความจำ ตัวอย่างเช่น

รูปด้านบน เป็นคำสั่งประกาศตัวแปล Arr เป็น Array ชนิด Integer ขนาด 5 index และในตารางด้านล่างเป็นสิ่งที่มนุษย์โดยทั่วไป จะจิตนการถึง และมักจะเข้าใจว่า Array หน้าตามันเป็นแบบนี้นะ แต่ในมุมมองของคอมพิวเตอร์คือ

รูปนี้จะเป็นรูปที่ใกล้เคียงสิ่งที่คอมพิวเตอร์มองเห็นมากที่สุด โดยค่า 0x5200000 เป็นค่าที่ผมสมมุติขึ้นว่า ตัวแปล Arr ที่ประกาศมา ถูกคอมพิวเตอร์อ้างถึงที่ตำแหน่งนี้ คอมพิวเตอร์เครื่องนี้ไม่ได้รู้หรอกว่า Array คืออะไร มันรู้เพียงแค่ว่า ถ้าต้องการเข้าถึงข้อมูลที่มนุษย์อ้างโดยใช้ชื่อว่า Arr จะอยู่ที่ตำแหน่ง 0x5200000 นี้ เมื่อเราประกาศตัวแปล int Arr[] ขนาด 5 index โดยจากที่ผมยกตัวอย่างเป็นภาษาซี ตัวแปล int มีขนาด 4 bytes ดังนั้น ถ้าข้อมูลเริ่มต้นที่ 0x52000000 จะถูกเพิ่มครั้งละ 4 bytes ไป 5 ครั้ง ไปจบที่ 0x52000013 นี่คือ Array ที่คอมพิวเตอร์จัดเก็บไว้ และถูกอ้างถึงด้วยตำแหน่งในหน่วยความจำคือ 0x5200000 เพียงค่าเดียว

เมื่อเราอ้างถึง Arr[0] คอมพิวเตอร์ไปชี้ไปที่หน่วยความจำตำแหน่งที่ 0x52000000 และเริ่มดึงข้อมูลขนาด 4 bytes ขึ้นมาทำงาน หรืออ่านตั้งแต่ 0x52000000 จนถึง 0x52000003 ถ้าเราอ้างถึง Arr[3] ก็จะไปที่ตำแหน่งเริ่มต้นของ Array แล้วบวกด้วยผลคูณของตำแหน่งกับขนาด ดังนี้ Array_Address + (index_id x Size_Bytes) หรือ 0x52000000 + (3 x 4) ได้เป็นตำแหน่งของข้อมูลคือ 0x5200000c และดึงข้อมูลจำนวน 4 bytes ขึ้นมาทำงาน

ในการออกแบบ ภาษาคอมพิวเตอร์ เราจะไม่เพิ่มเงื่อนไขที่ไม่จำเป็นลงไป ดังเช่นตัวอย่าง ที่ยกไปคือ Index 0 ทำแบบหนึ่ง Index ไม่เท่ากับ 0 ทำงานอีกแบบ แต่เราจะสั่งงาน Index ทุกค่าเหมือนกัน เพื่อให้ทำงานเร็วที่สุด และซับซ้อนน้อยที่สุด สรุปได้ว่าเพื่อให้ Array ทั้งหมดมีการทำงานแบบเดียวกัน Arr[3] ทำให้ Arr[0] จะต้องประมวลผล หรือคำนวณแบบนี้ 0x52000000 + (0 x 4) ได้เป็นตำแหน่งของข้อมูลคือ 0x52000000

ลองคิดดูว่าถ้า Array Index ไม่ได้เริ่มจาก 0 คอมพิวเตอร์จะทำงานอย่างไร ? ตัวอย่างเช่น

ผมยกตัวอย่างจากภาษา AWK โดยกำหนดให้ integer มีขนาด 4 bytes เท่ากัน และตำแหน่งเริ่มต้นของ array อยู่ที่ 0x52000000 เหมือนกัน ดังนั้นในกรณีที่เราอ้างถึง Arr[1] คอมพิวเตอร์จะต้องคำนวณโดยใช้ตำแหน่งเริ่มต้น บวก Index ลบ 1 คูณขนาดข้อมูล หรือ 0x52000000 + ((1–1) x 4) จะเห็นว่า คอมพิวเตอร์ ต้องทำงานเพิ่มขึ้นอีก 1 คำสั่ง คือต้องมีการเอา index-id มาลบ 1

หลายคนอาจจะบอกว่า แค่ 1 คำสั่งเอง ไม่มีผลหรอก ? ถ้าคุณพูดแบบนี้ แสดงว่าคุณยังไม่เคยทำงานระบบขนาดใหญ่มาก่อน ผมเคยทำงานระบบขนาด Source Code มากกว่า 1 ล้านบรรทัดมาแล้ว เคย Compile โปรแกรมข้ามคืนมาแล้ว และเคยสร้างโปรแกรมกราฟฟิก คำควณแสงแดด และเงา ที่ต้อง Compile ข้ามสัปดาห์มาแล้ว บอกเลยว่าแค่ 1 คำสั่งนี้ละ ถ้าจำนวนมันเยอะมากๆ มันจะส่งผลให้ความเร็วในการ Compile ต่างกันได้มหาศาล

อย่าคิดว่าเพียงแค่จุดเล็กๆ จะไม่มีผลนะครับ ถ้าเลือกใช้ให้เป็นนั่นคือ ประสิทธิภาพ (Efficiency) และ ประสิทธิผล (Effectiveness) ที่จะตามมานั่นเอง

แล้ว การออกแบบ และ ความขี้เกียจ มาได้อย่างไร ?

ภาษาคอมพิวเตอร์ แบ่งออกเป็นสองกลุ่มใหญ่คือ Compiler และ Interpreter

  • Compiler จะทำการแปลง Source Code ทั้งหมดเป็นภาษาเครื่องก่อน ถึงจะเริ่มทำงานได้
  • Interpreter จะอ่าน Source Code นำไปแปลเป็นภาษาเครื่อง และรัน ทีละบรรทัด

ทั้งสองรูปแบบนี้ ส่งผลต่อการออกแบบ Index ของ Array ด้วยเช่นกัน เรารู้แล้วว่า Array Index เริ่มจาก 0 ทำงานได้เร็วกว่า แต่ขึ้นอยู่กับว่าเป็นภาษาในรูปแบบไหน เช่น ภาษาแบบ Interpreter ถ้า Array Index เริ่มจาก 0 จะรันได้เร็วกว่า แต่ถ้าเป็นภาษาแบบ Compiler ไม่ว่า Array Index จะเป็นอะไร มันก็จะรันได้เร็วเท่ากัน แต่เวลา Compiler Souce code ไปเป็น Computer Code จะเร็วช้าต่างกัน เพราะถ้า Array Index ไม่ใช่ 0 ตัว Compiler จะต้องทำหน้าที่ในการแปลง Index ของ array นั้นๆ ให้เป็น 0 ก่อนถึงจะนำไปแปลงเป็นภาษาเครื่องต่อไป (ซึ่งเป็นวิธีที่นิยมมากที่สุด ไม่จำเป็นว่าทุกภาษา จะต้องทำแบบนี้ เสมอไปนะครับ)

ดังนั้นจึงเป็นที่มาของคำว่าการออกแบบ และความขี้เกียจ

  • ถ้า Array Index start with 0 คนออกแบบง่าย หรือขี้เกียจออกแบบให้คนศึกษาเรียนรู้ได้ง่าย ส่วนคนเขียนโปรแกรมต้องเรียนรู้ว่ามันเริ่มจาก 0 จะมีบางคนขี้เกียจที่จะเรียนรู้ และขี้เกียจทำความเข้าใจ
  • ถ้า Array Index start with 1 หรือค่าอื่นๆ คนออกแบบเหนื่อยเพราะต้องเพิ่มขั้นตอนในการทำงาน แต่ก็ทำให้คนเขียนโปรแกรมง่ายขึ้น หรือขี้เกียจที่จะเรียนรู้ในการเขียนโปรแกรมให้ได้ประสิทธิภาพ

ส่งท้าย

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

ตัวอย่างนี้ใช้ภาษา python เราประกาศ Array arr1 เริ่มต้นด้วย 3 index มีข้อมูลเป็น String จากนั้นเพิ่ม integer เข้าไป เพิ่ม Array arr2 เข้าไป และสุดท้ายเพิ่ม string เข้าไปอีก จะเห็นว่า Array แบบนี้เพิ่มขนาด Index ได้ และสามารถเก็บตัวแปลได้สามชนิดเลยคือ string, array of integer, และ integer เมื่อเอาไปรัน จะได้ผลลัพธ์ดังนี้

ภาษาสมัยปัจจุบัน ง่ายขึ้น และทำงานกับข้อมูลเยอะมากๆ ได้ดีขึ้น ราวๆ สิบปีที่แล้วผมเคยเขียน perl และ python อ่านข้อมูลขนาด 1 ล้าน index ขึ้นมา sort แทบไม่น่าเชื่อว่ามันสามารถทำงานได้ภายใน ไม่ถึงยีสิบนาที ดังนั้นการออกแบบ array ให้เริ่มจากอะไร จึงไม่ค่อยสำคัญแล้ว เพราะรูปแบบการทำงานกับ array มันล้ำหน้าไปไกลกว่าจุดเริ่มต้นมาก ส่วนใครอยากรู้ว่ามันทำงานแบบไหน ก็ลองไปศึกษาเรื่อง compiler กับ data structure เพิ่มเติมกันเอาเองนะครับ ถ้าให้อธิบายคงยาว

แล้วเจอกันใหม่ครับ
Lek Noi~

--

--

Lek Noi

คนธรรมดา คนหนึ่งที่ชอบด้านไอที และรักในการขีดๆ เขียนๆ