18
\$\begingroup\$

In the hovertext of this xkcd:

100he100k out th1s 1nno5at4e str1ng en100o501ng 15e been 500e5e50op1ng! 1t's 6rtua100y perfe100t! ...hang on, what's a "virtuacy"?

There's an "encoding" based on replacing runs of roman numerals with their sums. To do this:

  • Find all runs of at least 1 roman numeral (IVXLCDM)
  • Replace them with their sums, as numbers

The Roman Numerals are IVXLCDM, corresponding respectively to 1, 5, 10, 50, 100, 500, 1000. You should match them in either case.

For example, if we take encoding, c is the roman numeral 100, so it gets replaced with 100, leaving en100oding. Then, di are both roman numerals, respectively 500 and 1, so it gets replaced with their sum, 501, leaving en100o501ng.

Your challenge is to implement this, given a string. This is , shortest wins!

Note: the xkcd implements some more complex rules regarding consecutive characters like iv. This can create some ambiguities so I'm going with this simpler version.

Testcases

xkcd -> 10k600
encodingish -> en100o501ng1sh
Hello, World! -> He100o, Wor550!
Potatoes are green -> Potatoes are green
Divide -> 1007e
bacillicidic -> ba904
\$\endgroup\$
6
  • \$\begingroup\$ Do we have to handle either case, or can we assume, eg, all lowercase? \$\endgroup\$
    – Jonah
    Jun 27, 2022 at 2:49
  • \$\begingroup\$ @Jonah I'm gonna say either case \$\endgroup\$
    – emanresu A
    Jun 27, 2022 at 3:50
  • \$\begingroup\$ based on the same XKCD: Not-Roman-Numeral Addition \$\endgroup\$
    – bigyihsuan
    Jun 27, 2022 at 13:22
  • \$\begingroup\$ That's Numberwang! \$\endgroup\$ Jun 27, 2022 at 14:12
  • \$\begingroup\$ Shouldn't xkcd be 10k400 ? \$\endgroup\$
    – vsz
    Jun 28, 2022 at 5:35

9 Answers 9

12
\$\begingroup\$

Vyxal s, 9 bytes

ƛøṘ∨;⁽-ḊṠ

Try it online or run a test suite.

ƛøṘ∨;⁽-ḊṠ
ƛ          # Map, and for each character:
 øṘ        #  Convert from roman numeral to a number, or 0 if invalid
   ∨       #  Logical OR with the character: if it's 0, then use the character instead
    ;      # Close map
       Ḋ   # Group results where the same result appears consecutively...
     ⁽-    # ...with subtracting it by itself. Subtracting a number by itself is 0,
           # but subtracting a string by itself is "". These are two different values, so it works as a type check.
        Ṡ  # Sum each. Summing a list of characters will turn it into a string, but lists of numbers will simply sum.
           # The s flag joins the list together into a string before outputting.
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Wow this is clever. \$\endgroup\$
    – emanresu A
    Jun 27, 2022 at 0:26
4
\$\begingroup\$

Factor + grouping.extras math.unicode roman, 100 99 bytes

[ 1 group [ [ roman> ] try ] map [ real? ] group-by values [ [ Σ >dec ] try ] map flatten concat ]

Needs modern Factor for >dec. Here's a version that runs on TIO for 3 more bytes, though: Try it online!

How?

This answer relies on try in order to process heterogeneous arrays relatively succinctly, as roman> blows up on non- roman numbers and sum blows up on arrays of strings.

                          ! "encodingish"
