คุณจะจับคู่เฉพาะตัวเลขโรมันที่ถูกต้องกับการแสดงออกปกติได้อย่างไร


165

เมื่อคิดถึงปัญหาอื่น ๆ ของฉันฉันตัดสินใจว่าฉันไม่สามารถสร้างนิพจน์ทั่วไปที่จะจับคู่กับตัวเลขโรมันได้

ปัญหาคือการจับคู่เฉพาะตัวเลขโรมันที่ถูกต้อง เช่น 990 ไม่ใช่ "XM" แต่เป็น "CMXC"

ปัญหาของฉันในการสร้าง regex สำหรับเรื่องนี้คือเพื่อให้อนุญาตหรือไม่อนุญาตให้ใช้อักขระบางตัวฉันต้องมองย้อนกลับไป ตัวอย่างเช่นลองมาเป็นพัน ๆ

ฉันสามารถอนุญาต M {0,2} C? M (อนุญาตสำหรับ 900, 1,000, 1900, 2000, 2900 และ 3000) อย่างไรก็ตามหากการแข่งขันอยู่บน CM ฉันไม่สามารถอนุญาตให้ตัวละครที่ตามมาเป็น C หรือ D ได้ (เพราะฉันอยู่ที่ 900)

ฉันจะแสดงสิ่งนี้ใน regex ได้อย่างไร
ถ้ามันไม่สามารถแสดงให้เห็นได้อย่างชัดเจนใน regex มันสามารถใช้ได้ในไวยากรณ์ที่ไม่มีบริบทหรือไม่?

คำตอบ:


328

คุณสามารถใช้ regex ต่อไปนี้:

^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$

ทำลายมันลงM{0,4}ระบุส่วนพันและพื้นยับยั้งมันระหว่างและ0 4000มันค่อนข้างง่าย:

   0: <empty>  matched by M{0}
1000: M        matched by M{1}
2000: MM       matched by M{2}
3000: MMM      matched by M{3}
4000: MMMM     matched by M{4}

แน่นอนคุณสามารถใช้บางสิ่งบางอย่างเช่นM*อนุญาตให้มีตัวเลขใด ๆ (รวมถึงศูนย์) ของหลักพันหากคุณต้องการอนุญาตตัวเลขที่ใหญ่กว่า

ถัดไปคือ(CM|CD|D?C{0,3})ซับซ้อนกว่านี้เล็กน้อยสำหรับส่วนร้อยและครอบคลุมความเป็นไปได้ทั้งหมด:

  0: <empty>  matched by D?C{0} (with D not there)
100: C        matched by D?C{1} (with D not there)
200: CC       matched by D?C{2} (with D not there)
300: CCC      matched by D?C{3} (with D not there)
400: CD       matched by CD
500: D        matched by D?C{0} (with D there)
600: DC       matched by D?C{1} (with D there)
700: DCC      matched by D?C{2} (with D there)
800: DCCC     matched by D?C{3} (with D there)
900: CM       matched by CM

ประการที่สาม(XC|XL|L?X{0,3})ปฏิบัติตามกฎเดียวกันกับส่วนก่อนหน้า แต่สำหรับหลักสิบ:

 0: <empty>  matched by L?X{0} (with L not there)
10: X        matched by L?X{1} (with L not there)
20: XX       matched by L?X{2} (with L not there)
30: XXX      matched by L?X{3} (with L not there)
40: XL       matched by XL
50: L        matched by L?X{0} (with L there)
60: LX       matched by L?X{1} (with L there)
70: LXX      matched by L?X{2} (with L there)
80: LXXX     matched by L?X{3} (with L there)
90: XC       matched by XC

และสุดท้าย(IX|IV|V?I{0,3})เป็นส่วนที่หน่วยจัดการ0ผ่าน9และยังคล้ายกับก่อนหน้านี้สองส่วน (เลขโรมันแม้จะมีความแปลกประหลาดที่เห็นพวกเขาปฏิบัติตามกฎตรรกะบางอย่างเมื่อคุณคิดออกสิ่งที่พวกเขา):

