คำแนะนำเกี่ยวกับอาร์เรย์ใน Python

คำแนะนำเกี่ยวกับอาร์เรย์ใน Python

บทนำ

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

อาร์เรย์ถือเป็นหนึ่งในโครงสร้างข้อมูลพื้นฐานและใช้กันอย่างแพร่หลายที่สุด

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

ในคู่มือนี้ เราจะให้ภาพรวมที่ครอบคลุมของโครงสร้างข้อมูลอาร์เรย์ ก่อนอื่น เรามาดูกันว่าอาร์เรย์คืออะไร และคุณลักษณะหลักของอาร์เรย์คืออะไร จากนั้นเราจะเปลี่ยนไปสู่โลกของ Python สำรวจวิธีการปรับใช้ จัดการ และประยุกต์ใช้อาร์เรย์ในสถานการณ์จริง

ทำความเข้าใจเกี่ยวกับโครงสร้างข้อมูลอาร์เรย์

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

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

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

ตัวอย่างเช่น พิจารณาอาร์เรย์ของจำนวนเต็ม: [10, 20, 30, 40, 50]. ที่นี่องค์ประกอบ 20 มีดัชนีของ 1:

การจัดทำดัชนีอาร์เรย์หลาม

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

หมายเหตุ: อาร์เรย์มีประโยชน์อย่างยิ่งในสถานการณ์ที่ ขนาดของคอลเลกชันทราบล่วงหน้าและคงที่หรือการเข้าถึงแบบสุ่มบ่อยกว่าการแทรกและการลบ

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

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

  • มีการจัดทำดัชนี: เช่นเดียวกับแต่ละเพลงในเพลย์ลิสต์ของคุณที่มีตัวเลข (1, 2, 3, …) แต่ละองค์ประกอบในอาร์เรย์ก็มีดัชนี แต่ในภาษาการเขียนโปรแกรมส่วนใหญ่ ดัชนีเริ่มต้นที่ 0 ดังนั้น รายการแรกอยู่ที่ดัชนี 0 รายการที่สองที่ดัชนี 1 และอื่นๆ

  • มีขนาดคงที่: เมื่อคุณสร้างเพลย์ลิสต์สำหรับ 10 เพลง คุณไม่สามารถเพิ่มเพลงที่ 11 โดยไม่ลบเพลงหนึ่งออกก่อนได้ ในทำนองเดียวกัน อาร์เรย์จะมีขนาดคงที่ เมื่อคุณสร้างอาร์เรย์ตามขนาดที่กำหนดแล้ว คุณจะไม่สามารถเพิ่มรายการเกินความจุได้

  • เป็นเนื้อเดียวกัน: เพลงทั้งหมดในรายการเพลงของคุณเป็นแทร็กเพลง ในทำนองเดียวกัน องค์ประกอบทั้งหมดในอาร์เรย์จะเป็นประเภทเดียวกัน หากคุณมีอาร์เรย์จำนวนเต็ม คุณจะไม่สามารถเก็บสตริงข้อความไว้ในอาร์เรย์นั้นได้ทันที

  • มีการเข้าถึงโดยตรง: หากคุณต้องการฟังเพลงที่ 7 ในรายการเพลงของคุณคุณสามารถข้ามไปที่เพลงนั้นได้โดยตรง ในทำนองเดียวกัน เมื่อใช้อาร์เรย์ คุณสามารถเข้าถึงองค์ประกอบใดๆ ได้ทันทีหากคุณทราบดัชนีขององค์ประกอบนั้น

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

Python และอาร์เรย์

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

ได้อย่างรวดเร็วก่อน รายการของหลาม อาจดูเหมือนตรงกันกับอาร์เรย์ แต่มีความแตกต่างและความแตกต่างเล็กน้อยที่ต้องพิจารณา:

