7
\$\begingroup\$

Prior to the decimalisation of Sterling in February 1971, a pound (£ - from Roman libra) comprised 20 shillings (s - solidus), each of 12 pennies (d - denarius). Additionally, until 1960, each penny could be divided into four farthings (some parts of the Commonwealth also had fractions of farthings, but we'll ignore those in this question).

Your task is to write a simple adding machine for financial quantities in the old system. You will receive a set of strings, and emit/return a single string that represents the total of all the inputs. The format of input and output strings is as follows (adapted from Wikipedia: £sd):

  1. For quantities less than 1 shilling, the number of pence followed by the letter d:

    ¼d            -- smallest amount representable
    1d
    11¾d          -- largest value in this format
    
  2. For quantities less than £1, / is used to separate shillings and pence, with - as a placeholder if there are no pence:

    1/-           -- smallest amount in this format
    1/6
    19/11¾        -- largest value in this format
    
  3. For quantities of £1 or greater, we separate the parts with ., and include the units:

    £1
    £1.-.¼d       -- one pound and one farthing
    £1.1s.-       -- one pound and one shilling
    £999.19s.11¾d -- largest value needed in this challenge
    

The inputs will never sum to £1000 or more (i.e. you can assume the pounds will fit into 3 digits).

Input will be as strings by any of the usual input methods (as an array of strings, as a single string with newlines or other separators, as individual arguments, etc). There will be no additional characters (not mentioned above) in each string.

Output will be as a single string, by any of the usual output methods (standard output stream, function return value, global variable, etc.). You may include additional spaces around the numbers if that helps.

Examples

(assuming my mental arithmetic is up to scratch!)

  1. Input: ¼d, ¼d
    Output: ½d
  2. Input: 11¾d, ¼d
    Output: 1/-
  3. Input: £1, 6d, 9d
    Output: £1.1s.3d
  4. Input: £71.11s.4¼d, 12/6, 8/-, 8/-, 2½d
    Output: £73.-.¾d

If (and only if) your language/platform cannot represent the farthings (yes, I'm looking at you, ZX81!), you may use the letters n, w and r instead, to mean ¼ (one), ½ (two) and ¾ (three) respectively.

This is , so the shortest working program (in bytes) is the winner. Please indicate the source character-coding if it's not UTF-8 (and not determined by the language).

Standard loopholes which are no longer funny are not permitted.

\$\endgroup\$
11
  • 4
    \$\begingroup\$ I think you should allow using the ASCII representation for all languages because otherwise it gives a disadvantage to languages that support multibyte characters (because then it requires more bytes to check the character because scoring is by bytes) \$\endgroup\$ – hyper-neutrino Jan 17 '18 at 16:26
  • 2
    \$\begingroup\$ @HyperNeutrino, I thought about that; it's not so much about giving an advantage to the deprived environments as allowing them to compete at all. I'd be surprised to see it make a difference (particularly as many languages can use single-byte encodings, and that's explicitly allowed). \$\endgroup\$ – Toby Speight Jan 17 '18 at 16:37
  • 2
    \$\begingroup\$ @TobySpeight Some of those with single byte encodings may not have all three necessary fractions, but may be able to take UTF-8. Should they be punished? \$\endgroup\$ – Adám Jan 17 '18 at 16:45
  • 6
    \$\begingroup\$ I feel like we don't need to worry about giving a "disadvantage" to languages with custom code pages. The challenge is the challenge. No challenge will ever be completely equal in all languages. \$\endgroup\$ – dylnan Jan 17 '18 at 17:32
  • 2
    \$\begingroup\$ Thanks @Ourous - now corrected, by adding an additional term to reach the right total. I did admit that my mental arithmetic might be a bit rusty! \$\endgroup\$ – Toby Speight Jan 17 '18 at 22:30
2
\$\begingroup\$

Clean, 555 ... 504 bytes

Uses ANSI codepage Windows-1252, but since TIO treats Clean source files as UTF-8, all invalid UTF-8 character literals are represented with octal escape sequences and counted as one byte.

import StdEnv,Text,StdLib
n=0.0
e=entier
r=toReal
*0=""
*n=fromInt n
?'\274'=".25"
?'\275'=".5"
?'\276'=".75"
?c={c}
@s=r{#d\\c<-:s,d<-: ?c|all((<>)c)['sd\243']}
$s|"9"<s%(0,0)=(map@(split"."s)++[n,n])%(0,2)|endsWith"d"s=[n,n,@s]=[n:map@(split"/"s)]
^[x,y,z]#d=e z
#p=if(z>r d){'\273'+toChar(e((z-r d)/0.25))}""
#(y,d)=(d/12+e y,d rem 12)
#[x,y,d:_]=map*[y/20+e x,y rem 20,d]
|x>""=join"."["\243"+++x,y+++if(y>"")"s""-",d+++if(z>n)(p+++"d")"-"]|y>""=join"/"[y,d+++if(z>n)p"-"]=d+++p+++"d"

^o foldr(zipWith(+))[n,n,n]o map$

A partial function literal, taking [String] and returning String.

Try it online!

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

APL (Dyalog Classic), 265 260 bytes

Now works even for edge cases not in the example cases.

Full program. Prompts (STDERR) for input (STDIN) of list of strings. Outputs to STDOUT.

Assumes ⎕IO (Index Origin) to be 0, which is default on many systems.

0(('^0' '$',d)⎕R('' 'd',f)⍕2↓t)r(r←' 0d' ' 0?'⎕R'/-' '/'⊢d⎕R f,∘'d'⍕1↓t)('£',⍕⊃t)(∊'£'(⍕⊃t)'.','-'('s',⍨⍕1⊃t)[×1⊃t]'.','-d' '-'⎕R'-' ''⊢'d',⍨(d,⊂'^0')⎕R(f,'-')⍕2⊃t)[5⌊2⊥×t←v⊤+/((v←0 20 12)⊥(¯3+6×'£'=⊃)↑∘⍎('-' '\D',⍨f←,¨'nwr')⎕R('0 ',⍨1↓¨d←('\.',⍕)¨25 5 75))¨⎕]

Try it online! (or equivalent Unicode edition sporting proper fractions)

Explanation

This program has five parts; one that handles input, conversion, summation, and selection of output format, and one for each of the four possible output formats which are:

  1. less than a shilling: ¼d11¾d
  2. a whole number of shilling, which is the same format as
  3. between a shilling and a pound: 1/-19/11¾
  4. whole pounds: £1, £2,… £999
  5. everything else: £1.-.¼d£999.19s.11¾d

The overall structure of the program is:

0( less )r(r←' between )( whole )( everything )[ selection ]

This uses selection to index from a list of all possible formats. The initial 0 is a placeholder for output format 0 which does not exist. r← assigns format 4. to the variable r, which is then used as format 2.

input, conversion, summation, and selection of output format

5⌊2⊥×t←v⊤+/((v←0 20 12)⊥(¯3+6×'£'=⊃)↑∘⍎('-' '\D',⍨f←,¨'nwr')⎕R('0' ' ',⍨1↓¨d←('\.',⍕)¨25 5 75))¨⎕

 prompt for (evaluated) user input (list of strings)

( on each string, apply the following tacit function:

  25 5 75 the numeric list [25,5,75]

  ( on each number, apply the following function:

    the string representation

   '\.', preceded by an escaped (for PCRE) period

  d← store that in d (for decimals; ["\.25","\.5","\.75"])

  1↓¨ drop one character from each; [".25",".5",".75"]

  '0 ',⍨ append the two characters; [".25",".5",".75","0"," "]

  ()⎕R PCRE Replace the following strings with the above:

   ,¨'nwr' ravel (make into own vector) each of these characters; ["n","w","r"]

   f← store in f (for fractions)

   '-' '\D',⍨ append these two strings; ["n","w","r","-","\D"]

   execute as APL statement (this gives a numeric list)

   then

  ()↑ take this many elements (padding with zeros as needed):

    the first of the argument (one of the strings)

   '£'= Boolean (0 or 1) whether it is a pound symbol

    multiply by six; gives 6 if string had pound(s), 0 if not

   ¯3+ add negative three; gives 3 if string had pound(s), −3 if not

Taking a negative number of elements takes from the right and pads on the left if needed. Taking a positive number of elements takes from the left and pads on the right if needed. Thus we now have pounds, shillings, and pence in fixed positions in a length-3 list for each input string.

  ()⊥ convert to regular number (pence) from the following mixed-base:

   0 20 12 12 pence per shilling, 20 shilling per pound, no amount of pounds convert up

   v← store in v (for values)

+/ sum (lit. plus reduction)

v⊤ convert to mixed-base v

t← store in t (for total)

× signum of those values

2⊥ convert to regular number from base-2 (binary)

5⌊ find the minimum of five and that.

Gives 1 for pence-only, 2 for shilling only, 3 for shilling and pence, 4 for pounds only, and 5 for pounds and change.

everything else

∊'£'(⍕⊃t)'.','-'('s',⍨⍕1⊃t)[×1⊃t]'.','-d' '-'⎕R'-' ''⊢'d',⍨(d,⊂'^0')⎕R(f,'-')⍕2⊃t

2⊃t the third (pence) value of t

 string representation of that

⎕R() PCRE Replace the following strings:

  f,'-' the list f followed by a dash; ["n","w","r","-"]

() with:

  ⊂'^0' this string (regex: leading zero)

  d, appended to d; ["\.25","\.5","\.75","^0"]

'd',⍨ append a "d" to that

'-d' '-'⎕R'-' '' remove stray "d"s and dashes (caused by less than a whole pence)

…'.', prepend the following and a period to that:

  …] use

   1⊃t the second element of t (the shilling)

   × the signum of that (i.e. whether we have any shilling or not)

  '-'[ to index from a dash and the following:

  1⊃t the second element of t (the shilling)

   the string representation of that

  's',⍨ followed by an "s"

'£'()'.', prepend a pound sign, and the below, and a period to that:

  ⊃t the first element of t (the pounds)

   the string representation of that

ϵnlist (flatten)

whole pounds

'£',⍕⊃t

⊃t the first element of t (the pounds)

 the string representation of that

'£', prepend a pound symbol

between a shilling and a pound

' 0d' ' 0?'⎕R'/-' '/'⊢d⎕R f,∘'d'⍕1↓t

1↓t drop one element from t (leaving the shillings and the pence)

 the string representation (space separated) of that

,∘'d' append a "d" to that (lit. apply the append function with a curried right argument)

d⎕R f PCRE Replace d (["\.25","\.5","\.75"]) with f (["n","w","r"])

 yield that (separates the following from d)

' 0d' ' 0?'⎕R'/-' '/' PCRE Replace:
  no-pence with slash-dash
  leading pence-zero with slash

less than a shilling

('^0' '$',d)⎕R('' 'd',f)⍕2↓t

2↓t drop the first two elements of t (leaving just pence)

 the string representation of that

⎕R() PCRE Replace with the following:

  '' 'd',f the list f preceded by these two strings; ["","d","n","w","r"]

() where the following occur:

  '^0' '$',d the list d preceded by these two strings; ["^0","$","\.25","\.5","\.75"]
   the first is a leading zero
   the second marks the end of a string

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

Retina 0.8.2, 252 bytes

.+/.+
£.$&d
m`(?<!d)$
./d
m`^(?!£)
£./
s.
/
([¼½¾])?d
d0$1
T`¼½¾`123
[-\d]+
$*
+`.(1*)/(1*)d(1*)¶£(1*).(1*)/(1*)d
$4.$1$5/$2$6d$3
+`d1111
1d
+`/1{12}
1/
+`\.1{20}
1.
(.)(1*)
$1$.2
\b0
-
T`d`_¼½¾`.$
-?d(.)
$1d
£-.-/

£-.(.+)d
$1
(\..+)/
$1s.
-s
-
.-.-d

Try it online! Explanation:

.+/.+
£.$&d
m`(?<!d)$
./d
m`^(?!£)
£./
s.
/

Reformat the input into a fixed format £<l>.<s>/<d>d<f>.

([¼½¾])?d
d0$1
T`¼½¾`123
[-\d]+
$*

Convert to unary.

+`.(1*)/(1*)d(1*)¶£(1*).(1*)/(1*)d
$4.$1$5/$2$6d$3
+`d1111
1d
+`/1{12}
1/
+`\.1{20}
1.

Add up the values.

(.)(1*)
$1$.2
\b0
-
T`d`_¼½¾`.$
-?d(.)
$1d
£-.-/

£-.(.+)d
$1
(\..+)/
$1s.
-s
-
.-.-d

Convert back to decimal and format for display.

\$\endgroup\$
4
  • \$\begingroup\$ Could I bother you to explain the "format for display" part? \$\endgroup\$ – Adám Jan 18 '18 at 11:02
  • \$\begingroup\$ @Adám The fixed format has a character followed by an optional number of 1s for each part. This is matched by the 1st stage so that the 1s can be converted to decimal. The 2nd stage just converts 0s (but not 10 etc.) to -s. The 3rd stage converts the farthings to fractions and the 4th stage moves them before the d and deletes any - that was there. The 5th stage then handles amounts of less than 1s, while the 6th stage handles amounts between 1s and £1. The last two stages convert the / to s. (or -/ to -.) for amounts of £1 or more. \$\endgroup\$ – Neil Jan 18 '18 at 11:27
  • \$\begingroup\$ Shouldn't £1 ½d give £1.-.½d ? \$\endgroup\$ – Adám Jan 18 '18 at 11:49
  • \$\begingroup\$ @Adám Ugh, I'd overlooked whole pounds. Fixed. (Annoyingly, this means my code now ends in a blank line...) \$\endgroup\$ – Neil Jan 18 '18 at 13:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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