0: <empty>  matched by V?I{0} (with V not there)
1: I        matched by V?I{1} (with V not there)
2: II       matched by V?I{2} (with V not there)
3: III      matched by V?I{3} (with V not there)
4: IV       matched by IV
5: V        matched by V?I{0} (with V there)
6: VI       matched by V?I{1} (with V there)
7: VII      matched by V?I{2} (with V there)
8: VIII     matched by V?I{3} (with V there)
9: IX       matched by IX

เพียงจำไว้ว่า regex นั้นจะจับคู่สตริงว่าง หากคุณไม่ต้องการสิ่งนี้ (และเอ็นจิ้น regex ของคุณทันสมัยพอ) คุณสามารถใช้การมองย้อนกลับและมองไปข้างหน้าในเชิงบวก:

(?<=^)M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})(?=$)

(อีกทางเลือกหนึ่งคือเพียงแค่ตรวจสอบว่าความยาวไม่ได้เป็นศูนย์ล่วงหน้า)


12
ไม่ควรเป็น M {0,3}
เลมอน

3
วิธีใดเพื่อหลีกเลี่ยงการจับคู่สตริงว่าง?
Facundo Casco

11
@Aashish: เมื่อชาวโรมันถูกบังคับให้ต้องคำนึงถึงนั่นMMMMเป็นวิธีที่ถูกต้อง การเป็นตัวแทนของเหล็กโอเวอร์มานานหลังจากที่อาณาจักรหลักล่มสลายเป็นชิ้น ๆ
paxdiablo

2
@paxdiablo นี่คือวิธีที่ฉันพบ mmmcm ล้มเหลว String regx = "^ M {0,3} (CM | CD | D? C {0,3}) (XC | XL | L? X {0,3}) (IX | IV | V? I {0, 3}) $ "; if (input.matches (regx)) -> สิ่งนี้จะเป็นเท็จสำหรับ MMMCM / MMMM ใน java
amIT

2
/^M{0,3}(?:C[MD]|D?C{0,3})(?:X[CL]|L?X{0,3})(?:I[XV]|V?I{0,3})$/i
Crissov

23

จริงๆแล้วหลักฐานของคุณมีข้อบกพร่อง 990 คือ "XM" เช่นเดียวกับ "CMXC"

ชาวโรมันมีความกังวลน้อยเกี่ยวกับ "กฎ" มากกว่าครูชั้นประถมศึกษาปีที่สามของคุณ ตราบใดที่มันเพิ่มมันก็โอเค ดังนั้น "IIII" ก็ดีเท่ากับ "IV" สำหรับ 4 และ "IIM" นั้นยอดเยี่ยมมากสำหรับ 998

(หากคุณมีปัญหาในการจัดการกับเรื่องนั้น ... โปรดจำไว้ว่าการสะกดคำภาษาอังกฤษไม่ได้เป็นทางการจนกระทั่งถึงปี 1700 จนกระทั่งถึงตอนนั้นตราบใดที่ผู้อ่านสามารถคิดออกมันก็ดีพอ)


8
แน่นอนว่ามันเจ๋ง แต่ "เข้มงวดครูชั้นประถมศึกษาปีที่สาม" ต้องไวยากรณ์ของฉันทำให้ปัญหา regex ที่น่าสนใจอื่น ๆ อีกมากมายในความคิดของฉัน ...
แดเนียล Magliola

5
จุดดีเจมส์หนึ่งควรจะเป็นนักเขียนที่เข้มงวด แต่ผู้อ่านให้อภัย
Corin


13

เพียงบันทึกที่นี่:

