φυβλαςのβλογ
phyblas的博客



การใส่ความสามารถเพิ่มเติมให้สายอักขระใน javascript สามารถใช้ sprintf ได้
เขียนเมื่อ 2019/07/07 14:45
แก้ไขล่าสุด 2024/02/16 20:53
ใครที่เคยใช้ภาษาซี หรือไพธอน รูบี หรือ php มาก่อน คงจะชินกับการแปลงข้อมูลตัวเลขเป็นสายอักขระโดยใช้ %d %f %x พวกนี้

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

ฟังก์ชันนี้แรกเริ่มเดิมทีมาจากภาษาซี โดยที่เวลาใช้ฟังก์ชัน printf จะกำหนดรูปแบบแล้วก็ตัวแปรที่จะแทนค่าเข้าไปได้อิสระ เช่น printf("%02d__%.3f",1,2) จะแสดงค่า 01__2.000 ออกมา

แต่ถ้าแค่ต้องการแปลงเป็นสายอักขระเฉยๆเก็บไว้ในตัวแปรปกติจะใช้ฟังก์ชันชื่อ sprintf แทน

ฟังก์ชัน sprintf ได้ถูกนำมาใส่ใน php เช่นกัน วิธีใช้ก็เหมือนในภาษาซี http://www.w3school.com.cn/php/func_string_sprintf.asp

ไพธอนและรูบีก็ได้นำความสามารถของฟังก์ชันนี้มาใช้ แต่ปรับให้ใช้กับตัวดำเนินการ % แทน

เช่น ถ้าเป็นในไพธอน ปกติเขียนสายอักขระแล้วต่อด้วย % แล้วตามด้วยทูเพิลของค่าที่ต้องการป้อนเข้า เช่น แบบนี้ '%06d %.5f'%(112,129.3) จะได้ '000112 129.30000'

ส่วนในรูบีจะเขียนเป็น '%06d %.5f'%[112,129.3] ผลที่ได้ก็เหมือนกัน

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

ใช่แล้ว เมื่อไม่มีอยู่ก็สร้างขึ้นมาใหม่ซะเองเลยสิ

ว่าแล้วก็ลองเขียนขึ้นเองดูโดยใช้เรกูลาร์เอ็กซ์เพรชชัน (regex)

เราสามารถใส่เมธอดใหม่ให้คล้ายกับเวลาที่ไพธอนหรือรูบีใช้ % โดยในที่นี้จะให้เขียนเป็น '%06d %.5f'.$(112,129.3) ที่จริงอยากให้ใช้ % ได้เหมือนกัน แต่ไม่มีวิธีที่จะทำได้ เลยใช้ $ แทน

หากใส่ฟังก์ชันลงไปให้ String.prototype.$ ก็จะสามารถเรียกใช้ได้จากสายอักขระทุกตัว

