以下の内容はhttps://www.m3tech.blog/entry/dart-quineより取得しました。


DartでQuineをダーッと書きました

デジスマチームの荒谷(@_a_akira)です。

最近、社内で各言語のQuineを作成するブームが起きています。この流れに乗って、私もDartでQuineを作成してみましたので、本記事でその仕組みを解説します。

エムスリーは2025年9月10日(水)から12日(金)に開催されるDroidKaigi 2025にゴールドスポンサー、また2025年11月13日(木)に開催のFlutter Kaigi 2025にはシルバースポンサーとして協賛します。 イベントでは、今回作成したQuineをデザインしたクリアファイルをノベルティとして配布予定です。ぜひ弊社ブースにお立ち寄りいただき、お手に取ってみてください!

Quineとは

Quineとは自身のソースコードと同じ文字列を出力するプログラムのことです。社内ではすでに様々な言語?でQuineが作成されています。

DartのQuine

そして、今回私が作成したDartのQuineがこちらです。

import'dart:convert';void main(){var(w,e,l,c,o,m,E)=((joinM3)=>joinM3.group(0),(s,f)=>base64.decode(
f(s)),(s)=>s.split(':'),(s)=>s.replaceAll(RegExp(r'\s'),''),(b)=>List.generate(8,(l)=>(b>>(7-l))&1),
"""aW1wb3J0J2RhcnQ6Y29udmVydCc7dm9pZCBtYWluKCl7dmFyKHcsZSxsLGMsbyxtLEUpPSgoam9pbk0    zKT0+am9pbk0zL
mdyb3VwKDApL             ChzLGYpPT5iYXNlNjQuZGV             jb2RlKApmKHMpKSwo            cyk9PnMuc3B
saXQoJzonKSwoc y         k9PnMucmVwbGFjZUFsbCh            SZWdFeHAocidccycp                LCcnKSwoY
ik9Pkxpc3QuZ2VuZX         JhdGUoOCwobCk9PihiP         j4oNy1sKSkmMSksCiIi                   IiVzIiIi
LDAKKTtwcmludChSZ           WdFeHAoJy57MSwxM          DB9JykuYWxsTWF0Y2hl   cyhlKGwob       SlbMV0sY
ykuZXhwYW5kKG8pLm           1hcCgoZik9PmY9P            TA/KCgpe3ZhciB1PXV0ZjguZGVjb2R       lKGUobAo
obSlbMF0sYykpLnJl            cGxhY2VBbGwoJ            1xyXG4nLCcnKS5yZXBsYWNlRmlyc3Qo       JyVzJyxj
KG0pKTtyZXR1cm4gR             Tx1Lmxlbmd0a             D91W0UrK106Jyc7fSkoKTonICcpLm       pvaW4oCik
pLm1hcCh3KS5qb2l     u         KCdcbicpKT    t9        IC8vID09PT09PT0gV2UgYXJlIGh       pcmluZyEgPT
09PT09PSBXZWxjb21    lI        SA9PT09PT    09I        EpvaW4gTTMgZ3JvdXAgPT0             9PT09PT0gL
y8KLy8gPT09PT09P    T09P        T09PT09     PSB        GaW5kIHRoaXMgUXVpbmU                 gYXQ6IGh
0dHBzOi8vd3d3Lm0    zdGVj         aC5i     bG9n         L2VudHJ5L2RhcnQtcXVp  bmUgPT          09PT09
PT09PT09PT09PSAv    Lwo=:A         AA     AAAAA        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA        AAAAAA
AAPAAAAP/4AAAf/w    AAf/gAA        A     L/gAAD/        8AAH//gAAAH/AAAf8AAB///AAAAf/AA       D/wAAH
AH8AAAB/8AAf/gA     AAAfwAA             AH/4AD/         8AAAAB/AAAAf/wAP/4AAAAP4AAAD7/         gB5/g
AAAD+AAAAHn+APH     +AAAH/8A           AAA8P8B8         f4AAB//8AAADwf8Ph/wAADA/8AAAPA        /58H+A
AAAA/wAAA8B/vgP     8AAAAB/AA         AHwH/8B/wA        AAAP+AAAfAP/gH/AA  AAA/wAAB8Af       8AP8AAG
AD+AAAHgA/gA/wA    B4AfwAAN/sB       +Av/2AH//8A        AB//wDwD//4AH/+    AAAAAAAAAA       AAAAAAAA
AAAAAAAAAA  A        A  AAAAAAA      AAAAAAA A            A  AAAAAAAAAA                   AAAAAAAAAA
AAAAAAAAA               AAAAAAAA    AAAAAAAA                 AAAAAAAAAAAA              AAAAAAAA""",0
);print(RegExp('.{1,100}').allMatches(e(l(m)[1],c).expand(o).map((f)=>f==0?((){var u=utf8.decode(e(l
(m)[0],c)).replaceAll('\r\n','').replaceFirst('%s',c(m));return E<u.length?u[E++]:'';})():' ').join(
)).map(w).join('\n'));} // ======= We are hiring! ======= Welcome! ======= Join M3 group ======== //
// ================ Find this Quine at: https://www.m3tech.blog/entry/dart-quine ================ //