(^(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$)

ตรงกับตัวเลขโรมันทั้งหมด ไม่สนใจเกี่ยวกับสตริงว่าง (ต้องมีตัวอักษรตัวเลขโรมันอย่างน้อยหนึ่งตัว) ควรทำงานใน PCRE, Perl, Python และ Ruby

การสาธิตทับทิมออนไลน์: http://rubular.com/r/KLPR1zq3Hj

การแปลงออนไลน์: http://www.onlineconversion.com/roman_numerals_advanced.htm


2
ฉันไม่รู้ว่าทำไม แต่คำตอบหลักไม่ได้ผลสำหรับฉันในรายการแปลอัตโนมัติใน MemoQ อย่างไรก็ตามการแก้ปัญหานี้ไม่รวมถึงสัญลักษณ์เริ่มต้น / สิ้นสุดสตริง
orlando2bjr

1
@ orlando2bjr ดีใจที่ได้ช่วย ใช่ในกรณีนี้ฉันจับคู่หมายเลขด้วยตัวเองโดยไม่มีสภาพแวดล้อม หากคุณมองหาข้อความให้แน่ใจว่าคุณต้องลบ ^ $ ไชโย!
smileart

12

เพื่อหลีกเลี่ยงการจับคู่สตริงว่างคุณจะต้องทำซ้ำรูปแบบครั้งที่สี่และแทนที่แต่ละ0ที่มี1ในการเปิดและการบัญชีสำหรับV, LและD:

(M{1,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})|M{0,4}(CM|C?D|D?C{1,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})|M{0,4}(CM|CD|D?C{0,3})(XC|X?L|L?X{1,3})(IX|IV|V?I{0,3})|M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|I?V|V?I{1,3}))

ในกรณีนี้ (เพราะรูปแบบนี้ใช้^และ$) คุณจะดีกว่าที่จะตรวจสอบบรรทัดว่างก่อนและอย่าไปจับคู่มัน หากคุณกำลังใช้ขอบเขตของคำศัพท์คุณก็จะไม่มีปัญหาเพราะไม่มีคำใดที่ว่างเปล่า (อย่างน้อย regex ไม่ได้กำหนดอย่างใดอย่างหนึ่งไม่ได้เริ่ม philosophising ฉันถูกเน้นที่นี่!)


ในกรณีของฉัน (โลกแห่งความเป็นจริง) ของตัวเองฉันต้องการตัวเลขที่ตรงกับตอนจบคำและฉันไม่พบวิธีอื่นรอบตัว ฉันต้องการที่จะขัดออกหมายเลขเชิงอรรถจากเอกสารข้อความธรรมดาของฉันที่ข้อความเช่น "ทะเลแดงCLและ Great Barrier Reef CLI " the Red Seacl and the Great Barrier Reefcliได้รับการแปลง แต่ผมก็ยังมีปัญหากับคำที่ถูกต้องเช่นTahitiและfantasticจะขัดเข้าและTahitfantasti


ฉันมีปัญหาที่คล้ายกัน (!): การทำ "ซ้าย trim" ของจำนวนโรมันที่เหลือ / รายการที่เหลือของรายการ (HTML OL ประเภท I หรือ i) ดังนั้นเมื่อมีสิ่งที่เหลืออยู่ฉันต้องทำความสะอาด (เช่นฟังก์ชั่นการตัดแต่ง) ด้วย regex ของคุณที่จุดเริ่มต้น (ซ้าย) ของรายการข้อความ ... แต่ง่ายกว่า: รายการที่ไม่เคยใช้MหรือCหรือLคุณมีสิ่งนี้หรือไม่ ชนิดของ regex ง่าย?
Peter Krauss

... ตกลง, ที่นี่ดูเหมือนว่าตกลง (!),(X{1,3}(IX|IV|V?I{0,3})|X{0,3}(IX|I?V|V?I{1,3}))
Peter Krauss

1
คุณไม่จำเป็นต้องทำซ้ำเพื่อปฏิเสธสตริงที่ว่างเปล่า คุณสามารถใช้การยืนยันแบบ lookahead
jfs

