Golf my Shakespeare quote references

45

9

While writing my essay for Shakespeare, I realized that I needed to shorten my quote references to more manageable lengths. I had previously been writing this:

(Act 1, Scene 2, Lines 345-346)

But I've now been told to write them like this:

(I.ii.345-6)

Clearly, I need some golfed code to golf my Shakespeare quote references down a bit.

The Task

Write a program or function that, given a string input following Template 1 or 2, print or return a string following Template 3 or 4, respectively. You only have to support Acts 1 through 5 and Scenes 1 through 9.

Templates

Template 1

(Act x, Scene y, Lines a-b)

You may assume that x never exceeds 5, y never exceeds 9, a and b are always positive integers not exceeding your language's maximum positive standard integer value, and a is always exclusively less than b.

Template 2

(Act x, Scene y, Line a)

Same conditions as Template 1, excluding information about b.

Template 3

(x.y.a-b)

Where x is a capital roman numeral, y is a lowercase roman numeral, a and b are numbers, and b is shortened to only the digits less than the first differing digit of equal significance from a.

Template 4

(x.y.a)

Same conditions as Template 3, excluding information about b.

Test Cases

Let f(s) be the function defined in the Task. "" denotes a string value.

>>> f("(Act 1, Scene 2, Lines 345-346)")
"(I.ii.345-6)"

>>> f("(Act 3, Scene 4, Lines 34-349)")
"(III.iv.34-349)"

>>> f("(Act 5, Scene 9, Lines 123-234)")
"(V.ix.123-234)"

>>> f("(Act 3, Scene 4, Line 72)")
"(III.iv.72)"

>>> f("(Act 2, Scene 3, Lines 123-133)")
"(II.iii.123-33)"

>>> f("(Act 4, Scene 8, Lines 124-133)")
"(IV.viii.124-33)"

For the purposes of this challenge, the following arabic to roman numeral translations must be supported:

1 i     I
2 ii    II
3 iii   III
4 iv    IV
5 v     V
6 vi    (you do not have to support past 5)
7 vii
8 viii
9 ix

Addison Crump

Posted 2017-05-29T19:32:40.333

Reputation: 10 763

Are text files allowed? – Dat – 2017-05-29T20:33:13.910

@Dat In what sense? Input, output, both? – Addison Crump – 2017-05-29T20:34:39.313

Text file as an input. – Dat – 2017-05-29T20:35:50.767

@Dat Absolutely fine. If you need a text file output, that's fine, too. Found it in the defaults for I/O. – Addison Crump – 2017-05-29T20:36:07.137

And do bytes from text file count? – Dat – 2017-05-29T20:40:44.717

Second test case is wrong. It should be "(V.ix.123-234)" – Luke – 2017-05-29T20:41:26.393

@Dat Not if they're just your input. If they're more than just your input, no. – Addison Crump – 2017-05-29T20:41:30.897

@Luke Typo. Thanks. :D – Addison Crump – 2017-05-29T20:41:51.760

21I'm really hoping for an answer in SPL. – L3viathan – 2017-05-29T21:18:39.293

5Test case: (Act 1, Scene 2, Lines 345-3499) – dzaima – 2017-05-29T21:33:55.207

@dzaima Ooh. Good point, magnitude check. – Addison Crump – 2017-05-29T21:34:59.430

11Who´s up for an answer in Shakespeare? – Titus – 2017-05-30T00:15:08.767

@Titus Are you nominating yourself? – MickyT – 2017-05-30T00:57:05.017

The last two test cases do not match any of the templates. They should use Lines instead of Line. – Grimmy – 2017-05-30T14:12:53.717

“b is shortened to only the digits less than the first differing digit from a”: I interpret this as meaning that 34-349 should be shortened to 34-9 (9 being the first differing digit), but the test case contradicts that. A clarification would be great. – Grimmy – 2017-05-30T14:19:16.577

1@Grimy Fixed #1, is #2 good? – Addison Crump – 2017-05-30T14:23:19.870

Yep, they’re both good, now. Great question, btw! – Grimmy – 2017-05-30T15:58:10.433

Somebody likes Shakespeare. – Erik the Outgolfer – 2017-05-30T22:37:47.573

Answers

12

The Shakespeare Programming Language (non-competing)

I really liked this question, and as there was some interest in a Shakespeare-language answer, here is one.

A Tale of Two Cites (sic).

Julius Caesar, the first citizen of the Roman Republic.
Brutus, a traitor -- member of the Fifth Column.
Cicero, the greatest Roman orator.
Cleopatra, a proud queen, whom the Romans want to make one of their own.
Romeo, a man who's sometimes there.
Juliet, a maiden who can follow Romeo or stand on her own.


           Act I: Imperium Romanum.


           Scene I: Cleopatra puts men in their place.

[Enter Cleopatra and Julius Caesar]

Julius Caesar:
    Thou art as lovely as the sum of an amazing delicious gentle blossoming warm angel and a charming noble reddest rose.
    Speak your mind. Open your mind. Open your mind. Open your mind! Open your mind!

