ผมผิดตรงไหนครับ ขอผู้รู้ช่วยหน่อยครับ

บอกเราว่าเกิดอะไรขึ้น:
อธิบายปัญหาของคุณโดยละเอียดที่นี่

  **รหัสของคุณ**

function telephoneCheck(str) {
if(str.indexOf("(") === -1 && str.indexOf(")") > -1){
  return false;
}if(str.indexOf(")") - str.indexOf("(") >= 5){
  return false;
}if(str[0] === "-"){
  return false;
}
let polishedPhone = str.replace(/-| /g, "");

if(polishedPhone.indexOf("(") < polishedPhone.indexOf(")")){
  polishedPhone = polishedPhone.replace(/\(|\)/g, "");
}
console.log(polishedPhone);
if(polishedPhone.length === 10){
  return true;
}else if(polishedPhone.length === 11 && polishedPhone[0] === "1"){
  return true;
}
return false;
}
let result = telephoneCheck("1 (555) 555-5555");

  **ข้อมูลเบราว์เซอร์ของคุณ:**

ตัวแทนผู้ใช้คือ: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15

Challenge: Telephone Number Validator

ไปสู่ the challenge:

จริงๆ ปัญหานี้ ส่วนตัวมองว่าใช้ regular expression แก้ง่ายกว่าครับ
โดยเราสามารถใช้ ฟังก์ชัน test() เพื่อตรวจสอบว่า string ใดๆ match กับ pattern ของ regex หรือไม่ หาก match กัน ก็จะคืนค่า true แต่ถ้าไม่ จะคืนค่า false ซึ่งสามารถนำมาประยุกต์ใช้กับโจทย์นี้ได้ครับ เราจะสังเกตูได้ว่า หมายเลขโทรศัพท์นั้น มีรูปแบบ ที่ค่อนข้างคล้ายคลึงกัน

555-555-5555
(555)555-5555
(555) 555-5555
555 555 5555
5555555555
1 555 555 5555

จากรูปแบบที่โจทย์ให้ด้านบน เราจะได้ข้อมูล ดังนี้

  1. หมายเลขโทรศัพท์ประกอบด้วย ตัวเลข 10 ตัว (ไม่รวมเลขประเทศที่อยู่ด้านหน้า)
  2. หมายเลขจะแบ่งเป็นตัวเลข ได้ 3 กลุ่ม คือ
    2.1 กลุ่ม สามตัวแรก
    2.2 กลุ่ม สามตัวกลาง
    2.3 กลุ่ม สี่ตัวหลัง
  3. ในสามกลุ่มตัวตัวเลขที่กล่าว กลุ่มสามตัวแรก สามารถมีวงเล็บได้ หรือจะไม่มีก็ได้
  4. กลุ่มตัวเลขทั้งสามกลุ่มถูกแบ่งด้วย ขีด ช่องว่าง หรือ ติดกันเลยก็มี
  5. ตัวเลขประเทศต้องเป็นเลข 1 เท่านั้น ซึ่งอาจจะมีหรือไม่มีก็ได้

ทีนี้เราลองเอาข้อมูลดังกล่าว มาเขียนเป็น regex กันนะครับ

  1. หมายเลขโทรศัพท์ประกอบด้วย ตัวเลข 10 ตัว (ไม่รวมเลขประเทศที่อยู่ด้านหน้า)
//ข้อนี้ข้ามนะ  เราจะตรวจสอบตั้งแต่ข้อสอง
  1. หมายเลขจะแบ่งเป็นตัวเลข ได้ 3 กลุ่ม คือ
    2.1 กลุ่ม สามตัวแรก
    2.2 กลุ่ม สามตัวกลาง
    2.3 กลุ่ม สี่ตัวหลัง
let my_regex = /\d{3}\d{3}\d{4}/