ต่อไปนี้เป็นโค้ดที่ใช้
String.prototype.$ = function (...a) {
  i = -1
  return this.replace(
    /(%+)(#+)?(\+)?(0)?(\d+|\*)?(\.(\d+))?([bBcdeEfFgGiosuxX])/g,
    function (...m) {
      /* คำอธิบายตัวแปร
      m[1]: กลุ่มเครื่องหมาย % ด้านหน้า
      m[2]: กลุ่มเครื่องหมาย # ด้านหน้า ถ้ามี (มีผลกับ b, o, x, X)
      m[3]: เครื่องหมาย + ถ้ามี
      m[4]: ถ้ามีเลข 0 ให้เติมด้วย 0 ถ้าไม่มีให้เติมด้วยช่องว่าง
      m[5]: ส่วนที่บอกว่าจะเติมสายอักขระให้ยาวอย่างต่ำกี่ตัว
      m[6]: ส่วนที่บอกว่าจะเอาเลขทศนิยมกี่ตำแหน่ง
      m[7]: จำนวนตำแหน่งทศนิยม
      m[8]: ตัวกำหนดรูปแบบการเขียน
      */

      // เริ่มจากจัดการกับเครื่องหมาย % ด้านหน้า
      if (m[1].length % 2 == 0) {
        // ถ้าจำนวนของ % เป็นเลขคู่ แค่ลดจำนวน % เหลือครึ่ง
        return m[0].replace(/%%/g, "%")
      }
      // ถ้าจำนวนของ % เป็นเลขคี่ จึงดำเนินการแทนค่า
      i++

      // ขึ้นต้นด้วย % ที่นำหน้า ถ้ามี แต่เหลือครึ่งเดียว
      s = m[1].slice(0, m[1].length / 2)

      k = ""
      // ใส่เรื่องหมาย + ถ้าระบุ + และค่าเป็นจำนวนบวกและไม่ใช่สายอักขระ
      if (m[3] && !"cs".includes(m[8]) && a[i] > 0) {
        k = "+"
      }
      
      // จำนวนที่จะเติม 0 หรือช่องว่าง
      p = 0
      // ถ้าใส่เป็น * มาให้ดึงตัวเลขข้างหน้ามาใช้แล้วแปลงค่าตัวถัดไปแทน
      if (m[5] == "*") {
        p = a[i]
        i++ // เลื่อนไปอีกตำแหน่ง
      } // ถ้ามีโดยใส่มาเป็นตัวเลขให้ใช้ตัวเลขนั้น
      else if (m[5]) {
        p = parseInt(m[5])
      }

      // แปลงข้อมูลตามชนิดที่ใส่มา
      // เลขฐาน 2
      if ("bB".includes(m[8])) {
        // ถ้ามี #
        if (m[2]) {
          if (m[8] == "B") k += "0B"
          else k += "0b"
        }
        k += a[i].toString(2)
      } // โค้ดตัวหนังสือ
      else if (m[8] == "c") {
        k += String.fromCharCode(a[i])
      } // เลขจำนวนเต็ม
      else if ("diu".includes(m[8])) {
        if (typeof (a[i]) != "number")
          throw TypeError // ถ้าไม่ใช่ตัวเลขก็ให้เกิดข้อยกเว้นขึ้น
        k += parseInt(a[i])
      } // เลขทศนิยมในรูป E
      else if ("eE".includes(m[8])) {
        if // ถ้าระบุจำนวนตำแหน่งทศนิยม
          (m[6]) e = a[i].toExponential(m[7])
        else // ถ้าไม่ระบุจำนวนตำแหน่งทศนิยมก็ให้เป็น 6
          e = a[i].toExponential(6)
        // กรณีที่เลขหลัง +- มีหลักเดียว เติม 0 อีกตัว
        if ("+-".includes(e[e.length - 2]))
          e = e.replace("e+", "e+0").replace("e-", "e-0")
        if (m[8] == "E") // กรณี E เป็นตัวพิมพ์ใหญ่
          e = e.replace("e", "E")
        k += e
      } // เลขทศนิยม
      else if ("fF".includes(m[8])) {
        if (m[6]) // ถ้าระบุจำนวนตำแหน่งทศนิยม
          k += a[i].toFixed(m[7])
        else // ถ้าไม่ระบุจำนวนตำแหน่งทศนิยมก็ให้เป็น 6
          k += a[i].toFixed(6)
      } // เลขที่ปรับรูปตามสมควร
      else if ("gG".includes(m[8])) {
        if (typeof (a[i]) != "number")
          throw TypeError // ถ้าไม่ใช่ตัวเลขก็ให้เกิดข้อยกเว้นขึ้น
        if (a[i] >= 0.0001 && a[i] < 1000000) // กรณีเลขไม่มากหรือน้อยเกินไป
          k += a[i]
        else { // กรณีเลขมากหรือน้อยจนถึงระดับหนึ่ง
          e = a[i].toExponential()
          // กรณีที่เลขหลัง +- มีหลักเดียว เติม 0 อีกตัว
          if ("+-".includes(e[e.length - 2]))
            e = e.replace("e+", "e+0").replace("e-", "e-0")
          if (m[8] == "G") // กรณี G เป็นตัวพิมพ์ใหญ่
            e = e.replace("e", "E")
          k += e
        }
      } // เลขฐาน 8
      else if (m[8] == "o") {
        if (m[2]) k += '0o' // ถ้ามี #
        k += a[i].toString(8)
      } // เลขฐาน 16 โดยใช้ตัวพิมพ์เล็ก
      else if (m[8] == "x") {
        if (m[2]) k += '0x' // ถ้ามี #
        k += a[i].toString(16)
      } // เลขฐาน 16 โดยใช้ตัวพิมพ์ใหญ่
      else if (m[8] == "X") {
        if (m[2]) k += '0X' // ถ้ามี #
        k += a[i].toString(16).toUpperCase()
      } // สายอักขระธรรมดา
      else {
        k += a[i]
      }

      // ใส่ 0 หรือช่องว่าง
      _0 = ""
      if (p) {
        n0 = p - k.length
        while (n0 > 0) {
          n0--
          // ใส่ 0 ถ้าระบุ 0 และไม่ใช่สายอักขระ
          if (!"cs".includes(m[8]) && m[4] == "0")
            _0 += "0"
          // ใส่ช่องว่าง
          else
            _0 += " "
        }
      }

      // ถ้าเลือกเติม 0 และมีเครื่องหมาย +- อยู่ให้แทรกไว้หลัง +-
      if ("+-".includes(k[0]) && _0[0] == "0")
        s += k[0] + _0 + k.slice(1)
      else // ถ้าไม่มี +- ก็เติม 0 หรือช่องว่างไปเลย
        s += _0 + k
      return s
    }
  )
}

// ทดลองใช้
ss = `%+04d
%+4d
%+3d
%%d
%%%%02d
%%%d
%8.3f
%9.3E
%#03o
%#05x
%+07X
%#b
%c
%+7s
%7G
%g
%0*d
%0*.2f`
console.log(ss.$(1, 1.1, -2, 3.1, 4.2, 0.0053, 9, 1965, 1965, 61, 100, "ห", 0.02, 2e-5, 5, 1, 7, 1.11))
ผลที่ได้
+001
  +1
 -2
%d
%%02d
%3
   4.200
5.300E-03
0o11
0x7ad
+0007AD
0b111101
d
      ห
   0.02
2e-05
00001
0001.11

ที่จริงผลบางส่วนอาจไม่ได้เหมือนกับ sprintf ของจริงทั้งหมด แต่ก็ถือว่าใช้แทนได้ตามวัตถุประสงค์ที่ต้องการ

ข้างล่างนี้คือพื้นที่ทดสอบ sprintf โดยใช้โค้ดข้างต้นนี้ในการแปลงอักษร ลองทดสอบใช้กันดูได้

sprintf

ข้อมูล
สายอักขระ
ผลลัพธ์



-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> javascript

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

目录

从日本来的名言
模块
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
机器学习
-- 神经网络
javascript
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  查看日志

  推荐日志

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