รายการ แถว
โครงสร้างข้อมูล Python ในตัว ไม่ใช่ภาษา Python – มาจากโมดูล `array`
ขนาดไดนามิก ขนาดคงที่ (กำหนดไว้ล่วงหน้า)
สามารถเก็บรายการข้อมูลประเภทต่างๆ ได้ เก็บของประเภทเดียวกัน
จัดเตรียมวิธีการในตัวที่หลากหลายสำหรับการจัดการ จำเป็นต้องนำเข้าโมดูลภายนอก
O(1) ความซับซ้อนของเวลาสำหรับการดำเนินการเข้าถึง O(1) ความซับซ้อนของเวลาสำหรับการดำเนินการเข้าถึง
ใช้หน่วยความจำมากขึ้น หน่วยความจำมีประสิทธิภาพมากขึ้น

เมื่อดูที่โต๊ะนี้ ก็มักจะถาม- “จะใช้อันไหนเมื่อไร”. หากคุณต้องการคอลเลกชันที่สามารถขยายหรือย่อขนาดแบบไดนามิกและสามารถเก็บประเภทข้อมูลแบบผสมได้ รายการของ Python คือคำตอบของคุณ อย่างไรก็ตาม สำหรับสถานการณ์ที่ต้องการคอลเลกชันที่มีประสิทธิภาพหน่วยความจำมากขึ้นซึ่งมีองค์ประกอบประเภทเดียวกัน คุณอาจพิจารณาใช้ Python array โมดูลหรือไลบรารีภายนอกเช่น NumPy

พื้นที่ แถว โมดูลในหลาม

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

แม้ว่ารายการ Python จะมีความหลากหลายอย่างไม่น่าเชื่อและสามารถจัดเก็บออบเจ็กต์ประเภทใดก็ได้ แต่บางครั้งรายการเหล่านั้นก็อาจใช้มากเกินไป โดยเฉพาะอย่างยิ่งเมื่อคุณจำเป็นต้องจัดเก็บเพียงคอลเลกชั่นประเภทข้อมูลพื้นฐาน เช่น จำนวนเต็มหรือจำนวนทศนิยม ที่ array โมดูลให้วิธีสร้างอาร์เรย์ที่มีประสิทธิภาพหน่วยความจำมากกว่ารายการสำหรับประเภทข้อมูลเฉพาะ

การสร้างอาร์เรย์

ในการใช้งาน array โมดูล คุณต้องนำเข้าก่อน:

from array import array

เมื่อนำเข้าแล้ว คุณสามารถสร้างอาร์เรย์โดยใช้ array() ตัวสร้าง:

arr = array('i', [1, 2, 3, 4, 5])
print(arr)

ที่นี่ 'i' อาร์กิวเมนต์บ่งชี้ว่าอาร์เรย์จะเก็บลายเซ็นไว้ จำนวนเต็ม. มีรหัสประเภทอื่นๆ อีกหลายประเภท เช่น 'f' สำหรับการลอยตัวและ 'd' สำหรับคู่

การเข้าถึงและการแก้ไของค์ประกอบ

คุณสามารถเข้าถึงและแก้ไของค์ประกอบในอาร์เรย์ได้เหมือนกับที่คุณทำกับรายการ:

print(arr[2]) 

และตอนนี้ เรามาแก้ไของค์ประกอบโดยเปลี่ยนค่าเป็น 6:

arr[2] = 6
print(arr) 

วิธีการอาร์เรย์

พื้นที่ array module มีหลายวิธีในการจัดการกับอาร์เรย์:

  • append() – เพิ่มองค์ประกอบที่ส่วนท้ายของอาร์เรย์:

    arr.append(7)
    print(arr) 
  • extend() – ผนวกองค์ประกอบที่สามารถทำซ้ำได้ต่อท้าย:

    arr.extend([8, 9])
    print(arr) 
  • pop() – ลบและส่งคืนองค์ประกอบในตำแหน่งที่กำหนด:

    arr.pop(2)
    print(arr) 
  • remove(): ลบการเกิดขึ้นครั้งแรกของค่าที่ระบุ:

    arr.remove(2)
    print(arr) 
  • reverse(): กลับลำดับของอาร์เรย์:

    arr.reverse()
    print(arr) 