\d เป็นการ match กับตัวเลข การเติม {n} ต่อท้ายเป็นการบอกจำนวนที่ต้องการ match โดยที่ n เป็นจำนวนที่ต้องการ match เช่น \d{3} หมายความว่าต้อง match กับ ตัวเลขอะไรก็ได้สามตัว ซึ่ง \d{3}\d{3}\d{4} หมายความว่า match กับ ตัวเลขอะไรก็ได้สามตัวและต่อด้วยตัวเลขอะไรก็ได้สามตัวและต่อด้วยต่อด้วยตัวเลขอะไรก็ได้สี่ตัว ซึ่งใน step หากเราลอง รันโค้ดด้านล่าง จะได้ผลลัพธ์เป็นค่า true เนี่องจาก my_string ที่มีค่า 5555555555 ตรงกับ pattern ที่เราเขียนไว้นั่นเอง

let my_string = "5555555555"
let my_regex = /\d{3}\d{3}\d{4}/
let result = my_regex.test(my_string) 
console.log(result)  // true
  1. ในสามกลุ่มตัวตัวเลขที่กล่าว กลุ่มสามตัวแรก สามารถมีวงเล็บได้ หรือจะไม่มีก็ได้
    ในกรณีที่เราต้องการตรวจสอบ เครื่องหมายวงเล็บ ซึ่งใน regex มองว่าเป็นอักขระพิเศษ เราจำเป็นต้องใส่ \ลงไป ด้านหน้าอักขระพิเศษ ซี่งในกรณีนี้คือ วงเล็บ เช่น \( และ \) อย่างนี้เป็นต้น ตามที่โจทย์กล่าว ตัวแรกสามตัวแรกสามารถมีวงเล็บได้ ผมจึงปรับเปลี่ยน โค้ดจากข้อสองด้านบน เป็นแบบนี้
// old => /\d{3}\d{3}\d{4}/
let my_regex = /\(\d{3}\)\d{3}\d{4}/ 
// ใน \d{3} อันแรก มีการเพิ่มวงเล็บเข้าไป จึงเป็น \(\d{3}\)

ดูเหมือนว่า เราจะสามารถเคลียร์ข้อสามได้แล้ว แต่ยัง!! มันยังไม่หมด เพราะในข้อสามนั้นกล่าวไว้ว่า " กลุ่มสามตัวแรก สามารถมีวงเล็บได้ หรือจะไม่มีก็ได้" จากประโยคเราจำเป็นตัองตรวจสอบ ในกรณีที่ ไม่มีวงเล็บด้วย ซึ่งในที่นี้ ผมจะใช้เงื่อนไข | ซึ่งเทียบเท่ากับ “หรือ” นั่นเอง ในกรณีเราต้องเปลี่ยนแปลง โค้ดด้านบนเล็กน้อย

// old =>  /\(\d{3}\)\d{3}\d{4}/ 
let my_regex = /(\(\d{3}\)|\d{3})\d{3}\d{4}/ 
// ใน \d{3} อันแรก มีการ เปลี่ยนแปลง จาก \(\d{3}\)  เป็น (\(\d{3}\)|\d{3})

(\(\d{3}\)|\d{3}) อาจจะดูลายตาสักหน่อย แต่สามารถอธิบายได้ดังนี้

  • การใส่ วงเล็บ () ครอบเสมือนว่าเป็นการจัดกลุ่ม
  • ในกลุ่มนั้น จะเห็นว่ามี เงื่อนไข | ขั้นอยู่ตรงกลางด้วย ซึ่งหมายความว่า หาก string ตรงกับ อันใดอันหนึ่งในนั้น ก็จะถูก match ด้วย
  • เราจะเห็นว่าฝั่งนึง เป็น \(\d{3}\) ซึ่งจะ match กับ ตัวเลขที่มีวงเล็บครอบ
  • อีกผั่งนึง เป็น \d{3} ซึ่งจะ match กับ ตัวเลขที่ไม่มีวงเล็บครอบ
