φυβλαςのβλογ
บล็อกของ phyblas



การทำให้สายอักขระใน dart สามารถแปลงข้อความแบบ sprintf ได้เหมือนใน python หรือ ruby
เขียนเมื่อ 2024/02/16 20:01
แก้ไขล่าสุด 2024/04/08 19:28


ช่วงนี้เพิ่งได้เรียนรู้ภาษาดาร์ต (dart) และก็พบว่าเป็นภาษาที่น่าสนใจดี เขียนแล้วก็สนุกดี มีความสามารถอะไรหลายอย่างที่ช่วยให้สามารถเขียนได้ง่าย

แต่ว่าน่าเสียดายว่าภาษานี้ไม่สามารถใช้ความสามารถ sprintf คือการแปลงข้อมูลสายอักขระโดยใช้%d %f %s แบบในภาษา C หรือไพธอน หรือรูบีได้

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

>> การใส่ความสามารถเพิ่มเติมให้สายอักขระใน javascript สามารถใช้ sprintf ได้

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

ดังนั้นเราสามารถเพิ่มความสามารถให้กับสายอักขระ ให้เวลาตามด้วย % แล้วจะเกิดการแปลงขึ้นมา

ลองสร้างไฟล์ sprintf.dart ขึ้นมาดังนี้
extension Sprintf on String {
  // เขียนทับการดำเนินการด้วย % ให้กับออบเจ็กต์คลาส String
  String operator %(a) {
    // ถ้าไม่ใช่ลิสต์ก็ให้ทำเป็นลิสต์ไว้
    if (a is! List) {
      a = [a];
    }
    String e;
    int i = -1;
    return this.replaceAllMapped(
      RegExp(r'(%+)(#+)?(\+)?(0)?(\d+|\*)?(\.(\d+))?([bBcdeEfFgGiosuxX])'),
      (Match 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]!.replaceAll('%%', '%');
        }
        // ถ้าจำนวนของ % เป็นเลขคี่ จึงดำเนินการแทนค่า
        i++;

        // ขึ้นต้นด้วย % ที่นำหน้า ถ้ามี แต่เหลือครึ่งเดียว
        String s = m[1]!.substring(0, (m[1]!.length / 2).toInt());
        String k = '';
        // ใส่เรื่องหมาย + ถ้าระบุ + และค่าเป็นจำนวนบวกและไม่ใช่สายอักขระ
        if (m[3] != null && !'cs'.contains(m[8]!) && a[i] > 0) {
          k = '+';
        }

        // จำนวนที่จะเติม 0 หรือช่องว่าง
        int p = 0;
        // ถ้าใส่เป็น * มาให้ดึงตัวเลขข้างหน้ามาใช้แล้วแปลงค่าตัวถัดไปแทน
        if (m[5] == '*') {
          p = a[i];
          i++; // เลื่อนไปอีกตำแหน่ง
        } // ถ้ามีโดยใส่มาเป็นตัวเลขให้ใช้ตัวเลขนั้น
        else if (m[5] != null) {
          p = int.parse(m[5]!);
        }

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

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

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

จากนั้นแล้วก็สามารถหยิบมาใช้ได้โดย import เข้ามา
import 'sprintf.dart';

void main() {
  String ss = '''%+04d
%+4d
%+3d
%%d
%%%%02d
%%%d
%8.3G
%9.3E
%#03o
%#05x
%+07X
%#b
%c
%+7s
%7G
%g
%0*d
%0*.2f''';
  print(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.2
5.300E-03
0o11
0x7ad
+0007AD
0b111101
d
      ห
   0.02
2e-05
00001
0001.11

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

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

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

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


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

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

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

หมวดหมู่

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

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

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



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

  ค้นหาบทความ

  บทความแนะนำ

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

ไทย

日本語

中文