変数名やgroup関数を活かして、Welcome join M3 group というメッセージが隠れているのがおしゃれですね。

解説

基本的な仕組みは、先に紹介したKotlinやSwiftのQuineと同様です。 ソースコード全体から、データ部分(長い文字列)を %s に置き換えたテンプレートを作成します。そして、そのテンプレートと、M3のアスキーアート(AA)を0と1で表現したビットデータを : で結合し、Base64でエンコードします。

実行時には、このエンコードされたデータをデコードし、テンプレートの %s の部分にデータ自身を埋め込むことで、元のソースコードを復元して出力するという仕組みです。

このままでは読みにくいため、まずは分かりやすいようにフォーマットしてみましょう。

import 'dart:convert';

void main() {
  var (w, e, l, c, o, m, E) = (
    (joinM3) => joinM3.group(0),
    (s, f) => base64.decode(f(s)),
    (s) => s.split(':'),
    (s) => s.replaceAll(RegExp(r'\s'), ''),
    (b) => List.generate(8, (l) => (b >> (7 - l)) & 1),
    """aW1wb3J0J2RhcnQ6Y29udmVydCc7dm9pZCBtYWluKCl7dmFyKHcsZSxsLGMsbyxtLEUpPSgoam9pbk0    zKT0+am9pbk0zL
mdyb3VwKDApL             ChzLGYpPT5iYXNlNjQuZGV             jb2RlKApmKHMpKSwo            cyk9PnMuc3B
saXQoJzonKSwoc y         k9PnMucmVwbGFjZUFsbCh            SZWdFeHAocidccycp                LCcnKSwoY
ik9Pkxpc3QuZ2VuZX         JhdGUoOCwobCk9PihiP         j4oNy1sKSkmMSksCiIi                   IiVzIiIi
LDAKKTtwcmludChSZ           WdFeHAoJy57MSwxM          DB9JykuYWxsTWF0Y2hl   cyhlKGwob       SlbMV0sY
ykuZXhwYW5kKG8pLm           1hcCgoZik9PmY9P            TA/KCgpe3ZhciB1PXV0ZjguZGVjb2R       lKGUobAo
obSlbMF0sYykpLnJl            cGxhY2VBbGwoJ            1xyXG4nLCcnKS5yZXBsYWNlRmlyc3Qo       JyVzJyxj
KG0pKTtyZXR1cm4gR             Tx1Lmxlbmd0a             D91W0UrK106Jyc7fSkoKTonICcpLm       pvaW4oCik
pLm1hcCh3KS5qb2l     u         KCdcbicpKT    t9        IC8vID09PT09PT0gV2UgYXJlIGh       pcmluZyEgPT
09PT09PSBXZWxjb21    lI        SA9PT09PT    09I        EpvaW4gTTMgZ3JvdXAgPT0             9PT09PT0gL
y8KLy8gPT09PT09P    T09P        T09PT09     PSB        GaW5kIHRoaXMgUXVpbmU                 gYXQ6IGh
0dHBzOi8vd3d3Lm0    zdGVj         aC5i     bG9n         L2VudHJ5L2RhcnQtcXVp  bmUgPT          09PT09
PT09PT09PT09PSAv    Lwo=:A         AA     AAAAA        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA        AAAAAA
AAPAAAAP/4AAAf/w    AAf/gAA        A     L/gAAD/        8AAH//gAAAH/AAAf8AAB///AAAAf/AA       D/wAAH
AH8AAAB/8AAf/gA     AAAfwAA             AH/4AD/         8AAAAB/AAAAf/wAP/4AAAAP4AAAD7/         gB5/g
AAAD+AAAAHn+APH     +AAAH/8A           AAA8P8B8         f4AAB//8AAADwf8Ph/wAADA/8AAAPA        /58H+A
AAAA/wAAA8B/vgP     8AAAAB/AA         AHwH/8B/wA        AAAP+AAAfAP/gH/AA  AAA/wAAB8Af       8AP8AAG
AD+AAAHgA/gA/wA    B4AfwAAN/sB       +Av/2AH//8A        AB//wDwD//4AH/+    AAAAAAAAAA       AAAAAAAA
AAAAAAAAAA  A        A  AAAAAAA      AAAAAAA A            A  AAAAAAAAAA                   AAAAAAAAAA
AAAAAAAAA               AAAAAAAA    AAAAAAAA                 AAAAAAAAAAAA              AAAAAAAA""",
    0
  );
  print(RegExp('.{1,100}')
      .allMatches(e(l(m)[1], c)
          .expand(o)
          .map((f) => f == 0
              ? (() {
                  var u =
                      utf8.decode(e(l(m)[0], c)).replaceAll('\r\n', '').replaceFirst('%s', c(m));
                  return E < u.length ? u[E++] : '';
                })()
              : ' ')
          .join())
      .map(w)
      .join('\n'));
} // ======= We are hiring! ======= Welcome! ======= Join M3 group ======== //
// ================ Find this Quine at: https://www.m3tech.blog/entry/dart-quine ================ //