7

โชคดีที่ช่วงของตัวเลขนั้น จำกัด ที่ 1..3999 หรือประมาณนั้น ดังนั้นคุณสามารถสร้าง regex piece-meal ได้

<opt-thousands-part><opt-hundreds-part><opt-tens-part><opt-units-part>

แต่ละส่วนเหล่านั้นจะจัดการกับความหลากหลายของสัญกรณ์โรมัน ตัวอย่างเช่นการใช้สัญลักษณ์ Perl:

<opt-hundreds-part> = m/(CM|DC{0,3}|CD|C{1,3})?/;

ทำซ้ำและประกอบ

เพิ่ม : <opt-hundreds-part>สามารถบีบอัดเพิ่มเติม:

<opt-hundreds-part> = m/(C[MD]|D?C{0,3})/;

เนื่องจากส่วนคำสั่ง 'D? C {0,3}' ไม่ตรงกับอะไรเลยจึงไม่จำเป็นต้องมีเครื่องหมายคำถาม และส่วนใหญ่แล้ววงเล็บควรเป็นประเภทที่ไม่จับภาพ - ในภาษา Perl:

<opt-hundreds-part> = m/(?:C[MD]|D?C{0,3})/;

แน่นอนมันควรจะเป็นแบบตัวพิมพ์เล็กและตัวพิมพ์ใหญ่

คุณสามารถขยายสิ่งนี้เพื่อจัดการกับตัวเลือกต่าง ๆ ที่กล่าวถึงโดย James Curran (เพื่ออนุญาตให้ XM หรือ IM สำหรับ 990 หรือ 999 และ CCCC สำหรับ 400 เป็นต้น)

<opt-hundreds-part> = m/(?:[IXC][MD]|D?C{0,4})/;


คุณหมายถึงอะไรโดยโชคดีที่ช่วงของตัวเลขจะถูก จำกัด 1..3999 หรือราว ? ใคร จำกัด
SexyBeast

@SexyBeast: ไม่มีเครื่องหมายโรมันมาตรฐานสำหรับ 5,000 ให้นับจำนวนที่ใหญ่กว่าคนเดียวดังนั้นระเบียบที่ทำงานถึงแล้วหยุดทำงาน
Jonathan Leffler

ไม่แน่ใจว่าทำไมคุณถึงเชื่อเช่นนั้น แต่ตัวเลขโรมันสามารถแสดงตัวเลขเป็นล้านได้ en.wikipedia.org/wiki/Roman_numerals#Large_numbers
AmbroseChapel

@AmbroseChapel: ตามที่ฉันได้กล่าวไว้ไม่มีสัญลักษณ์มาตรฐานใด ๆ (5,000 รายการ) สำหรับตัวเลขที่ใหญ่กว่านี้ คุณต้องใช้หนึ่งในหลายระบบตามที่ระบุไว้ในบทความ Wikipedia ที่คุณเชื่อมโยงและคุณประสบปัญหาเกี่ยวกับการสะกดการันต์สำหรับระบบที่มี overbars, underbars หรือกลับ C เป็นต้นและคุณจะต้องอธิบายให้ทุกคนทราบว่า ระบบที่คุณใช้และความหมาย; โดยทั่วไปผู้คนจะไม่รู้จักเลขโรมันที่เกินจากเอ็มคุณอาจเลือกคิดแบบอื่น นั่นคืออภิสิทธิ์ของคุณเช่นเดียวกับอภิสิทธิ์ของฉันที่จะแสดงความคิดเห็นก่อนหน้านี้
Jonathan Leffler

7
import re
pattern = '^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$'
if re.search(pattern, 'XCCMCI'):
    print 'Valid Roman'
else:
    print 'Not valid Roman'

สำหรับคนที่อยากจะเข้าใจตรรกะโปรดดูที่ขั้นตอนตามคำอธิบายขั้นตอนใน 3 หน้าในdiveintopython