let my_regex = /(\(\d{3}\)|\d{3})\d{3}\d{4}/ 
console.log(my_regex.test("5555555555")) // ไม่มีวงเล็บครอบ => true
console.log(my_regex.test("(555)5555555")) // มีวงเล็บครอบ => true
  1. กลุ่มตัวเลขทั้งสามกลุ่มถูกแบ่งด้วย ขีด ช่องว่าง หรือ ติดกันเลยก็มี
    ในที่นี้ เราสามารถตรวจสอบขีดได้จาก - และ ช่องว่างได้จาก \s ในกรณีนี้ผมจะนำสองค่าดังกล่าว มาใส่ไว้ใน เซต ในลักษณะนี้ [-\s] หลังจากนั้นผมจะใส่เครื่องหมาย * หลัง เซตในลักษณะนี้ [-\s]* เพื่อว่าในกรณีที่อักษรใดๆในเซตนั้นไม่ปรากฏเลยก็จะ match เช่นกัน หลังจากนั้นผมก็จะนำเซตดังกล่าวมาขั้นไว้ระหว่างกลุ่มของตัวเลข
// old => /(\(\d{3}\)|\d{3})\d{3}\d{4}/
let my_regex = /(\(\d{3}\)|\d{3})[-\s]*\d{3}[-\s]*\d{4}/ 

หากเรานำmy_regex มาทดสอบกับข้อความ ต่อไปนี้ จะได้ผลลัพธ์ เป็น true ทั้งหมด

555-555-5555
(555)555-5555
(555) 555-5555
555 555 5555
5555555555
  1. ตัวเลขประเทศต้องเป็นเลข 1 เท่านั้น ซึ่งอาจจะมีหรือไม่มีก็ได้
    เดินทางมาถึงข้อสุดท้ายคคือการตรวจสอบตัวเลขประเทศ โดยทั่วไปเราก็ใส่ 1[-\s]* ลงไปเลยก็น่าจะได้ แต่ว่าเราต้องตรวจสอบ เผื่อในกรณีที่ไม่มีด้วย ซึ่งในที่นี้เราอาจจะคิดว่าต้องใช้เครื่องหมาย * แต่ส่วนตัวผมใช้เครื่องหมาย ? แทน เนื่องจากเครื่องหมาย * นั้นเป็นการตรวจสอบว่า มีอักษรนั้น จำนวน 0 ตัวหรือมากกว่านั้น แต่ ? เป็นการตรวจสอบว่าอักษรนั้นมี จำนวน 0 ตัว หรือ 1 ตัว เพียงเท่านั้นซึ่งเหมาะสมกับสถานการณ์นี้มากกว่า ดังนั้น ผลลัพธ์จึงเป็นการ เพิ่ม 1?[-\s]* ลงไปด้านหน้า
// old => /(\(\d{3}\)|\d{3})[-\s]*\d{3}[-\s]*\d{4}/ 
let my_regex = /1?[-\s]*(\(\d{3}\)|\d{3})[-\s]*\d{3}[-\s]*\d{4}/ 
console.log(my_regex.test("5555555555")) // ไม่มีเลขประเทศ=> true
console.log(my_regex.test("1 555 555 5555")) // มีเลขประเทศ => true

ท้ายที่สุด ผมได้เพิ่ม ^ ลงไปด้านหน้า และ $ ลงไปด้านท้ายเพื่อตรวจสอบว่า string ใดๆ ได้เริ่มต้น และจบด้วย
ใดๆก็ตามที่คิดกับสัญลักษณ์ ซึ่งในที่นี้คือ ลงท้ายด้วย \d{4} และขึ้่นต้น ด้วย 1 หรือ (\(\d{3}\)|\d{3})

// old => /1?[-\s]*(\(\d{3}\)|\d{3})[-\s]*\d{3}[-\s]*\d{4}/ 
let my_regex = /^1?[-\s]*(\(\d{3}\)|\d{3})[-\s]*\d{3}[-\s]*\d{4}$/ 

เพียงเท่านี้ก็จะครอบคลุมรูปแบบของหมายเลขโทรศัพท์ ตามที่โจทย์กำหนดแล้ว