フォーマットしただけでも、少し処理の流れが見えてきました。 main関数は大きく分けて「変数宣言」と「printによる出力」の2つの部分で構成されています。

宣言部の詳細

このQuineは、AAを埋め込みつつ、100文字ごとに改行するために、変数名を1文字にしたり、型定義を省略したりといった工夫をしています。

各変数がどのような役割を持っているか、分かりやすい名前に書き換えてみましょう。

import 'dart:convert';
import 'dart:typed_data';

///  w → extractMatch - 正規表現マッチから文字列を抽出する関数
///  e → base64Decode - Base64デコードを行う関数
///  l → splitOnColon - 文字列をコロンで分割する関数
///  c → removeWhitespace - 空白文字を除去する関数
///  o → byteToBits - Byteをbit配列に変換する関数
///  m → encodedData - Base64エンコードされたデータ
///  E → charIndex - 文字のインデックスカウンター
void main() {
  // 1. 正規表現マッチから文字列を抽出する関数
  String? extractMatch(RegExpMatch? match) => match?.group(0);
  
  // 2. Base64デコードを行う関数
  Uint8List base64Decode(String source, String Function(String) formatter) => 
      base64.decode(formatter(source));
  
  // 3. 文字列をコロンで分割する関数
  List<String> splitOnColon(String source) => source.split(':');
  
  // 4. 空白文字を除去する関数
  String removeWhitespace(String source) => source.replaceAll(RegExp(r'\s'), '');
  
  // 5. Byteをbit配列に変換する関数
  List<int> byteToBits(int byte) => 
      List.generate(8, (int bitIndex) => (byte >> (7 - bitIndex)) & 1);

  // 6. Base64エンコードされたデータ(プログラムのソースとAAのデータ)
  String encodedData =
      """aW1wb3J0J2RhcnQ6Y29udmVydCc7dm9pZCBtYWluKCl7dmFyKHcsZSxsLGMsbyxtLEUpPSgoam9pbk0    zKT0+am9pbk0zL
mdyb3VwKDApL             ChzLGYpPT5iYXNlNjQuZGV             jb2RlKApmKHMpKSwo            cyk9PnMuc3B
saXQoJzonKSwoc y         k9PnMucmVwbGFjZUFsbCh            SZWdFeHAocidccycp                LCcnKSwoY
ik9Pkxpc3QuZ2VuZX         JhdGUoOCwobCk9PihiP         j4oNy1sKSkmMSksCiIi                   IiVzIiIi
LDAKKTtwcmludChSZ           WdFeHAoJy57MSwxM          DB9JykuYWxsTWF0Y2hl   cyhlKGwob       SlbMV0sY
ykuZXhwYW5kKG8pLm           1hcCgoZik9PmY9P            TA/KCgpe3ZhciB1PXV0ZjguZGVjb2R       lKGUobAo
obSlbMF0sYykpLnJl            cGxhY2VBbGwoJ            1xyXG4nLCcnKS5yZXBsYWNlRmlyc3Qo       JyVzJyxj
KG0pKTtyZXR1cm4gR             Tx1Lmxlbmd0a             D91W0UrK106Jyc7fSkoKTonICcpLm       pvaW4oCik
pLm1hcCh3KS5qb2l     u         KCdcbicpKT    t9        IC8vID09PT09PT0gV2UgYXJlIGh       pcmluZyEgPT
09PT09PSBXZWxjb21    lI        SA9PT09PT    09I        EpvaW4gTTMgZ3JvdXAgPT0             9PT09PT0gL
y8KLy8gPT09PT09P    T09P        T09PT09     PSB        GaW5kIHRoaXMgUXVpbmU                 gYXQ6IGh
0dHBzOi8vd3d3Lm0    zdGVj         aC5i     bG9n         L2VudHJ5L2RhcnQtcXVp  bmUgPT          09PT09
PT09PT09PT09PSAv    Lwo=:A         AA     AAAAA        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA        AAAAAA
AAPAAAAP/4AAAf/w    AAf/gAA        A     L/gAAD/        8AAH//gAAAH/AAAf8AAB///AAAAf/AA       D/wAAH
AH8AAAB/8AAf/gA     AAAfwAA             AH/4AD/         8AAAAB/AAAAf/wAP/4AAAAP4AAAD7/         gB5/g
AAAD+AAAAHn+APH     +AAAH/8A           AAA8P8B8         f4AAB//8AAADwf8Ph/wAADA/8AAAPA        /58H+A
AAAA/wAAA8B/vgP     8AAAAB/AA         AHwH/8B/wA        AAAP+AAAfAP/gH/AA  AAA/wAAB8Af       8AP8AAG
AD+AAAHgA/gA/wA    B4AfwAAN/sB       +Av/2AH//8A        AB//wDwD//4AH/+    AAAAAAAAAA       AAAAAAAA
AAAAAAAAAA  A        A  AAAAAAA      AAAAAAA A            A  AAAAAAAAAA                   AAAAAAAAAA
AAAAAAAAA               AAAAAAAA    AAAAAAAA                 AAAAAAAAAAAA              AAAAAAAA""";
  
  // 文字のindex counter
  int charIndex = 0;

  print(RegExp('.{1,100}')
      .allMatches(base64Decode(splitOnColon(encodedData)[1], removeWhitespace)
          .expand(byteToBits)
          .map((f) => f == 0
              ? (() {
                  var u = utf8
                      .decode(base64Decode(splitOnColon(encodedData)[0], removeWhitespace))
                      .replaceAll('\r\n', '')
                      .replaceFirst('%s', removeWhitespace(encodedData));
                  return charIndex < u.length ? u[charIndex++] : '';
                })()
              : ' ')
          .join())
      .map(extractMatch)
      .join('\n'));
}