หมายเหตุ มีวิธีการมากกว่าที่เราระบุไว้ที่นี่ อ้างถึง เอกสาร Python อย่างเป็นทางการ เพื่อดูรายการวิธีการที่มีอยู่ทั้งหมดใน array โมดูล.

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

อาร์เรย์ NumPy

NumPy ย่อมาจาก Numerical Python เป็นแพ็คเกจพื้นฐานสำหรับการคำนวณเชิงตัวเลขใน Python หนึ่งในคุณสมบัติหลักของมันคือความทรงพลัง อ็อบเจ็กต์อาร์เรย์ N มิติซึ่งนำเสนอการดำเนินการที่รวดเร็วบนอาร์เรย์ รวมถึงทางคณิตศาสตร์ ตรรกะ การจัดการรูปร่าง และอื่นๆ

อาร์เรย์ NumPy มีความหลากหลายมากกว่าในตัวของ Python array และเป็นเนื้อหาหลักในโครงการวิทยาศาสตร์ข้อมูลและการเรียนรู้ของเครื่อง

เหตุใดจึงใช้อาร์เรย์ NumPy

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

แม้ว่ารายการและอาร์เรย์ในตัวของ Python จะเป็นมิติเดียว แต่อาร์เรย์ NumPy ก็สามารถเป็นได้ หลายมิติทำให้เหมาะสำหรับการแทนเมทริกซ์หรือเทนเซอร์

ดูคู่มือเชิงปฏิบัติสำหรับการเรียนรู้ Git ที่มีแนวทางปฏิบัติที่ดีที่สุด มาตรฐานที่ยอมรับในอุตสาหกรรม และเอกสารสรุปรวม หยุดคำสั่ง Googling Git และจริงๆ แล้ว เรียน มัน!

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

หมายเหตุ เมื่อคุณทราบขนาดของข้อมูลล่วงหน้า การจัดสรรหน่วยความจำล่วงหน้าสำหรับอาร์เรย์ (โดยเฉพาะใน NumPy) อาจนำไปสู่การปรับปรุงประสิทธิภาพได้

การสร้างอาร์เรย์ NumPy

หากต้องการใช้ NumPy คุณต้องติดตั้งก่อน (pip install numpy) จากนั้นนำเข้า:

import numpy as np

เมื่อนำเข้าแล้ว คุณสามารถสร้างอาร์เรย์ NumPy โดยใช้ไฟล์ array() ฟังก์ชั่น:

arr = np.array([1, 2, 3, 4, 5])
print(arr) 

คุณยังสามารถสร้างอาร์เรย์หลายมิติได้:

matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(matrix)

สิ่งนี้จะทำให้เรา:

[[1 2 3] [4 5 6] [7 8 9]]

นอกจากวิธีพื้นฐานที่เราสามารถสร้างอาร์เรย์ได้แล้ว NumPy ยังมอบวิธีที่ชาญฉลาดอื่นๆ ที่เราสามารถสร้างอาร์เรย์ได้อีกด้วย หนึ่งในนั้นคือ arange() วิธี. มันสร้างอาร์เรย์ที่มีค่าที่เพิ่มขึ้นอย่างสม่ำเสมอ:

arr = np.arange(10)
print(arr) 

อีกอย่างหนึ่งคือ linspace() วิธีการซึ่งสร้างอาร์เรย์ที่มีจำนวนองค์ประกอบที่ระบุ โดยมีระยะห่างเท่ากันระหว่างค่าเริ่มต้นและค่าสิ้นสุดที่ระบุ:

even_space = np.linspace(0, 1, 5)
print(even_space) 

การเข้าถึงและการแก้ไของค์ประกอบ

การเข้าถึงและแก้ไของค์ประกอบในอาร์เรย์ NumPy นั้นใช้งานง่าย:

print(arr[2]) arr[2] = 6
print(arr) 

ทำสิ่งเดียวกันนี้กับอาร์เรย์หลายมิติ:

print(matrix[1, 2]) matrix[1, 2] = 10
print(matrix)

จะเปลี่ยนค่าขององค์ประกอบในแถวที่สอง (index 1) และคอลัมน์ที่สาม (index 2):

[[1 2 3] [4 5 20] [7 8 9]]

การเปลี่ยนรูปร่างของอาร์เรย์

NumPy มีฟังก์ชันและวิธีการมากมายในการจัดการและดำเนินการกับอาร์เรย์ ตัวอย่างเช่น คุณสามารถใช้ reshape() วิธีการ เปลี่ยนรูปร่างของอาร์เรย์. สมมติว่าเรามีอาร์เรย์ง่ายๆ:

import numpy as np arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
print("Original Array:")
print(arr) 

และเราอยากแปลงมันเป็นเมทริกซ์ขนาด 3×4 สิ่งที่คุณต้องทำคือใช้ reshape() วิธีการที่มีมิติข้อมูลที่ต้องการส่งผ่านเป็นอาร์กิวเมนต์:


reshaped_arr = arr.reshape(3, 4)
print("Reshaped Array (3x4):")
print(reshaped_arr)

ซึ่งจะส่งผลให้:

Reshaped Array (3x4):
[[ 1 2 3 4] [ 5 6 7 8] [ 9 10 11 12]]

การคูณเมทริกซ์

พื้นที่ numpy.dot() ใช้วิธีการสำหรับ การคูณเมทริกซ์. มันจะส่งกลับผลคูณดอทของสองอาร์เรย์ สำหรับอาร์เรย์หนึ่งมิติ มันคือ สินค้าภายใน ของอาร์เรย์ สำหรับอาร์เรย์ 2 มิติจะเทียบเท่ากับ การคูณเมทริกซ์และสำหรับ ND มันคือ a สินค้ารวม เหนือแกนสุดท้ายของอาร์เรย์แรกและแกนที่สองถึงสุดท้ายของอาร์เรย์ที่สอง

มาดูกันว่ามันทำงานอย่างไร ขั้นแรก เรามาคำนวณผลคูณดอทของอาร์เรย์ 1-D สองตัวกัน (ผลคูณภายในของเวกเตอร์):

import numpy as np vec1 = np.array([1, 2, 3])
vec2 = np.array([4, 5, 6])
dot_product_1d = np.dot(vec1, vec2) print("Dot product of two 1-D arrays:")
print(dot_product_1d) 

ซึ่งจะส่งผลให้:

Dot product of two 1-D arrays:
32

32 อันที่จริงแล้วคือผลคูณภายในของอาร์เรย์ทั้งสอง – (14 + 25 + 3*6). ต่อไป เราสามารถทำการคูณเมทริกซ์ของอาร์เรย์ 2 มิติสองตัวได้:


mat1 = np.array([[1, 2], [3, 4]])
mat2 = np.array([[2, 0], [1, 3]])
matrix_product = np.dot(mat1, mat2) print("Matrix multiplication of two 2-D arrays:")
print(matrix_product) 

ซึ่งจะให้เรา:

Matrix multiplication of two 2-D arrays:
[[ 4 6] [10 12]]

อาร์เรย์ NumPy เป็นก้าวสำคัญที่เพิ่มขึ้นจากรายการในตัวของ Python และ array โดยเฉพาะอย่างยิ่งสำหรับการคำนวณทางวิทยาศาสตร์และคณิตศาสตร์ ประสิทธิภาพเมื่อรวมกับฟังก์ชันการทำงานที่หลากหลายของไลบรารี NumPy ทำให้กลายเป็นเครื่องมือที่ขาดไม่ได้สำหรับทุกคนที่ต้องการดำเนินการเชิงตัวเลขใน Python

สรุป

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

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

ประทับเวลา:

เพิ่มเติมจาก สแต็ค