Cleopatra:
    You are as stuffed as the sum of a hard old green horse and the sum of a grandmother and
    a normal tiny bottomless furry small purple roman.

[Exit Julius Caesar]
[Enter Brutus]

Cleopatra:
    You are as sorry as the difference between a rich morning and a leech.
    You are as smelly as the difference between yourself and a sunny rural blue bold uncle.
    You are as vile as the difference between Julius Caesar and yourself.

[Exit Brutus]
[Enter Cicero]

Cleopatra:
    You are as half-witted as the difference between Brutus and the bluest death.


           Scene II: How do you solve a problem like Cleopatra?

[Exeunt]
[Enter Cleopatra and Julius Caesar]

Julius Caesar:
    Listen to your heart!

[Exit Cleopatra]
[Enter Brutus]

Julius Caesar:
    Is Cleopatra more pretty than a fair charming noble angel?

Brutus:
    If so, we must proceed to Scene IV. Is Cleopatra not worse than the sweetest small aunt?

Julius Caesar:
    If so, let us proceed to Scene III.

Brutus:
    Speak your mind.

Julius Caesar:
    Is Cleopatra nicer than the moon?

Brutus:
    If so, speak your mind.

Julius Caesar:
    Is Cleopatra better than a golden King?

Brutus:
    If so, speak your mind.

Julius Caesar:
    We shall proceed to Scene V.


          Scene III: Brutus and his friends.
Julius Caesar:
    Is Cleopatra as fair as the blossoming smooth sky?

Brutus:
    If so, speak your mind!

Julius Caesar:
    Speak your mind!

Julius Caesar:
    Is Cleopatra jollier than the sum of a yellow sweet road and a summer's day?

Brutus:
    If so, speak your mind!

Julius Caesar:
    Is Cleopatra friendlier than the sum of a sweet large angel and a white cow?

Brutus:
    If so, speak your mind!

Julius Caesar:
    Is Cleopatra as furry as a rich handsome huge mistletoe?

Brutus:
    If so, speak your mind!

Julius Caesar:
    We shall proceed to Scene V.


          Scene IV: Cicero is asked to speak.
[Exit Brutus]
[Enter Cicero]

Julius Caesar:
    Is Cleopatra as beautiful as the sum of a small furry white angel and a summer's day?

Cicero:
    If so, speak your mind!

Julius Caesar:
    Speak YOUR mind!


          Scene V: A period piece -- Cleopatra's reprisal.
[Exeunt]
[Enter Cleopatra and Julius Caesar]

Julius Caesar:
    You are as beautiful as the sum of a embroidered sweetest sunny delicious trustworthy Lord
    and a reddest charming mighty honest King.
    You are as healthy as the difference between yourself and a embroidered Lord. Speak your mind!
    Open your mind! Open your mind! Open your mind! Open your mind! Open your mind! Open your mind!

Cleopatra:
    Are you jollier than the sum of a little rural white bottomless blue blue sky and a rural furry white green old morning?

Julius Caesar:
    If so, we must proceed to Act II. Open your mind! Open your mind!

Cleopatra:
    You are as damned as the difference between yourself and a half-witted dusty snotty rotten oozing death.

[Exit Julius Caesar]
[Enter Brutus]

Cleopatra:
    You are as rotten as the difference between yourself and a rural rotten bottomless evil miserable famine.

[Exit Brutus]
[Enter Cicero]

Cleopatra:
    You are as fatherless as the difference between Brutus and a normal pig. Let us return to Scene II.



          Act II: Lovers' arithmetick.

          Scene I: Our lovers discuss what they have in common.

[Exeunt]
[Enter Romeo and Juliet]

Romeo:
    Thou art as bold as a curse. Listen to your heart!

Juliet:
    Am I better than nothing? If so, let us proceed to Scene III.

Romeo:
    Open your mind. Open your mind.

Juliet:
    Listen to your heart! Open your heart!

Romeo:
    Thou art as amazing as the product of the difference between a handsome white proud white grandfather and an aunt
    and the sum of a loving niece and the Heaven. Speak your mind! Open your mind.
    Listen to your heart. Is the quotient between yourself and the sum of the sum of
    a noble noble mighty blossoming embroidered good father
    and a gentle large large normal old joy and an old happy squirrel as yellow as the quotient between
    myself and the sum of the sum of a pretty beautiful yellow green bold charming kingdom and
    a beautiful blue normal cute large nephew and a pretty big cousin?

Juliet:
    If not, we shall proceed to Scene II.

Romeo:
    You are as sweet as the remainder of the quotient between yourself and the sum of the sum of
    a blossoming bottomless golden peaceful noble healthy nose and
    a happy honest sunny green healthy hero and a hard blue fellow.

Juliet:
    YOU are as sweet as the remainder of the quotient between yourself and the sum of the sum of
    a blossoming bottomless golden peaceful noble healthy nose and
    a happy honest sunny green healthy hero and a hard blue fellow.


          Scene II: Tense times.