それぞれの関数の役割を解説します。

1. extractMatch (元の w )

正規表現の Match オブジェクトから、マッチした部分の文字列を抽出します。

2. base64Decode (元の e)

Base64エンコードされた文字列をデコードします。 実際のコードは同じformatterを使っていますが、宣言を同時に行っている都合で、formatterは関数を渡す形になっています。

3. splitOnColon (元のl)

ソースコードのテンプレートとAAのデータを分離するために、文字列を : で分割します。

4. removeWhitespace (元のc)

Base64文字列に含まれる空白文字を正規表現で除去します。 こちらはいろいろな書き方があると思いますが、文字数調整のためこのようにしました。

5. byteToBits (元のo)

Uint8List の各バイトを8桁のビット(0か1)のリストに変換します。これはAAを表現するための処理です。 この処理は、以下のようにバイトを2進数文字列に変換する方法でも実現できますが、今回は文字数削減のためにビット演算を用いています。

var binaryString = aaBytes
      .map((byte) => byte.toRadixString(2).padLeft(8, '0'))
      .join();
String result = binaryString
     .split('')
     .map((bit) => bit == '0' ? (charIndex < restoredSource.length ? restoredSource[charIndex++] : '')  : ' ')
     .join();

print処理の詳細

次に出力部分を分解してみましょう。

  // A. 文字列の分離
  List<String> parts = splitOnColon(encodedData);
  String sourceData = parts[0];  // プログラムのソースコード部分
  String aaData = parts[1];  // AAデータ部分

  // B. AAから文字列を生成
  Uint8List aaBytes = base64Decode(aaData, removeWhitespace);
  Iterable<int> bits = aaBytes.expand(byteToBits);

  // C. プログラムソースを復元
  String restoredSource = utf8
      .decode(base64Decode(sourceData, removeWhitespace))
      .replaceAll('\r\n', '')
      .replaceFirst('%s', removeWhitespace(encodedData));

  // D. ビットパターンからAAになるようにmapping
  String result = bits
      .map((int bit) => bit == 0
      ? (charIndex < restoredSource.length ? restoredSource[charIndex++] : '')
      : ' ')
      .join();

  // E. 100文字毎に改行して出力
  print(RegExp('.{1,100}')
      .allMatches(result) 
      .map(extractMatch)
      .join('\n'));