ข้อแตกต่างจากโซลูชันดั้งเดิม (ซึ่งมีM{0,4}) คือเพราะฉันพบว่า 'MMMM' ไม่ใช่ตัวเลขโรมันที่ถูกต้อง (เช่นโรมโบราณส่วนใหญ่อาจไม่ได้คิดถึงจำนวนมหาศาลนั้นและจะไม่เห็นด้วยกับฉัน) หากคุณเป็นหนึ่งในคนที่ไม่พอใจชาวโรมันเก่าโปรดยกโทษให้ฉันและใช้รุ่น {0,4}


1
regex ในคำตอบอนุญาตให้มีตัวเลขที่ว่างเปล่า หากคุณไม่ต้องการมัน คุณสามารถใช้การยืนยันแบบ lookaheadในการปฏิเสธสตริงที่ว่างเปล่า (มันจะไม่สนใจตัวอักษรที่เป็นตัวพิมพ์ใหญ่)
jfs

2

ฉันกำลังตอบคำถามนี้นิพจน์ปกติใน Python สำหรับเลขโรมันที่นี่
เพราะถูกทำเครื่องหมายว่าซ้ำกับคำถามนี้

มันอาจจะคล้ายกันในชื่อ แต่นี่เป็นคำถาม / ปัญหาเฉพาะของ regex
ที่สามารถเห็นได้จากคำตอบของคำถามนั้น

รายการที่ถูกขอสามารถรวมกันเป็นหนึ่งเดียวสลับกันแล้ว
ห่อหุ้มภายในกลุ่มการจับภาพที่จะใส่ลงในรายการที่มี findall () ที่
ฟังก์ชั่น
มันทำแบบนี้:

>>> import re
>>> target = (
... r"this should pass v" + "\n"
... r"this is a test iii" + "\n"
... )
>>>
>>> re.findall( r"(?m)\s(i{1,3}v*|v)$", target )
['v', 'iii']

การปรับเปลี่ยน regex เป็นตัวประกอบและจับเฉพาะตัวเลขมีดังนี้:

 (?m)
 \s 
 (                     # (1 start)
      i{1,3} 
      v* 
   |  v
 )                     # (1 end)
 $

1

ขณะที่ Jeremy และ Pax ชี้ให้เห็นข้างต้น ... '^ M {0,4} (CM | CD | D? C {0,3}) (XC | XL | L? X {0,3}) (IX | IV | V? I {0,3}) $ 'ควรเป็นคำตอบของคุณ ...

URL เฉพาะที่ควรแนบมา (IMHO) คือ http://thehazeltree.org/diveintopython/7.html

ตัวอย่าง 7.8 เป็นรูปแบบย่อโดยใช้ {n, m}


1

ในกรณีของฉันฉันพยายามค้นหาและแทนที่ตัวเลขโรมันทั้งหมดด้วยคำเดียวในข้อความดังนั้นฉันจึงไม่สามารถใช้จุดเริ่มต้นและจุดสิ้นสุด ดังนั้นโซลูชั่น @paxdiablo จึงพบการแข่งขันที่มีความยาวเป็นศูนย์จำนวนมาก ฉันลงเอยด้วยนิพจน์ต่อไปนี้:

(?=\b[MCDXLVI]{1,6}\b)M{0,4}(?:CM|CD|D?C{0,3})(?:XC|XL|L?X{0,3})(?:IX|IV|V?I{0,3})

รหัส Python สุดท้ายของฉันเป็นดังนี้:

import re
text = "RULES OF LIFE: I. STAY CURIOUS; II. NEVER STOP LEARNING"
text = re.sub(r'(?=\b[MCDXLVI]{1,6}\b)M{0,4}(?:CM|CD|D?C{0,3})(?:XC|XL|L?X{0,3})(?:IX|IV|V?I{0,3})', 'ROMAN', text)
print(text)