Juliet:
    Is the quotient between yourself and the sum of a good beautiful delicious grandmother
    and a noble wind as amazing as the quotient between myself and the sum of
    a smooth furry embroidered roman and a honest sister?

Romeo:
    If so, you are as amazing as the remainder of the quotient between
    yourself and the sum of a cute healthy smooth kingdom and a normal mother.


          Scene III: Parting is such sweet sorrow.
Romeo:
    Open your heart! You are as noble as the sum of a honest charming smooth peaceful fine rose and the sum of
    a cute amazing trustworthy summer's day and an angel. Speak your mind!

(It's over 6000 bytes long.) There are some tricks in there but I have not tried to golf it much, because: (1) I already contributed my share of golfing on another answer, and (2) changing all characters to "Page" and "Puck", or all phrases to "big big big big big cat", seems to spoil the fun. Instead for the part that deals with Roman numerals I have used characters who are Romans, etc. I did reuse characters and instructions to save some typing. :-)

The program should be mostly straightforward but one wrinkle worth mentioning is that when I wrote this I assumed that reading an integer would work like scanf: (1) consume only as many characters from the input as corresponds to an integer, and (2) in case of failure, leave the variable unchanged. (I used this second property to distinguish between Template 1 and 2 in Act II, by reading up to "Line" and attempting to read an integer.) Unfortunately it turns out that there's (what I consider) a bug in the original implementation of the language where reading an integer consumes everything up to the end of the line and throws an error if it fails, so it needs a patch to libspl.c to make int_input behave more like scanf.

And with that, it works:

% spl2c < good.spl > good.c
% gcc -lspl -o good good.c                                    
% for f in in-*; do cat $f; echo "->"; ./good < $f; echo "\n"; done    
(Act 1, Scene 2, Lines 345-346)
->
(I.ii.345-6)

(Act 3, Scene 4, Lines 34-349)
->
(III.iv.34-349)

(Act 5, Scene 9, Lines 123-234)
->
(V.ix.123-234)

(Act 3, Scene 4, Line 72)
->
(III.iv.72)

(Act 2, Scene 3, Lines 123-133)
->
(II.iii.123-33)

(Act 4, Scene 8, Lines 124-133)
->
(IV.viii.124-33)

Slightly higher-level pseudocode that I worked off of, to help anyone trying to understand:

Print `(`=40
Read 5 chars
Read Int A
Output A in Roman
Output `.`=46
Read 8 chars
Read Int S
Output S in roman
Output `.`=46
Read 6 chars
Set N to -1
Read Int N
If N ≠ -1 goto finish
Read 2 chars
Read Int M
Output Int M
Output `-`=45
Read 1 char
Read Int N
Reduce N wrt M
finish:
Output N
Print `)`=41

Relating the above to the final code is left as an exercise. :-) Note that ShakespearePL has arithmetic and stacks and gotos but no pointers (only labels), so implementing "subroutines" like the conversion to Roman is a bit… interesting.

ShreevatsaR

Posted 2017-05-29T19:32:40.333

Reputation: 923

Wow, that's beautiful. Thank you! :) – Steve Bennett – 2017-06-06T05:41:21.517

1slaps the upvote button repeatedly – Addison Crump – 2017-06-06T06:08:53.243

9

LaTeX, 513 364 259 226 215 178 159 Bytes

Good essays should always be written in LaTeX.

\documentclass{tui}\makeatletter\input{xstring}\def~#1 #2, #3 #4, #5 #6){(\@Roman#2.\@roman#4.\StrCut{#6}-\A\B\A\if\B\else-\fi\StrCompare\A\B[\P]\StrMid\B\P9)}

This uses the xstring package as there isn't exactly a lot of string handling built in. On the plus side the upper limit for the built-in \Roman formatting is larger than we'll ever need (even for the sonnets) at 2^31-1. I've included \documentclass{ecv} in the count, but none of the test code:

\begin{document}
\t(Act 1, Scene 2, Lines 345-346) 

\t(Act 3, Scene 4, Lines 34-349)

\t(Act 5, Scene 9, Lines 123-234)

\t(Act 3, Scene 4, Line 72)

\t(Act 2, Scene 3, Lines 123-133)

\t(Act 4, Scene 8, Lines 124-133)
\end{document}