A. データの分離

まず、エンコードされた文字列を : で分割し、ソースコードのテンプレートとAAのデータに分けます。

List<String> parts = splitOnColon(encodedData);
String sourceData = parts[0];  // プログラムのソースコード部分
String aaData = parts[1];  // AAデータ部分

B. AAのビット化

AAのデータをデコードし、byteToBits 関数を使って0と1のビットリストに変換します。

Uint8List aaBytes = base64Decode(aaData, removeWhitespace);
Iterable<int> bits = aaBytes.expand(byteToBits);

C. ソースコードの復元

ソース部分も空白と改行を削除してデコードし、冒頭に解説した通り、String部分をエンコードしたソースコードとAAの組み合わせに置き換えます。

String restoredSource = utf8
    .decode(base64Decode(sourceData, removeWhitespace))
    .replaceAll('\r\n', '')
    .replaceFirst('%s', removeWhitespace(encodedData));

D. AAへのマッピング

ビットリストを走査し、ビットが0の部分に復元したソースコードの文字を1文字ずつ配置し、ビットが1の部分には空白を配置します。これにより、AAが描画されます。 本来は文字数がピッタリ収まれば、lengthの判定は不要ですが、Quineの文字数調整のためにAA部分に余計な0を追加しているのでindex out of boundsの判定をしています。 ここは工夫の余地がありそうです。

String result = bits
    .map((int bit) => bit == 0
    ? (charIndex < restoredSource.length ? restoredSource[charIndex++] : '')
    : ' ')
    .join();

E. 整形して出力

最後に、生成された文字列を正規表現で100文字ごとに区切り、改行を挟んで出力します。これでQuineの完成です。

print(RegExp('.{1,100}')
    .allMatches(result) 
    .map(extractMatch)
    .join('\n'));

まとめ

一見複雑に見えるQuineも、細かく分解してみると意外とシンプルな処理の組み合わせでできていることがお分かりいただけたかと思います。 皆さんもぜひ、お気に入りの言語でQuine作りに挑戦してみてはいかがでしょうか。「Dartでもっと面白いQuineが書ける!」といったアイデアも大歓迎です!

今回ご紹介したDart版Quineのクリアファイルを、DroidKaigi 2025とFlutterKaigi 2025の弊社ブースで配布しますので、ぜひお立ち寄りください!

We are Hiring!

エムスリーでは、M3デジカルスマート診察券をはじめ、現時点で7つのアプリでFlutterが採用されています! Flutterに限らず、プログラミングが大好きなギークなエンジニアを募集しています!ご興味のある方は、ぜひ下記リンクからご応募ください。

エンジニア採用ページはこちら

jobs.m3.com

エンジニア新卒採用サイト! !

fresh.m3recruit.com

カジュアル面談! !

jobs.m3.com




以上の内容はhttps://www.m3tech.blog/entry/dart-quineより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14