1 group                   ! { "e" "n" "c" "o" "d" "i" "n" "g" "i" "s" "h" }
[ [ roman> ] try ] map    ! { "e" "n" 100 "o" 500 1 "n" "g" 1 "s" "h" }
[ real? ] group-by values ! { V{ "e" "n" } V{ 100 } V{ "o" } V{ 500 1 } V{ "n" "g" } V{ 1 } V{ "s" "h" } }
[ [ Σ >dec ] try ] map    ! { V{ "e" "n" } "100" V{ "o" } "501" V{ "n" "g" } "1" V{ "s" "h" } }
flatten                   ! { "e" "n" "100" "o" "501" "n" "g" "1" "s" "h" }
concat                    ! "en100o501ng1sh"
\$\endgroup\$
2
  • \$\begingroup\$ "Language: Factor + the kitchen sink". This is why I love Factor. What other language randomly comes with a "to Roman" function built-in in some standard library somewhere? \$\endgroup\$ Jun 27, 2022 at 22:08
  • 1
    \$\begingroup\$ @SilvioMayolo A lot of golfing languages do. But they include it because they anticipate Roman numerals in golf questions. I think Factor has it because there is a small, but enthusiastic group of people who like writing stuff in Factor. Incidentally, the roman vocabulary is touted as being a good introductory example of Factor code, as it is well-written and includes many language features such as metaprogramming. \$\endgroup\$
    – chunes
    Jun 27, 2022 at 22:21
4
\$\begingroup\$

JavaScript (Node.js), 91 bytes

s=>s.replace(/[ivxlcdm]+/gi,s=>Buffer(s).map(c=>t-=~[9,4,,99,499,49,999][c%32%24%7],t=0)|t)

Try it online!

\$\endgroup\$
4
\$\begingroup\$

R, 146 bytes

\(s){`!`=is.na
T=tapply
r=as.roman(t<-el(strsplit(s,"")))
a=T(r,i<-rep(seq(a=x<-rle(!r)$l),x),sum)
a[!a]<-T(t,i,g<-\(x)Reduce(paste0,x))[!a]
g(a)}

Attempt This Online!

Definitely not the best tool for the job, despite built-in roman numerals.

Explanation outline:

  1. Split string to characters (stored in t; this part could be omitted if we took input as a vector of characters).
  2. Convert to type roman - successful for Roman digits, otherwise NA (stored in r).
  3. Build index i for spitting on consecutive runs of NAs or not NAs.
  4. Tapply (split+sapply) sum on Roman numerals.
  5. Fill NAs with chunks from original string.
  6. Collapse vector to one string.
\$\endgroup\$
3
\$\begingroup\$

Ret1na 0.8.2, 45 bytes

i(`m
dd
d
5$*c
c
ll
l
5$*x
x
vv
v
5$*i
i+
$.&

Try 1t on51ne! 51nk 1n150u500es test 100ases. E10p50anat1on:

i(`

Run the who50e s100r1pt 100ase-1nsens1t6e50y.

m
dd

1000 = 2 * 500.

d
5$*c

500 = 5 * 100.

c
ll

100 = 2 * 50.

l
5$*x

50 = 5 * 10.

x
vv

10 = 2 * 5.

v
5$*i

5 = 5 * 1.

i+
$.&

100on5ert to 500e1101a50.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ It took me a while to figure out 500e1101a50 :P \$\endgroup\$
    – emanresu A
    Jun 27, 2022 at 8:44
  • \$\begingroup\$ @emanresuA Too much decimal? \$\endgroup\$
    – Neil
    Jun 27, 2022 at 8:45
2
\$\begingroup\$

05AB1E, 21 bytes

.γu.vd}εDSu.v©àdi®O]J

Try it online or verify all test cases.

Or alternatively, with I/O as character array:

u.vøεΣa}н}.γd}ε¤diO]S

Try it online or verify all test cases.

Can definitely be golfed a bit. But despite having a Roman Number builtin, the functionality of some builtins are nowhere near as versatile as the Vyxal answer.. Why you might ask?

  1. The Roman Number builtin only works on uppercase letters (probably a bug..), so preserving the casing costs quite a few bytes.
  2. Sum given an error on letters in the new 05AB1E version, so explicit checks before summing are unfortunately necessary.

Explanation:

.γ             # Adjacent group by the (implicit) input-string:
  u            #  Convert to uppercase
   .v          #  Convert from letter to Roman Number
     d         #  Check if this is a (non-negative) integer
 }ε            # After the adjacent group-by: map over each group:
   D           #  Duplicate the current group
    S          #  Convert it to a list of characters
     u         #  Uppercase each
      .v       #  Convert it to a Roman Number
               #  (it will remain an uppercase character if invalid)
        ©      #  Store this list in variable `®` (without popping)
         àdi   #  If this list contains an integer:
            ®O #   Push and sum list `®`
  ]            # Close the if-statement and map
   J           # Join everything together to a single string
               # (which is output implicitly as result)
u              # Uppercase each character in the (implicit) input-list
 .v            # Convert each to a Roman Number
               # (it will remain an uppercase character if invalid)
   ø           # Pair each with the characters of (implicit) input-list
    ε          # Map over each pair:
     Σ         #  (Stable) sort the pair by:
      a        #   Check if it's a letter (0 if number; 1 if letter)
     }н        #  After the sort-by: pop and push the first character
    }.γ        # After the map: adjacent group by:
       d       #  Is it a (non-negative) integer
      }ε       # After the adjacent group-by: map over each group:
        ¤      #  Push the first item (without popping the group list)
         di    #  If it's a (non-negative) integer:
           O   #   Sum the group together
       ]       # Close the if-statement and map
        S      # Convert the numbers and remaining groups to a flattened list
               # of characters
               # (which is output implicitly as result)
\$\endgroup\$
2
\$\begingroup\$

Charcoal, 52 bytes

≔⁰θF⊞O⪪S¹ω«≔⌕⪪IVXLCDM¹↥ιη¿⊕η≧⁺×Xχ÷η²⊕×⁴﹪η²θ«⁺∨θωι≔⁰θ

Try it online! Link is to verbose version of code. Explanation:

≔⁰θ

Start with a running total of 0.

F⊞O⪪S¹ω«

Loop over the input characters, but include a trailing empty string.

≔⌕⪪IVXLCDM¹↥ιη

Get the index of the uppercase character in the list of Roman numerals.

¿⊕η

If this is a Roman numeral, then...

≧⁺×Xχ÷η²⊕×⁴﹪η²θ«

... use the index to convert it to decimal and add it to the running total, otherwise:

⁺∨θωι

Prefix the running total, if any, to the character before printing it.

≔⁰θ

Clear the running total.

\$\endgroup\$
2
\$\begingroup\$

Python, 130 122 117 bytes

-8 bytes by porting Arnauld's formula

-5 bytes thanks to movatica

lambda s:re.sub('[ivxlcdm]+',lambda M:str(sum(1+[9,4,0,99,499,49,999][ord(i)%32%24%7]for i in M[0])),s,0,2)
import re

Try it online!

Matches runs of roman numerals and replaces them with their sum. The 2 at the end is equivalent to re.IGNORECASE.

\$\endgroup\$
2
  • \$\begingroup\$ M[0] instead of M.group() for 117 bytes \$\endgroup\$
    – movatica
    Jun 30, 2022 at 14:11
  • 1
    \$\begingroup\$ @movatica Nice! Thanks \$\endgroup\$ Jun 30, 2022 at 14:51
1
\$\begingroup\$

C (gcc), 150 159 152 137 133 126 bytes

  • +9 to handle either case
  • -7 thanks to emanresu A
  • -22 thanks to ceilingcat
  • -4 thanks to Neil

Processes a string, looking for Roman numerals. If it finds one, the value is added to the total consecutive run. Once a non-numeral is found, the total is printed if it is greater than zero (and the total is reset), then the non-numeral is printed. Finally, the total is printed if the end of string is encountered in case the final character was a Roman numeral.

char*n="ivxlcdm",*d;t;f(char*s){for(t=0;t=!(d=index(n,*s|32))?t&&printf("%d",t),*s&&!putchar(*s):t+L"\1\5\n2dǴϨ"[d-n],*s++;);}

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ You can save 7 bytes by or with 32 and loweracse. \$\endgroup\$
    – emanresu A
    Jun 28, 2022 at 1:54
  • \$\begingroup\$ 135 bytes but feels like it should be shorter. \$\endgroup\$
    – Neil
    Jun 30, 2022 at 0:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.