เอาท์พุท:

RULES OF LIFE: ROMAN. STAY CURIOUS; ROMAN. NEVER STOP LEARNING


0

ฉันเห็นคำตอบหลายข้อที่ไม่ครอบคลุมสตริงที่ว่างเปล่าหรือใช้ lookaheads เพื่อแก้ปัญหานี้ และฉันต้องการเพิ่มคำตอบใหม่ที่ครอบคลุมสตริงว่างและไม่ใช้ lookahead regex เป็นหนึ่งต่อไปนี้:

^(I[VX]|VI{0,3}|I{1,3})|((X[LC]|LX{0,3}|X{1,3})(I[VX]|V?I{0,3}))|((C[DM]|DC{0,3}|C{1,3})(X[LC]|L?X{0,3})(I[VX]|V?I{0,3}))|(M+(C[DM]|D?C{0,3})(X[LC]|L?X{0,3})(I[VX]|V?I{0,3}))$

ฉันอนุญาตให้ไม่มีที่สิ้นสุดMด้วยM+แต่แน่นอนว่าบางคนสามารถเปลี่ยนM{1,4}เป็นอนุญาตเพียง 1 หรือ 4 หากต้องการ

ด้านล่างคือการสร้างภาพข้อมูลที่ช่วยให้เข้าใจสิ่งที่กำลังทำอยู่นำหน้าด้วยการสาธิตออนไลน์สองรายการ:

Debuggex Demo

การสาธิต Regex 101

การสร้างภาพการแสดงออกปกติ


0

สิ่งนี้ใช้ได้กับเอนจิน Java และ PCRE regex และควรทำงานกับ JavaScript ล่าสุด แต่อาจไม่สามารถใช้งานได้ในทุกบริบท

(?<![A-Z])(M*(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3}))(?![A-Z])

ส่วนแรกคือการมองในแง่ลบที่เลวร้าย แต่สำหรับวัตถุประสงค์เชิงตรรกะมันเป็นเรื่องง่ายที่สุดที่จะเข้าใจ โดยพื้นฐานแล้วคำแรก(?<!)บอกว่าไม่ตรงกลาง([MATCH])ถ้ามีตัวอักษรมาก่อนที่ตรงกลาง([MATCH])และคนสุดท้าย(?!)บอกว่าไม่ตรงกลาง([MATCH])ถ้ามีตัวอักษรมาหลังจากนั้น

ตรงกลาง([MATCH])เป็นเพียง regex ที่ใช้กันมากที่สุดสำหรับการจับคู่ลำดับของตัวเลขโรมัน แต่ตอนนี้คุณไม่ต้องการจับคู่ว่าถ้ามีตัวอักษรอยู่รอบ ๆ

ดูด้วยตัวคุณเอง https://regexr.com/4vce5


-1

ปัญหาของการแก้ปัญหาจาก Jeremy และ Pax คือมันไม่ตรงกับ "ไม่มีอะไร"

regex ต่อไปนี้คาดว่าจะมีตัวเลขโรมันอย่างน้อยหนึ่งตัว:

^(M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})|[IDCXMLV])$

6
อันนั้นจะไม่ทำงาน (เว้นแต่คุณจะใช้การใช้ regex ที่แปลกมาก) - ส่วนด้านซ้ายของ|สามารถจับคู่สตริงว่างและตัวเลขโรมันที่ถูกต้องทั้งหมดดังนั้นด้านขวาจึงซ้ำซ้อนอย่างสมบูรณ์ และใช่มันยังคงตรงกับสตริงว่าง
DirtY iCE

"ปัญหาของการแก้ปัญหาจาก Jeremy and Pax คือ" ... เหมือนกับปัญหาที่คำตอบนี้มี หากคุณกำลังจะเสนอวิธีแก้ไขปัญหาที่ควรจะเป็นคุณควรทดสอบ :-)
paxdiablo