(If you were crazy enough to actually use this, you'd have to ungolf the macro names at least. Overwriting single character macros is bad practice)

Ungolfed and commented:

\documentclass{ecv}%We have to have a documentclass
\makeatletter %treat the @ sign as a normal character (it's used in "internal" macro names)
\input{xstring} %use the xstring package
\def\shortref#1 #2, #3 #4, #5 #6){ %macro with 6 positional arguments searated by spaces and commas 
    (%print the open bracket
    \@Roman#2 %arg 2 is the Act,  print uppercase Roman
    .%print the full stop
    \roman#4 %arg 4 is the Scene, lowercase roman
    .%print the full stop
    \StrCut{#6}{-}{\A}{\B}%split arg 6 (Lines) at the hyphen, into macros \A and \B
    \A %print the bit before the hyphen
    \ifx\B\empty%if \B has nothing in it do nothing
    \else %otherwise
        - %we need to print a hyphen
    \fi%endif
    \StrCompare{\A}{\B}[\P] %returns (in macro \P) the position at which \A and \B first differ
    \StrMid{\B}{\P}{9}%print \B starting from position \P (9 > the number of remaining characters)
)}%print the closing bracket

Note that in this version the comments are required otherwise the newline is interpreted as whitespace and expands to a space.

Chris H

Posted 2017-05-29T19:32:40.333

Reputation: 581

You could save three bytes by using ~ as your macro name instead of \s. But actually you don't need \s (\stripcomma in the ungolfed version) at all: you can just \def\t#1 #2, #3 #4, #5 #6 and TeX will take care of stripping the commas. (So you could use the ~ trick on \t instead, saving 1 byte.) – ShreevatsaR – 2017-05-31T15:05:43.587

@ShreevatsaR thank you. You figured out how to get the comma rejection inline and it was simpler than anything I tried. The trick with the active ~ is a bit nasty but I like it here. It meant I had to change the documentclass (to one of the other 3-letter .cls files I had installed) – Chris H – 2017-05-31T15:18:18.877

Actually there seems to be a bug: I see 345-346 for the first one (instead of 345-6), and 124-133 for the last one (instead of 124-33). – ShreevatsaR – 2017-05-31T15:29:13.400

@ShreevatsaR I've introduced that at some point. I wonder how. \ifx\B\empty\else saved a few as well – Chris H – 2017-05-31T15:31:33.763

@ShreevatsaR got it: I needed to terminate the defintion of ~ with #6) to ensure the whole last value got picked up. But in debugging I noticed where I could save another 4B: \StrCut doesn't need braces around the macros it assigns to. – Chris H – 2017-05-31T15:47:30.880

And now better than Python! – Chris H – 2017-05-31T15:49:09.520

Nice! You can also save quite a few more bytes with \makeatletter (aka \catcode\@=11but that's the same length) and then using@Roman{#2}and@roman{#4}— you don't need the counterz` at all. The code actually becomes cleaner :D – ShreevatsaR – 2017-05-31T15:53:11.823

@ShreevatsaR that's great. Thank you. I'll have a look tomorrow when I'm in front of a computer again but it looks like it's worth around 30B – Chris H – 2017-05-31T16:27:25.893

1Yes I count 182 bytes, which beats not just the Python answer but also Ruby, PHP and one of the Perl answers :-) – ShreevatsaR – 2017-06-01T06:28:30.020

1@ShreevatsaR better still: 178 as \@roman and \@Roman don't need braces around the argument. – Chris H – 2017-06-01T07:58:03.957

So cool. Looks like some more can go too (all except the one for \def and the one after \StrCut), bringing it down to 166 bytes (if I'm counting correctly). Would beat the Javascript answer and match the R answer :-) – ShreevatsaR – 2017-06-02T05:16:55.317

Also the \ifx can be just \if I think… or even \ifx\B\empty\else-\fi can be just \if\B\else-\fi it looks like (not sure I understand this yet). If this is correct, this would make it 159. – ShreevatsaR – 2017-06-02T05:22:10.963

@ShreevatsaR \if\B\else-\fi works, so 159 as you say. I reckon next time there's a challenge suitable for LaTeX you'll have an entry -- and beat me. – Chris H – 2017-06-02T08:54:09.440

1All the main xstring ideas were yours :-) It was fun golfing this together! – ShreevatsaR – 2017-06-02T17:00:49.383

8

JavaScript (ES6), 210 183 178 177 171 bytes

Saved 27 bytes by unrolling rest parameters (thanks to ETHproductions)

Saved 5 bytes by not matching the opening paren and by tweaking the roman numeral generation

Saved 1 byte by adjusting the final ternary expression

Saved 6 bytes by combining two matching groups

s=>s.replace(/Act (\d)\D*(\d)\D*(\d*)(\d*-?)\3(\d*)/,(_,a,b,c,d,e)=>'IIIV'.slice(a>3&&a-2,a)+'.'+'iiiviiix'.slice('233336'[b-4],b-(b>4))+'.'+c+d+(d.length>e.length?e:c+e))

Test Cases:

let f = s=>s.replace(/Act (\d)\D*(\d)\D*(\d*)(\d*-?)\3(\d*)/,(_,a,b,c,d,e)=>'IIIV'.slice(a>3&&a-2,a)+'.'+'iiiviiix'.slice('233336'[b-4],b-(b>4))+'.'+c+d+(d.length>e.length?e:c+e))

;[
  ["(Act 1, Scene 2, Lines 345-346)", "(I.ii.345-6)"],
  ["(Act 3, Scene 4, Lines 34-349)", "(III.iv.34-349)"],
  ["(Act 5, Scene 9, Lines 123-234)", "(V.ix.123-234)"],
  ["(Act 3, Scene 4, Line 72)", "(III.iv.72)"],
  ["(Act 2, Scene 3, Line 123-133)", "(II.iii.123-33)"],
  ["(Act 4, Scene 8, Line 124-133)", "(IV.viii.124-33)"],
  ["(Act 1, Scene 2, Lines 345-3499)", "(I.ii.345-3499)"]
].map(([a,b]) => console.log(`${a} => ${f(a)} (${f(a) == b ? 'Pass' : 'Fail'})`))
.as-console-wrapper { min-height: 100% }

gyre

Posted 2017-05-29T19:32:40.333

Reputation: 273

I can't test right now, but does it still work if you replace Act and each \D* with .*? – Patrick Roberts – 2017-05-30T19:02:36.113

It might; I was hesitant to try because JavaScript employs greedy matching by default. I'll test it later today and let you know if it works. – gyre – 2017-05-30T20:11:37.677

8

Jelly,  87 86  85 bytes

DµU=/œr1LCṫ@Ṫ
Ṗ,Çj”-µ⁸L’$?W
⁾-,yḲḊm2Ṗ€Vµ“¬Q:’ṃ⁾IV;”X“¤|ʂ’BṚ¤œṗị@µ1,2¦Œl2¦µ;ṪÇ$“(..)”ż

Try it online! or see a test suite

How?

DµU=/œr1LCṫ@Ṫ - Link 1, toPageShort: list of numbers [fromPage, toPage]  e.g.  [345,365]
D             - cast to decimal lists                                 [[3,4,5],[3,6,5]]
 µ            - monadic chain separation (call that d)
  U           - reverse each                                          [[5,4,3],[5,6,3]]
   =/         - reduce by equality                                              [1,0,1]
     œr1      - strip any 1s from the right                                       [1,0]
        L     - length                                                                2
         C    - complement (1-z)                                                     -1
            Ṫ - tail d                                                          [3,6,5]
          ṫ@  - tail from index (swap @rguments)                                  [6,5]

Ṗ,Çj”-µ⁸L’$?W - Link 2, format page number(s): number or list of two numbers
           ?  - if:
          $   -   last two links as a monad:
        L     -     length
         ’    -     decremented (0 for a number, 1 for a list of two numbers)
      µ       - then:
Ṗ             -   pop (list without the last item, i.e. just the from page)
  Ç           -   call the last link (1) as a monad with the list as its argument
 ,            -   pair
    ”-        -   literal '-'
   j          -   join
              - else:
       ⁸      -   link's left argument (the single page number)
            W - wrap the result in a list (for ease of post-formatting in Main)

⁾-,yḲḊm2Ṗ€Vµ ... µ1,2¦Œl2¦µ;ṪÇ$“(..)”ż - Main link: string s
⁾-,                                    - literal ['-',',']
   y                                   - map (change '-' to ',')
    Ḳ                                  - split at spaces
     Ḋ                                 - dequeue (get all but 1st)
      m2                               - mod-index-2 (1st,3rd,5th)
        Ṗ€                             - pop €ach (ditch last char)
          V                            - evaluate - list of numbers
                                       -   either [act,scene,page] or
                                       -   [act,scene,[from,to]]
           µ     µ   ¦                 - apply to indexes:
                  1,2                  - [1,2]
             ...                       -   see monadic chain " ... ", below
                        2¦             - apply to index 2:
                      Œl               -   lowercase
                          µ            - monadic chain separation
                              $        - last 2 links as a monad:
                            Ṫ          -   tail (page(s))
                             Ç         -   call last link (2) as a monad
                           ;           - concatenate
                               “(..)”  - literal ['(','.','.',')']
                                     ż - zip together
                                       - implicit print

“¬Q:’ṃ⁾IV;”X“¤|ʂ’BṚ¤œṗị@ - monadic chain " ... " from Main
                         -   Get Roman numeral: number n (n>0, n<10) (act or scene)
“¬Q:’                    - base 250 literal 520559
      ⁾IV                - literal ['I','V']
     ṃ                   - base decompression -> "IIIIIIIVVVIVIIVIIII"
          ”X             - literal 'X'
         ;               - concatenate -> "IIIIIIIVVVIVIIVIIIIX"
                   ¤     - nilad followed by link(s) as a nilad:
            “¤|ʂ’        -   base 250 literal 281418
                 B       -   convert to a binary list
                  Ṛ      -   reverse
                    œṗ   -   split at truthy indexes i.e: I II III IV V VI VII VIII IX
                      ị@ -   index into (swap @arguments) using n

Jonathan Allan

Posted 2017-05-29T19:32:40.333

Reputation: 67 804

1That's got to be some sort of record for a Jelly script – MickyT – 2017-05-30T02:25:07.280

@MickyT Nope I have longer out there... – Jonathan Allan – 2017-05-30T02:25:39.550

This induces headache. Don't read it 0/10. :P – Erik the Outgolfer – 2017-05-30T22:36:54.283

@EriktheOutgolfer Sorry!! – Jonathan Allan – 2017-05-30T22:38:55.760

2Jokes aside, I can see some really unique stuff in that code, like œr, Ṗ,Ç, Ṗ€V, ṪÇ$, W as the last link on a helper link, possibly others too, nice effort! This isn't your usual 80-something Jelly submission, this deserves special recognition among Jelly people. – Erik the Outgolfer – 2017-05-30T22:42:54.547

6

R, 94 126 112 166 bytes

And now it's wordier than before :(, back to trying to golf it further. Regex for reducing the page reference shamelessly stolen borrowed from @FryAmTheEggman.

Now I really need to do some work to get back the bytes, but it works for the second case now.

R=as.roman;i=sub(',','',scan(,''));sprintf('(%s.%s.%s',R(i[2]),tolower(R(i[4])),`if`(!diff(c(nchar(el(strsplit(i[6],'-'))),0))-1,sub('((.+).*-)\\2','\\1',i[6]),i[6]))

Try it online! - Note that el doesn't work on TIO and has been replaced with unlist

R=as.roman;                              # Used to convert to roman numeral class
i=sub(',','',scan(,''));                 # Get input, splits on spaces, remove ,'s
sprintf('(%s.%s.%s',                     # Use sprintf to format the output.
    R(i[2]),                             # Convert to roman numeral
    tolower(R(i[4])),                    # Convert to roman numeral and lowercase
    `if`(                                #
       !diff(                            # Test if the lengths of the last string
       c(nchar(el(strsplit(i[6],'-'))),0)# split on the hyphen are different (extra 0 to appease diff)
       )-1,                              # taking into account the trailing )
       sub('((.+).*-)\\2','\\1',i[6]),   # on true use regex to reduce range
       i[6]                              # else output as is
    )
)

MickyT

Posted 2017-05-29T19:32:40.333

Reputation: 11 735

4

Retina, 89 88 bytes

T`, lL`._
2`(\d+)
$*i
i{5}
v
i{4}
iv
viv
ix
1T`l`L`\w+
(\b(.+)(.)*-)\2((?<-3>.)*)\b
$1$4

Try it online!

Saved 3 bytes thanks to Neil.

Strips away the unnecessary characters before replacing the first two numbers with blocks of i characters. Then it matches chunks of these is to form the appropriate roman numerals. Then we capitalise the first roman numeral. Finally we match as many numbers as we can before the hyphen and after the hyphen such that the number of digits in the number is the same. We then remove that prefix from the second number.

FryAmTheEggman

Posted 2017-05-29T19:32:40.333

Reputation: 16 206

Replacing iiiii with v, iiii with iv and viv with ix seems to save a couple of bytes. – Neil – 2017-05-29T23:56:39.800

However, your line number shortening seems to be getting it wrong for 345-356 - I was expecting 345-56. – Neil – 2017-05-30T00:00:12.457

@Neil Whoops, forgot the kleene expansion. Anyway thanks for the tip! – FryAmTheEggman – 2017-05-30T00:18:11.857

Can you use \b at the end of the last replacement to avoid having to repeat the ) in the substitution? – Neil – 2017-05-30T07:53:08.713

@Neil I didn't think that would work since I thought I'd need to use \d but it does seem to work since there isn't another word boundary. Thanks! – FryAmTheEggman – 2017-05-30T13:46:30.543

4

PHP>=7.1, 195 Bytes

preg_match_all("#\d+#",$argn,$t);[[$a,$s,$b,$e]]=$t;for(;$e&&~$c=$e[$k-=1];$p="-".substr($e,$i))$c>$b[$k]&&$i=$k;echo"(",strtoupper(($r=[_,i,ii,iii,iv,v,vi,vii,viii,ix])[$a]),".$r[$s].$b",$p,")";

Testcases

Expanded

preg_match_all("#\d+#",$argn,$t); # match for all groups of digits
[[$a,$s,$b,$e]]=$t; # shorter variables names for these groups
for(;$e&&~$c=$e[$k-=1];$p="-".substr($e,$i)) # prepare the seceond line if exists
  $c>$b[$k]&&$i=$k; 
echo"(" # print the char (
,strtoupper(($r=[_,i,ii,iii,iv,v,vi,vii,viii,ix])[$a]) # print the upper roman digit for Act
,".$r[$s].$b" # print the lower roman digit for Scene and the first line with trailing "."
,$p # print shorted second Line
,")"; #Print the closing )

Jörg Hülsermann

Posted 2017-05-29T19:32:40.333

Reputation: 13 026

1preg_match_all("#\d+#",$argn,$m);[$a,$s,$b,$e]=$m[0]; saves two bytes. if($e){for(;$b[$i]==$e[$i];$i++);echo"-",substr($e,$i);}echo")"; should save 46. (you do not have to support past 5) saves 15 bytes. – Titus – 2017-05-30T00:29:02.690

1".$r[$s].$b" saves another 5 bytes; and [[$a,$s,$b,$e]]=$m; another one. Unfortunately, array assignments don´t work by reference (yet). – Titus – 2017-05-30T00:41:43.543

if($e&&$e-$b){for($x=str_pad($b,strlen($e),0,0);$x[$i]==$e[$i];$i++);echo"-",substr($e,$i);} saves 10 bytes and might work. – Titus – 2017-05-30T00:57:05.697

Looks ok to me. And &&$e-$b is unnecessary for the test cases; so it saves 17 bytes, not 10. Btw. you still don´t need roman 6 to 9. ;) – Titus – 2017-05-30T01:17:59.100

@Titus I need the roman number about 5 for the testcases with scene over 5 – Jörg Hülsermann – 2017-05-30T01:24:33.720

1You could replace for(;str_pad($b,strlen($e),0,0)[$i]==$e[$i];)$i++; with for(;$e&&~$c=$e[-++$k];)$c>$b[-$k]&&$i=-$k;. – Christoph – 2017-05-31T12:07:48.623

@Christoph Thank You after an own shorter approach that not work, I have reduced your idea – Jörg Hülsermann – 2017-05-31T14:02:24.723

3

Perl 5, 185 + 1 = 186 bytes

1 byte penalty for the required -n flag.

May fail for some test cases where the scene has more than 10^11 lines, but AFAIK no Shakespeare scenes are quite that long ;)

y/A-z //d;while($x++<9){s/,(\d+)(\d{$x})-\g1(\d{$x}\))/,$1$2-$3/};@F=split/,/;for($F[0],$F[1]){$_.='i'while(y/2-91/1-8/d);s/i{5}/v/g;s/i{4}/iv/;s/v(i)?v/$1x/;s/$/,/};$F[0]=uc$F[0];say@F

In readable form:

y/A-z //d; #Delete all characters besides numbers, parenthesis, and comma
while($x++<9){s/,(\d+)(\d{$x})-\g1(\d{$x}\))/,$1$2-$3/}; #Shortens the line numbers. Super ugly, but my simpler code broke on test case 2- that fix added 26 bytes :( I'll revisit this later, perhaps...
@F=split/,/; #Splits the input into an array so we can mess with the act and scene without messing with the lines
for($F[0],$F[1]){ #For loop over the act and scene
    $_.='i'while(y/2-91/1-8/d); #Recursively turn numbers into naive Roman numerals (i.e. 9 would be iiiiiiiii)
    s/i{5}/v/g;s/i{4}/iv/;s/v(i)?v/$1x/;s/$/,/ #Substitution rules to convert naive Roman numerals into real Roman numerals and add a comma to the end
}
$F[0]=uc$F[0]; #Convert act to uppercase
say@F #Output

Chris

Posted 2017-05-29T19:32:40.333

Reputation: 1 313

2

Python 2, 301 259 252 221 bytes

A whopping -31 bytes thanks to Chas Brown.

So, uh, this is... extremely long... I think I can golf this but I've been wracking my brain for a while.

import re
def f(s):s,r=re.match(r'.*?(\d),.*?(\d), .*? (\d*)(\d*-?)\3(\d*)',s),'0 i ii iii iv v vi vii viii ix'.split();b,c,d,e,f=s.groups();print'(%s.%s.%s)'%(r[int(b)].upper(),r[int(c)],d+e+(f if len(e)>len(f)else d+f))

Try it online!

Breakdown

import re                     # re module for regex stuff

def f(s):                     # function that accepts one argument

    s, r = (re.match(r'.*?(\d),.*?(\d), .*? (\d*)(\d*-?)\3(\d*)', s),
           # match the string and capture the important parts using regex

           '0 i ii iii iv v vi vii viii ix'.split()
           # array that stores roman numerals

    b, c, d, e, f = s.groups()
                    # all the numbers from the match to variables

    print '(%s.%s.%s)' % (
                              r[int(b)].upper(),
                              # get the respective roman numeral and make it uppercase

                              r[int(c)],
                              # get the respective roman numeral

                              d + e + (f if len(e) > len(f) else d + f)
                              # shorten the second number if it's shorter than the first number
                         )

totallyhuman

Posted 2017-05-29T19:32:40.333

Reputation: 15 378

You can save a bit by using b,c,d,e,f=s.groups() instead of a,b,c,d,e,f=[s.group(n) for n in range(6)] – Chas Brown – 2017-05-30T21:34:26.843

And another 5 by using [0]+'i,ii,iii,iv,v,vi,vii,viii,ix'.split(',') instead of [s,'i','ii','iii','iv','v','vi','vii','viii','ix']. – Chas Brown – 2017-05-30T21:50:14.437

Amended - And another 8 by using [0]+'i ii iii iv v vi vii viii ix'.split() instead of [s,'i','ii','iii','iv','v','vi','vii','viii','ix']. – Chas Brown – 2017-05-30T21:57:47.073

Oh, huh, didn't know you could do that. Thanks! – totallyhuman – 2017-05-30T21:58:04.090

Nice tweak putting the 0 inside the quotes. One last minor tweak I can see: you are using: s,r=XXX,YYY;b,c,d,e,f=s.groups(); you can save another 4 bytes by instead equivalently saying: b,c,d,e,f=XXX.groups();r=YYY;. So you end up with 81 bytes less than my submission! :) – Chas Brown – 2017-05-31T01:35:29.940

2

Ruby, 204+1 = 205 bytes

Uses the -p flag.

d=[r=%w"i ii iii iv v vi vii viii ix",r.map(&:upcase)]
i=-1
gsub(/\w+ ([\d-]+)/){(a=d.pop)?a[$1.hex]:(a,b=$1.split ?-;b ?(a="%0#{b.size}d"%a;b[0]=""while b[0]==a[i+=1];a.sub(/^0*/){}+?-+b):a)}
gsub", ",?.

Try it online!

Value Ink

Posted 2017-05-29T19:32:40.333

Reputation: 10 608

2

Python 2.7 298 bytes

import re
r=lambda n:'iiiviiix'[2*(n>3)+(n>4)+3*(n>8):n-(n>4)]
o=lambda x,y,n=0:n*(len(x)==len(y))if not x or x[0]!=y[0]else o(x[1:],y[1:],n+1)
q=lambda a,s,g,h:(r(int(a)).upper(),r(int(s)),g+'-'+h[o(g,h):]if h else g)
f=lambda p:'(%s.%s.%s)'%q(*re.match('\D*(\d)\D*(\d)\D*(\d+).(\d*)',p).groups())

Chas Brown

Posted 2017-05-29T19:32:40.333

Reputation: 8 959

2

Perl, 99 bytes

/(.+)(-\1|.(?2).)\b/;@r=(s/-$1/-/,I,II,III,IV,V,VI,VII,VIII,IX);s/Act(.+)(.,).+ /$r[$1].\L$r[$2]./

Run with perl -pe. 98 bytes (source) + 1 byte (p flag) = 99.

Grimmy

Posted 2017-05-29T19:32:40.333

Reputation: 12 521

At the time of this posting, there is another Perl answer (https://codegolf.stackexchange.com/a/123400/6484), but it is 186 bytes long and uses very different ideas, so I felt making a separate answer was appropriate.

– Grimmy – 2017-05-30T15:51:31.333

This seems to be wasteful as it takes provisions for roman numerals beyond 5 – Hagen von Eitzen – 2017-05-30T22:07:46.907

2@HagenvonEitzen the challenge specifies that you have to support roman numerals up to 9. Test case 3 has “Scene 9”, and test case 6 has “Scene 8”; both would fail if I only supported roman numerals up to 5. – Grimmy – 2017-05-31T07:07:04.293

2

q/kdb+, 200 187 bytes

Solution:

f:{R:(($:)N:(!)11)!($:)``i`ii`iii`iv`v`vi`vii`viii`ix`x;S:","vs x inter .Q.n,",-";L:"-"vs P:S 2;"(",("."sv(upper R S 0;R S 1;$[{x[y]=x z}[#:;F:L 0;T:L 1];F,"-",((*:)N(&:)F<>T)_T;P])),")"}

Examples:

q)f "(Act 1, Scene 2, Lines 345-346)"
"(I.ii.345-6)"
q)f "(Act 3, Scene 4, Lines 34-349)"
"(III.iv.34-349)"
q)f "(Act 5, Scene 9, Lines 123-234)"
"(V.ix.123-234)"
q)f "(Act 3, Scene 4, Line 72)"
"(III.iv.72)"
q)f "(Act 2, Scene 3, Lines 123-133)"
"(II.iii.123-33)"
q)f "(Act 4, Scene 8, Lines 124-133)"
"(IV.viii.124-33)"

Explanation: (slightly ungolfed)

f:{
  // create map of 0->10 to roman numerals, e.g. "5" -> "v"
  R:(($:)N:(!)11)!($:)``i`ii`iii`iv`v`vi`vii`viii`ix`x;
  // remove everything from the input string except -, then split on ,
  S:","vs x inter .Q.n,",-";
  // split the final field on '-', also save final field in variable P
  L:"-"vs P:S 2;
  // if the length of from/to lines are the same then find the first character
  // where they differ, and cut this many characters from the 'to' string
  M:$[{x[y]=x z}[#:;F:L 0;T:L 1];F,"-",((*:)N(&:)F<>T)_T;P];
  // join everything together, use act/scene to index into 
  // the roman numeral map, uppercase the act
  "(",("."sv(upper R S 0;R S 1;M)),")"
  }

Notes:

Technically it can be 2 bytes shorter (no need for the f:) but makes it easier to show examples this way.

Edits:

  • -13 bytes; replaced string with $:, count with #:, til with (!) and first with (*:), cast the indices of R to strings so we dont have to cast act/scene into ints

streetster

Posted 2017-05-29T19:32:40.333

Reputation: 3 635