TeX, 216 byte (masing-masing 4 baris, 54 karakter)
Karena ini bukan tentang jumlah byte, ini tentang kualitas output typeset :-)
{\let~\catcode~`A13 \defA#1{~`#113\gdef}AGG#1{~`#1 13%
\global\let}GFF\elseGHH\fiAQQ{Q}AII{\ifxQ}AEE#1#2#3|{%
I#3#2#1FE{#1#2}#3|H}ADD#1#2|{I#1FE{}#1#2|H}ACC#1#2|{D%
#2Q|#1 }ABBH#1 {HI#1FC#1|BH}\gdef\S#1{\iftrueBH#1 Q }}
Cobalah secara Online! (Overleaf; tidak yakin cara kerjanya)
File tes lengkap:
{\let~\catcode~`A13 \defA#1{~`#113\gdef}AGG#1{~`#1 13%
\global\let}GFF\elseGHH\fiAQQ{Q}AII{\ifxQ}AEE#1#2#3|{%
I#3#2#1FE{#1#2}#3|H}ADD#1#2|{I#1FE{}#1#2|H}ACC#1#2|{D%
#2Q|#1 }ABBH#1 {HI#1FC#1|BH}\gdef\S#1{\iftrueBH#1 Q }}
\S{swap the a first and last letters of each word}
pwas eht a tirsf dna tasl setterl fo hace dorw
\S{SWAP THE A FIRST AND LAST LETTERS OF EACH WORD}
\bye
Keluaran:
Untuk LaTeX Anda hanya perlu boilerplate:
\documentclass{article}
\begin{document}
{\let~\catcode~`A13 \defA#1{~`#113\gdef}AGG#1{~`#1 13%
\global\let}GFF\elseGHH\fiAQQ{Q}AII{\ifxQ}AEE#1#2#3|{%
I#3#2#1FE{#1#2}#3|H}ADD#1#2|{I#1FE{}#1#2|H}ACC#1#2|{D%
#2Q|#1 }ABBH#1 {HI#1FC#1|BH}\gdef\S#1{\iftrueBH#1 Q }}
\S{swap the a first and last letters of each word}
pwas eht a tirsf dna tasl setterl fo hace dorw
\S{SWAP THE A FIRST AND LAST LETTERS OF EACH WORD}
\end{document}
Penjelasan
TeX adalah binatang aneh. Membaca kode normal dan memahaminya adalah suatu prestasi tersendiri. Memahami kode TeX yang dikaburkan melangkah lebih jauh. Saya akan mencoba membuat hal ini dapat dimengerti oleh orang-orang yang juga tidak mengenal TeX, jadi sebelum kita mulai di sini adalah beberapa konsep tentang TeX untuk membuat hal-hal lebih mudah diikuti:
Untuk (tidak begitu) pemula TeX mutlak
Pertama, dan yang paling item yang penting dalam daftar ini: kode tidak tidak harus dalam bentuk persegi panjang, meskipun budaya pop mungkin menyebabkan Anda berpikir begitu .
TeX adalah bahasa ekspansi makro. Anda dapat, sebagai contoh, mendefinisikan \def\sayhello#1{Hello, #1!}
dan kemudian menulis \sayhello{Code Golfists}
untuk mendapatkan TeX untuk dicetak Hello, Code Golfists!
. Ini disebut "undelimited macro", dan untuk memberikannya parameter pertama (dan hanya, dalam hal ini) Anda melampirkannya dalam kurung kurawal. TeX menghapus kawat gigi itu ketika makro mengambil argumen. Anda dapat menggunakan hingga 9 parameter: \def\say#1#2{#1, #2!}
lalu \say{Good news}{everyone}
.
Counterpart macro undelimited yang, tidak mengejutkan, yang dibatasi :) Anda bisa membuat definisi sebelumnya anak laki-laki lebih semantical : \def\say #1 to #2.{#1, #2!}
. Dalam hal ini parameter diikuti oleh apa yang disebut teks parameter . Teks parameter tersebut membatasi argumen makro ( #1
dibatasi oleh ␣to␣
, spasi yang disertakan, dan #2
dibatasi oleh .
). Setelah definisi itu, Anda dapat menulis \say Good news to everyone.
, yang akan diperluas ke Good news, everyone!
. Bagus bukan? :) Namun argumen yang dibatasi adalah (mengutip TeXbook ) “urutan token terpendek (mungkin kosong) dengan {...}
grup bertumpuk dengan benar yang diikuti dalam input oleh daftar token non-parameter khusus ini”. Ini berarti perluasan dari\say Let's go to the mall to Martin
akan menghasilkan kalimat yang aneh. Dalam hal ini Anda akan perlu untuk “menyembunyikan” yang pertama ␣to␣
dengan {...}
: \say {Let's go to the mall} to Martin
.
Sejauh ini baik. Sekarang segalanya mulai menjadi aneh. Ketika TeX membaca sebuah karakter (yang didefinisikan oleh "kode karakter"), TeX akan memberikan karakter itu "kode kategori" (catcode, untuk teman-teman :) yang mendefinisikan apa arti karakter itu. Kombinasi kode karakter dan kategori ini membuat token (lebih lanjut tentang itu di sini , misalnya). Yang menarik bagi kami di sini pada dasarnya adalah:
catcode 11 , yang mendefinisikan token yang dapat membuat urutan kontrol (nama mewah untuk makro). Secara default semua huruf [a-zA-Z] adalah catcode 11, jadi saya bisa menulis \hello
, yang merupakan satu urutan kontrol tunggal, sedangkan \he11o
urutan kontrol \he
diikuti oleh dua karakter 1
, diikuti oleh huruf o
, karena 1
bukan catcode 11. Jika saya lakukan \catcode`1=11
, sejak saat itu \he11o
akan menjadi satu urutan kontrol. Satu hal penting adalah bahwa kode-kode ditetapkan ketika TeX pertama kali melihat karakter di tangan, dan kode seperti itu dibekukan ... SELAMANYA! (syarat dan ketentuan berlaku)
catcode 12 , yang merupakan sebagian besar karakter lain, seperti 0"!@*(?,.-+/
dan sebagainya. Mereka adalah jenis catcode yang paling tidak istimewa karena hanya berfungsi untuk menulis hal-hal di atas kertas. Tapi, hei, siapa yang menggunakan TeX untuk menulis?!? (sekali lagi, syarat dan ketentuan berlaku)
catcode 13 , yang mana sih :) Sungguh. Berhentilah membaca dan lakukan sesuatu dari hidup Anda. Anda tidak ingin tahu apa itu catcode 13. Pernah dengar hari Jumat, tanggal 13? Tebak dari mana namanya berasal! Lanjutkan dengan risiko Anda sendiri! Karakter catcode 13, juga disebut karakter "aktif", bukan hanya karakter lagi, itu adalah makro itu sendiri! Anda dapat mendefinisikannya untuk memiliki parameter dan memperluas ke sesuatu seperti yang kita lihat di atas. Setelah Anda melakukan \catcode`e=13
Anda berpikir Anda bisa melakukannya \def e{I am the letter e!}
, TAPI. KAMU. TIDAK BISA! e
bukan surat lagi, jadi \def
bukankah \def
Anda tahu, itu \d e f
! Oh, pilih surat lain yang Anda katakan? Baik! \catcode`R=13 \def R{I am an ARRR!}
. Baiklah, Jimmy, coba! Saya berani Anda melakukan itu dan menulis R
kode Anda! Itulah yang dimaksud dengan catcode 13. SAYA TENANG! Mari kita lanjutkan.
Oke, sekarang untuk pengelompokan. Ini cukup mudah. Tugas apa pun ( \def
merupakan operasi penugasan, \let
(kami akan membahasnya) adalah tugas lain) yang dilakukan dalam suatu grup dikembalikan ke posisi semula sebelum grup itu dimulai kecuali tugas itu bersifat global. Ada beberapa cara untuk memulai grup, salah satunya adalah dengan karakter catcode 1 dan 2 (oh, kode cat lagi). Secara default {
adalah catcode 1, atau begin-group, dan }
catcode 2, atau end-group. Contoh: \def\a{1} \a{\def\a{2} \a} \a
Ini mencetak 1 2 1
. Di luar grup \a
ada 1
, lalu di dalamnya didefinisikan ulang 2
, dan ketika grup berakhir, ia dikembalikan ke 1
.
The \let
operasi tugas operasi lain seperti \def
, tapi agak berbeda. Dengan \def
Anda menentukan makro yang akan diperluas ke hal-hal, dengan \let
Anda membuat salinan dari hal-hal yang sudah ada. Setelah \let\blub=\def
( =
opsional) Anda dapat mengubah awal e
contoh dari item catcode 13 di atas \blub e{...
dan bersenang-senang dengan yang itu. Atau lebih baik, bukannya melanggar hal-hal yang Anda dapat memperbaiki (yang akan Anda lihat itu!) Yang R
misalnya: \let\newr=R \catcode`R=13 \def R{I am an A\newr\newr\newr!}
. Pertanyaan cepat: dapatkah Anda mengganti nama \newR
?
Akhirnya, yang disebut "ruang palsu". Ini semacam topik yang tabu karena ada orang yang mengklaim bahwa reputasi yang diperoleh di TeX - LaTeX Stack Exchange dengan menjawab pertanyaan "ruang palsu" tidak boleh dipertimbangkan, sementara yang lain dengan sepenuh hati tidak setuju. Dengan siapa Anda setuju? Tempatkan taruhan Anda! Sementara itu: TeX memahami jeda baris sebagai spasi. Cobalah untuk menulis beberapa kata dengan jeda baris (bukan baris kosong ) di antaranya. Sekarang tambahkan %
di akhir baris ini. Sepertinya Anda “berkomentar” pada ruang-ruang ujung ini. Itu dia :)
(Semacam) tidak mengubah kode
Mari kita buat persegi panjang itu menjadi sesuatu (bisa dibilang) lebih mudah diikuti:
{
\let~\catcode
~`A13
\defA#1{~`#113\gdef}
AGG#1{~`#113\global\let}
GFF\else
GHH\fi
AQQ{Q}
AII{\ifxQ}
AEE#1#2#3|{I#3#2#1FE{#1#2}#3|H}
ADD#1#2#3|{I#2FE{#1}#2#3|H}
ACC#1#2|{D{}#2Q|#1 }
ABBH#1 {HI#1FC#1|BH}
\gdef\S#1{\iftrueBH#1 Q }
}
Penjelasan dari setiap langkah
setiap baris berisi satu instruksi tunggal. Mari kita pergi satu per satu, membedah mereka:
{
Pertama-tama kita memulai sebuah kelompok untuk menjaga beberapa perubahan (yaitu perubahan catcode) lokal sehingga mereka tidak mengacaukan teks input.
\let~\catcode
Pada dasarnya semua kode kebingungan TeX dimulai dengan instruksi ini. Secara default, baik dalam TeX polos dan LaTeX, ~
karakter adalah karakter aktif yang dapat dibuat menjadi makro untuk digunakan lebih lanjut. Dan alat terbaik untuk mengubah kode TeX adalah perubahan catcode, jadi ini umumnya pilihan terbaik. Sekarang alih-alih \catcode`A=13
kita dapat menulis ~`A13
( =
ini opsional):
~`A13
Sekarang surat itu A
adalah karakter aktif, dan kita dapat mendefinisikannya untuk melakukan sesuatu:
\defA#1{~`#113\gdef}
A
sekarang merupakan makro yang mengambil satu argumen (yang seharusnya merupakan karakter lain). Pertama-tama catcode argumen diubah menjadi 13 untuk membuatnya aktif: ~`#113
(ganti ~
dengan \catcode
dan tambahkan =
dan Anda punya :) \catcode`#1=13
. Akhirnya ia meninggalkan \gdef
(global \def
) dalam aliran input. Singkatnya, A
buat karakter lain aktif dan mulai definisinya. Mari kita coba:
AGG#1{~`#113\global\let}
AG
"aktifkan" pertama G
dan lakukan \gdef
, yang diikuti oleh yang berikutnya G
memulai definisi. Definisi G
ini sangat mirip dengan A
, kecuali bahwa alih-alih \gdef
itu melakukan \global\let
(tidak ada \glet
seperti \gdef
). Singkatnya, G
mengaktifkan karakter dan membuatnya menjadi sesuatu yang lain. Mari kita buat pintasan untuk dua perintah yang akan kita gunakan nanti:
GFF\else
GHH\fi
Sekarang alih-alih \else
dan \fi
kita cukup menggunakan F
dan H
. Jauh lebih pendek :)
AQQ{Q}
Sekarang kita gunakan A
lagi untuk mendefinisikan makro lain Q
,. Pernyataan di atas pada dasarnya tidak (dalam bahasa yang kurang dikaburkan) \def\Q{\Q}
. Ini bukan definisi yang sangat menarik, tetapi memiliki fitur yang menarik. Kecuali Anda ingin memecah beberapa kode, satu-satunya makro yang berkembang Q
adalah Q
dirinya sendiri, sehingga berfungsi seperti penanda unik (disebut quark ). Anda bisa menggunakan \ifx
persyaratan untuk menguji apakah argumen makro adalah quark dengan \ifx Q#1
:
AII{\ifxQ}
sehingga Anda dapat yakin bahwa Anda menemukan spidol tersebut. Perhatikan bahwa dalam definisi ini saya menghapus ruang antara \ifx
dan Q
. Biasanya ini akan menyebabkan kesalahan (perhatikan bahwa highlight sintaksis berpikir itu \ifxQ
adalah satu hal), tetapi karena sekarang Q
adalah katak 13 maka tidak dapat membentuk urutan kontrol. Namun, berhati-hatilah untuk tidak memperluas kuark ini atau Anda akan terjebak dalam loop tak terbatas karena Q
memperluas Q
yang memperluas ke Q
mana ...
Sekarang setelah pendahuluan telah selesai, kita dapat pergi ke algoritma yang tepat untuk mengatur setterl. Karena tokenization TeX, algoritma harus ditulis mundur. Ini karena pada saat Anda melakukan definisi, TeX akan melakukan tokenize (menetapkan kode) ke karakter dalam definisi menggunakan pengaturan saat ini jadi, misalnya, jika saya lakukan:
\def\one{E}
\catcode`E=13\def E{1}
\one E
hasilnya adalah E1
, sedangkan jika saya mengubah urutan definisi:
\catcode`E=13\def E{1}
\def\one{E}
\one E
outputnya adalah 11
. Ini karena pada contoh pertama E
definisi dalam tokenized sebagai huruf (catcode 11) sebelum perubahan catcode, jadi itu akan selalu menjadi huruf E
. Namun, pada contoh kedua, E
pertama kali diaktifkan, dan baru kemudian \one
ditetapkan, dan sekarang definisi tersebut berisi katak 13 E
yang diperluas 1
.
Namun, saya akan mengabaikan fakta ini dan menyusun ulang definisi untuk memiliki urutan logis (tetapi tidak berfungsi). Dalam paragraf berikut Anda dapat mengasumsikan bahwa surat-surat B
, C
, D
, dan E
aktif.
\gdef\S#1{\iftrueBH#1 Q }
(perhatikan ada bug kecil di versi sebelumnya, itu tidak mengandung ruang terakhir dalam definisi di atas. Saya hanya memperhatikannya saat menulis ini. Baca terus dan Anda akan melihat mengapa kita membutuhkannya untuk menghentikan makro dengan benar. )
Pertama kita mendefinisikan makro tingkat pengguna \S
,. Yang ini seharusnya tidak menjadi karakter aktif untuk memiliki sintaks ramah (?), Jadi makro untuk gwappins dan setterl adalah \S
. Makro dimulai dengan kondisi selalu benar \iftrue
(akan segera menjadi jelas mengapa), dan kemudian memanggil B
makro diikuti oleh H
(yang telah kita tentukan sebelumnya \fi
) untuk mencocokkan \iftrue
. Lalu kita meninggalkan argumen makro #1
diikuti oleh spasi dan oleh quark Q
. Misalkan kita gunakan \S{hello world}
, maka input streamakan terlihat seperti ini: \iftrue BHhello world Q␣
(Saya mengganti ruang terakhir dengan ␣
sehingga rendering situs tidak memakannya, seperti yang saya lakukan pada versi kode sebelumnya). \iftrue
itu benar, jadi itu mengembang dan kita dibiarkan BHhello world Q␣
. TeX tidak menghapus \fi
( H
) setelah kondisi dievaluasi, melainkan membiarkannya di sana sampai benar\fi
- benar diperluas. Sekarang B
makro diperluas:
ABBH#1 {HI#1FC#1|BH}
B
adalah makro terbatas yang memiliki parameter teks H#1␣
, jadi argumennya adalah apa pun yang ada di antara H
dan spasi. Melanjutkan contoh di atas aliran input sebelum perluasan B
is BHhello world Q␣
. B
diikuti oleh H
, sebagaimana mestinya (jika TeX akan menimbulkan kesalahan), maka ruang berikutnya adalah antara hello
dan world
, begitu #1
juga kata hello
. Dan di sini kita harus membagi teks input di spasi. Yay: D Perluasan B
menghapus segala sesuatu sampai ke ruang pertama dari input stream dan menggantikan dengan HI#1FC#1|BH
dengan #1
menjadi hello
: HIhelloFChello|BHworld Q␣
. Perhatikan bahwa ada yang baru BH
nanti dalam aliran input, untuk melakukan rekursi ekorB
dan memproses kata-kata selanjutnya. Setelah kata ini diproses, B
proses kata berikutnya hingga kata yang akan diproses adalah kuark Q
. Ruang terakhir setelah Q
diperlukan karena makro dibatasi B
membutuhkan satu di akhir argumen. Dengan versi sebelumnya (lihat sunting histori) kode akan bertingkah jika Anda menggunakan \S{hello world}abc abc
(ruang antara abc
s akan lenyap).
OK, kembali ke input stream: HIhelloFChello|BHworld Q␣
. Pertama ada H
( \fi
) yang melengkapi inisial \iftrue
. Sekarang kita punya ini (pseudocoded):
I
hello
F
Chello|B
H
world Q␣
The I...F...H
berpikir sebenarnya adalah \ifx Q...\else...\fi
struktur. The \ifx
pemeriksaan tes jika (pertama tanda yang) kata meraih adalah Q
quark. Jika tidak ada yang lain untuk melakukan dan berakhir eksekusi, jika apa yang tersisa adalah: Chello|BHworld Q␣
. Sekarang C
diperluas:
ACC#1#2|{D#2Q|#1 }
Argumen pertama C
adalah undelimited, jadi kecuali bersiap akan menjadi tanda tunggal, Argumen kedua dibatasi oleh |
, jadi setelah ekspansi C
(dengan #1=h
dan #2=ello
) input stream adalah: DelloQ|h BHworld Q␣
. Perhatikan bahwa lain |
yang diletakkan di sana, dan h
dari hello
diletakkan setelah itu. Setengah swapping dilakukan; huruf pertama ada di akhir. Di TeX, mudah untuk mengambil token pertama dari daftar token. Makro sederhana \def\first#1#2|{#1}
mendapatkan huruf pertama saat Anda menggunakan \first hello|
. Yang terakhir adalah masalah karena TeX selalu mengambil daftar token "terkecil, mungkin kosong" sebagai argumen, jadi kita perlu beberapa solusi. Item berikutnya dalam daftar token adalah D
:
ADD#1#2|{I#1FE{}#1#2|H}
D
Makro
ini adalah salah satu solusi dan berguna dalam kasus tunggal di mana kata memiliki satu huruf. Andaikan hello
kita tidak melakukannya x
. Dalam hal ini input stream akan DQ|x
, maka D
akan memperluas (dengan #1=Q
, dan #2
mengosongkan) ke: IQFE{}Q|Hx
. Ini mirip dengan blok I...F...H
( \ifx Q...\else...\fi
) di B
, yang akan melihat bahwa argumen adalah quark dan akan mengganggu eksekusi hanya menyisakan x
untuk pengaturan huruf. Dalam kasus lain (kembali ke hello
contoh), D
akan memperluas (dengan #1=e
dan #2=lloQ
) ke: IeFE{}elloQ|Hh BHworld Q␣
. Sekali lagi, I...F...H
akan memeriksa Q
tapi akan gagal dan mengambil \else
cabang: E{}elloQ|Hh BHworld Q␣
. Sekarang bagian terakhir dari hal ini, theE
makro akan berkembang:
AEE#1#2#3|{I#3#2#1FE{#1#2}#3|H}
Teks parameter di sini sangat mirip dengan C
dan D
; argumen pertama dan kedua tidak didahulukan, dan yang terakhir dibatasi oleh |
. Input stream terlihat seperti ini: E{}elloQ|Hh BHworld Q␣
, kemudian E
mengembang (dengan #1
kosong, #2=e
dan #3=lloQ
): IlloQeFE{e}lloQ|HHh BHworld Q␣
. Lain I...F...H
blok cek untuk quark (yang melihat l
dan kembali false
): E{e}lloQ|HHh BHworld Q␣
. Sekarang E
memperluas lagi (dengan #1=e
kosong, #2=l
dan #3=loQ
): IloQleFE{el}loQ|HHHh BHworld Q␣
. Dan lagi I...F...H
. Makro melakukan beberapa iterasi lagi sampai Q
akhirnya ditemukan dan true
cabang diambil: E{el}loQ|HHHh BHworld Q␣
-> IoQlelFE{ell}oQ|HHHHh BHworld Q␣
-> E{ell}oQ|HHHHh BHworld Q␣
-> IQoellFE{ello}Q|HHHHHh BHworld Q␣
. Sekarang quark ditemukan dan mengembang bersyarat untuk: oellHHHHh BHworld Q␣
. Fiuh.
Oh, tunggu, apa ini? SURAT NORMAL? Oh Boy! Surat-surat yang akhirnya menemukan dan TeX menuliskan oell
, kemudian sekelompok H
( \fi
) ditemukan dan diperluas (apa-apa) meninggalkan input stream dengan: oellh BHworld Q␣
. Sekarang kata pertama memiliki huruf pertama dan terakhir ditukar dan apa TeX menemukan selanjutnya adalah yang lain B
untuk mengulangi seluruh proses untuk kata berikutnya.
}
Akhirnya kami mengakhiri grup mulai kembali ke sana sehingga semua tugas lokal dibatalkan. Tugas lokal adalah perubahan catcode huruf A
, B
, C
, ... yang dibuat macro sehingga mereka kembali ke surat arti normal mereka dan dapat digunakan dengan aman dalam teks. Dan itu saja. Sekarang \S
makro yang didefinisikan kembali di sana akan memicu pemrosesan teks seperti di atas.
Satu hal yang menarik tentang kode ini adalah bahwa kode ini sepenuhnya dapat dikembangkan. Artinya, Anda bisa menggunakannya dengan aman dalam memindahkan argumen tanpa khawatir itu akan meledak. Anda bahkan dapat menggunakan kode untuk memeriksa apakah huruf terakhir dari sebuah kata sama dengan yang kedua (untuk alasan apa pun Anda membutuhkannya) dalam \if
ujian:
\if\S{here} true\else false\fi % prints true (plus junk, which you would need to handle)
\if\S{test} true\else false\fi % prints false
Maaf untuk penjelasan bertele-tele (mungkin terlalu). Saya mencoba membuatnya sejelas mungkin untuk non TeXies juga :)
Ringkasan untuk yang tidak sabar
Makro \S
mendahului input dengan karakter aktif B
yang mengambil daftar token yang dibatasi oleh ruang final dan meneruskannya ke C
. C
mengambil token pertama dalam daftar itu dan memindahkannya ke akhir daftar token dan memperluas D
dengan apa yang tersisa. D
memeriksa apakah "apa yang tersisa" kosong, dalam hal ditemukan satu kata, tidak melakukan apa-apa; jika tidak mengembang E
. E
loop melalui daftar token sampai menemukan huruf terakhir dalam kata, ketika ditemukan meninggalkan huruf terakhir, diikuti oleh tengah kata, yang kemudian diikuti oleh huruf pertama yang tersisa di ujung token stream oleh C
.
Hello, world!
menjadi,elloH !orldw
(menukar tanda baca sebagai huruf) atauoellH, dorlw!
(mempertahankan tanda baca di tempat)?