ฉันมีสตริงว่างกับสิ่งนี้
Aminah Nuraini

-2

ฉันจะเขียนฟังก์ชั่นการทำงานให้ฉัน นี่คือฟังก์ชันเลขโรมันสองฟังก์ชันใน PowerShell

function ConvertFrom-RomanNumeral
{
  <#
    .SYNOPSIS
        Converts a Roman numeral to a number.
    .DESCRIPTION
        Converts a Roman numeral - in the range of I..MMMCMXCIX - to a number.
    .EXAMPLE
        ConvertFrom-RomanNumeral -Numeral MMXIV
    .EXAMPLE
        "MMXIV" | ConvertFrom-RomanNumeral
  #>
    [CmdletBinding()]
    [OutputType([int])]
    Param
    (
        [Parameter(Mandatory=$true,
                   HelpMessage="Enter a roman numeral in the range I..MMMCMXCIX",
                   ValueFromPipeline=$true,
                   Position=0)]
        [ValidatePattern("^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$")]
        [string]
        $Numeral
    )

    Begin
    {
        $RomanToDecimal = [ordered]@{
            M  = 1000
            CM =  900
            D  =  500
            CD =  400
            C  =  100
            XC =   90
            L  =   50
            X  =   10
            IX =    9
            V  =    5
            IV =    4
            I  =    1
        }
    }
    Process
    {
        $roman = $Numeral + " "
        $value = 0

        do
        {
            foreach ($key in $RomanToDecimal.Keys)
            {
                if ($key.Length -eq 1)
                {
                    if ($key -match $roman.Substring(0,1))
                    {
                        $value += $RomanToDecimal.$key
                        $roman  = $roman.Substring(1)
                        break
                    }
                }
                else
                {
                    if ($key -match $roman.Substring(0,2))
                    {
                        $value += $RomanToDecimal.$key
                        $roman  = $roman.Substring(2)
                        break
                    }
                }
            }
        }
        until ($roman -eq " ")

        $value
    }
    End
    {
    }
}

function ConvertTo-RomanNumeral
{
  <#
    .SYNOPSIS
        Converts a number to a Roman numeral.
    .DESCRIPTION
        Converts a number - in the range of 1 to 3,999 - to a Roman numeral.
    .EXAMPLE
        ConvertTo-RomanNumeral -Number (Get-Date).Year
    .EXAMPLE
        (Get-Date).Year | ConvertTo-RomanNumeral
  #>
    [CmdletBinding()]
    [OutputType([string])]
    Param
    (
        [Parameter(Mandatory=$true,
                   HelpMessage="Enter an integer in the range 1 to 3,999",
                   ValueFromPipeline=$true,
                   Position=0)]
        [ValidateRange(1,3999)]
        [int]
        $Number
    )

    Begin
    {
        $DecimalToRoman = @{
            Ones      = "","I","II","III","IV","V","VI","VII","VIII","IX";
            Tens      = "","X","XX","XXX","XL","L","LX","LXX","LXXX","XC";
            Hundreds  = "","C","CC","CCC","CD","D","DC","DCC","DCCC","CM";
            Thousands = "","M","MM","MMM"
        }

        $column = @{Thousands = 0; Hundreds = 1; Tens = 2; Ones = 3}
    }
    Process
    {
        [int[]]$digits = $Number.ToString().PadLeft(4,"0").ToCharArray() |
                            ForEach-Object { [Char]::GetNumericValue($_) }

        $RomanNumeral  = ""
        $RomanNumeral += $DecimalToRoman.Thousands[$digits[$column.Thousands]]
        $RomanNumeral += $DecimalToRoman.Hundreds[$digits[$column.Hundreds]]
        $RomanNumeral += $DecimalToRoman.Tens[$digits[$column.Tens]]
        $RomanNumeral += $DecimalToRoman.Ones[$digits[$column.Ones]]

        $RomanNumeral
    }
    End
    {
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.