23
\$\begingroup\$

Task

Given an integer \$n\in[0,10^{12})\$ in any convenient format, return the number of strokes needed to write that character in simplified Chinese.

Background

Chinese numerals are expressed in base 10 with a system of digits and places, with an important distinction that digits are in groups of four, rather than three.

The individual characters used to write Chinese can be described at the lowest level as a collection of strokes, laid out in a certain order and manner. The number of strokes required to write a character is that character's stroke count.

The (simplified) characters used to write numbers in Chinese are:

num   char  strokes
0     零*    13
1     一     1
2     二**   2
3     三     3
4     四     5
5     五     4
6     六     4
7     七     2
8     八     2
9     九     2
10    十     2
100   百     6
1000  千     3
10^4  万     3
10^8  亿     3
 * 0 can also be written 〇, but we won't use that here.  
** 两 is largely interchangeable with 二, apart from never
   appearing before 十. We won't consider it here for simplicity,
   but 两 is very common in actual usage.

For example, 9 8765 4321 is 九亿八千七百六十五万四千三百二十一: nine hundred-million (九 亿), eight thousand seven hundred sixty-five ten-thousand (八千七百六十五 万), four thousand three hundred twenty-one (四千三百二十一). In all, 53 strokes are needed to write this out.

There are additionally some special rules involving the digits 0 and 1. These can vary slightly between dialects, but we'll choose these:

  • When there are non-trailing 0s in a 4-digit group, they are combined into a single . No place marker is used.
    (This is because e.g. 一百二 is a common way to say 120. We won't consider that form.)

    • 1020 is 一千零二十.
    • 6 0708 is 六万零七百零八.
    • 3 0000 4005 is 三亿四千零五.
    • 0 is 零.
  • If the number would begin with 一十, the is omitted.

    • Powers of 10 are 一, 十, 一百, 一千, 一万, 十万, 一百万, etc.
    • 111 is 一百一十一.

Test Cases

           n  strokes  chinese
           0  13       零
          10  2        十
         236  17       二百三十六
        7041  26       七千零四十一
       50010  23       五万零一十
      100000  5        十万
      860483  42       八十六万零四百八十三
     4941507  52       四百九十四万一千五百零七
    51001924  38       五千一百万一千九百二十四
   105064519  70       一亿零五百零六万四千五百一十九
   300004005  31       三亿四千零五
   987654321  53       九亿八千七百六十五万四千三百二十一
  1240601851  56       十二亿四千零六十万一千八百五十一
608726402463  79       六千零八十七亿二千六百四十万二千四百六十三

@user202729 provided a script in the sandbox to help with reading the Chinese numbers: Try it online!

\$\endgroup\$
4
  • 7
    \$\begingroup\$ Why is 10^8 called hundred-thousand in English? 10^8 is a hundred million \$\endgroup\$
    – user253751
    Aug 25, 2020 at 9:42
  • \$\begingroup\$ @user253751 typo, fixed. \$\endgroup\$
    – att
    Aug 25, 2020 at 18:15
  • \$\begingroup\$ The 一十 rule applies to just the start of the whole number, right, not each group? \$\endgroup\$
    – xnor
    Aug 25, 2020 at 22:56
  • \$\begingroup\$ @xnor that's right. \$\endgroup\$
    – att
    Aug 25, 2020 at 23:07

4 Answers 4

10
\$\begingroup\$

Python 2, 163 162 155 151 bytes

-1 thanks to Arnauld, -7 thanks to ovs

Hexdump:

00000000: efbb bf65 7865 6322 789c 1dcb c10e c220  ...exec"x......
00000010: 0cc6 f157 e1c2 d2c2 88b4 6359 34c2 bb20  ...W......cY4..
00000020: b893 7689 274f 3ebb 65a7 36bf 7cff 3dbf  ..v.'O>.e.6.|.=.
00000030: eafb d1ab 91b9 e578 1353 a543 fc32 734a  .......x.S.C.2sJ
00000040: ebc2 d44b 114b d1a5 8956 f430 7e5d 9866  ...K.K...V.0~].f
00000050: 93bb 5a62 bba1 5fdc e067 1ace 1b97 d226  ..Zb.._..g.....&
00000060: c240 ca40 67ec 75ad 2de8 b92f 7852 440c  .@[email protected]../xRD.
00000070: 2039 0f62 f43b c885 e21c 7e5c 728f 8fd1   9.b.;....~\r...
00000080: 343a d7fe d154 254b 222e 6465 636f 6465  4:...T%K".decode
00000090: 2827 7a69 7027 29                        ('zip')

Unpacked:

f=lambda n,c=0:n and(0x222445321d>>n%10*4&15)+(n%10and c%4*9%12%7)+3*(n%1e4and 272>>c&1)-13*((1>n%10+c%4)+((c%4<3)>n%100))-(n==c%4<2)+f(n/10,-~c)or 13*0**c

Try it online!

Commented version

f=lambda n,c=0:n and        # If n is not zero, sum the following:
                            # The strokes of the current digit
  (0x222445321d>>n%10*4&15) #   [13,1,2,3,5,4,4,2,2,2][n%10] 
                            # The 10/100/1000 marker stroke count, if 
                            # the currrent digit is zero
  + (n%10and c%4*9%12%7) #   [0, 2, 6, 3][n%10 and c%4]
                            # The 10^4 / 10^8 marker stroke count
  + 3*(n%1e4and 272>>c&1)   #   3*(n%10000 and c in [4,8]) 
  - 13*(                    # Subtract 13 because we overcounted the zeros if
                            #   It's a trailing zero in a group of four
        (1>n%10+c%4)        #     (n%10 == 0 and c%4 == 0)
                            #   and/or if it's part of a group of zeros
        +((c%4<3)>n%100)    #     (n%100 == 0 and c%4 != 3)
    )                       # Subtract 1 if we lead a group of four with 10
  - (n==c%4<2)              #   (n==1 and c%4==1)
  + f(n/10,-~c)             # Recurse on next digit
                            # Else if n=0, return 13 if we are at the first
or 13*0**c                  # digit, else 0

We also use the exec"...".decode('zip') to compress our code to save a few bytes.

\$\endgroup\$
7
  • \$\begingroup\$ (c%4!=3) can be turned into (c%4<3). Besides, it seems that the parentheses can be omitted, but I don't know the precedence of Python operators well enough to be 100% sure. \$\endgroup\$
    – Arnauld
    Aug 25, 2020 at 13:09
  • \$\begingroup\$ Thx @Arnauld! Unfortunately I don't think I can remove the brackets, since >> binds tighter than <. In fact, what's really annoying about Python is that + binds tighter than >>, else I could remove a lot of the brackets. \$\endgroup\$
    – Sisyphus
    Aug 26, 2020 at 0:57
  • 1
    \$\begingroup\$ 158 bytes by converting some bit shifts to comparisons (changes in the header). \$\endgroup\$
    – ovs
    Aug 26, 2020 at 9:02
  • \$\begingroup\$ Nice catch @ovs, updated \$\endgroup\$
    – Sisyphus
    Aug 26, 2020 at 9:25
  • 1
    \$\begingroup\$ And c%4*9%12%7 is 2 bytes shorter than 1936>>c%4*3&7: tio.run/… \$\endgroup\$
    – ovs
    Aug 26, 2020 at 9:48
8
\$\begingroup\$

JavaScript (ES6),  148 145  139 bytes

f=(n,i=0,z=n/1e4|0)=>n?f(z,3)-!(n/10^1)+(g=d=>~~d?n%1e4&&(q=n/d%10,x=296/~~q%5^20>>q&1,x?z=x+=3064/d&7:z*q&&(z=0,13))+g(d/10):i)(1e3):!i*13

Try it online!

Black magic

  • The number of strokes in the Chinese character for a single decimal digit \$1\le q\le 9\$ can be obtained with:

    296 / q % 5 ^ 20 >> q & 1
    

    Try it online!

    This formula returns \$0\$ for \$q=0\$, which is actually what we want because zeros have to be processed separately anyway.

  • The number of strokes in the Chinese character for a multiplier \$d\in\{1,10,100,1000\}\$ can be obtained with:

    3064 / d & 7
    

    Try it online!

Commented

f = (                      // f is a recursive function taking:
  n,                       //   n = input
  i = 0,                   //   i = number of strokes for either 10^4 or 10^8
                           //       (0 for the first iteration, then 3)
  z = n / 1e4 | 0          //   z = next 4-digit block, also used as a flag to
) =>                       //       tell whether zeros must be inserted
n ?                        // if n is not equal to 0:
  f(z, 3)                  //   do a recursive call with the next 4-digit block
  - !(n / 10 ^ 1)          //   subtract 1 if n is the range [10...19] to account
  +                        //   for a leading '10' where the '1' must be omitted
  ( g = d =>               //   g is a recursive function taking a divisor d
    ~~d ?                  //     if d greater than or equal to 1:
      n % 1e4 &&           //       abort if the entire block is 0
      (                    //       otherwise:
        q = n / d % 10,    //         q = (n / d) mod 10
        x = 296 / ~~q % 5  //         compute the number of strokes x for this
            ^ 20 >> q & 1, //         decimal digit (i.e. the integer part of q)
        x ?                //         if it's not a zero:
          z =              //           set z to a non-zero value
            x +=           //           add to x ...
              3064 / d & 7 //           ... the number of strokes for d
        :                  //         else:
          z * q &&         //           if both z and q are non-zero:
            (z = 0, 13)    //             add 13 strokes and set z to 0 so that
      )                    //             other contiguous zeros are ignored
      + g(d / 10)          //       add the result of a recursive call with d / 10
    :                      //     else:
      i                    //       add i to account for either 10^4 or 10^8
  )(1e3)                   //   initial call to g with d = 1000
:                          // else:
  !i * 13                  //   stop the recursion; add 13 if the input was zero
\$\endgroup\$
7
\$\begingroup\$

05AB1E, 60 59 bytes

Rv•мΛ~?•ÁyèyĀiŽAΘÁNè]IR4ô¤¤sg*2Q(sεÔ0Û}©J0¢I_+13*ŽE₃Á®¦ĀOèO

Try it online or verify all test cases.

Explanation:

Here a run-down of what each part of the code accomplishes, which we'll sum with O at the very end to get the result:

  • Convert each digit to the amount of strokes used, except for edge case 0: Rv•мΛ~?•Áyè]
  • Get the strokes of 10, 100, 1000 per part of four digits if the current digit is not a 0: RvyĀiŽAΘÁNè]
  • Subtract 1 in case there is a leading 一十 conversion, by separating the number into parts of four digits, and checking if the first part starts with a 1 and contains just two digits: IR4ô¤¤sg*2Q(
  • Add 13 for each 0, excluding those that are adjacent to other 0s, or trailing in every part of four digits: IR4ôεÔ0Û}©J0¢13*
  • Take care of edge case \$n=0\$: I_+13*
  • Get the strokes of the \$10^4\$ and \$10^8\$, excluding parts that consist solely of 0s: ŽE₃Á®¦ĀOè
R                # Reverse the (implicit) input-integer
 v               # Loop over each of its digits `y`:
  •мΛ~?•         #  Push compressed integer 1235442220
        Á        #  Rotate it once to the right: 0123544222
         yè      #  Index the current digit `y` into 0123544222
  yĀi            #  If `y` is NOT 0:
     ŽAΘ         #   Push compressed integer 2630
        Á        #   Rotate it once to the right: 0263
         Nè      #   Index the loop-index `N` into 0263 (modulair 0-based)
 ]               # Close both the if-statement and loop
IR               # Push the reversed input-integer again
  4ô             # Split it into parts of size 4, where the last part might be smaller
    ¤            # Push the last part (without popping the list itself)
     ¤           # Push the last digit (without popping the number itself)
      s          # Swap so the number is at the top of the stack again
       g         # Pop and push its length
        *        # Multiply it to the digit
         2Q      # Check if length * digit is equal to 2 (1 if truthy; 0 if falsey)
           (     # Negate that
    s            # Swap so the list of parts of 4 is at the top of the stack again
     ε           # Map each part to:
      Ô          #  Connected uniquify each digit (i.e. 0030 becomes 030)
       0Û        #  Remove any leading 0s
     }©          # After the map: store this list in variable `®` (without popping)
       J         # Join it together to a single string
        0¢       # Count the amount of 0s left in the string
          I_     # Check if the input-integer is 0 (1 if 0; 0 otherwise)
            +    # Add that to the count of 0s
             13* # Multiply it by 13
ŽE₃              # Push compressed integer 3660
   Á             # Rotate it once to the right: 0366
    ®            # Push the list from variable `®`
     ¦           # Remove the first item
      Ā          # Truthify each part ("" becomes 0; everything else becomes 1)
       O         # Sum this list
        è        # Index it into the 0366
O                # And finally sum all values on the stack
                 # (after which this sum is output implicitly as result)

See this 05AB1E tip of mine (section How to compress large integers?) to understand why •мΛ~?• is 1235442220; ŽAΘ is 2630; and ŽE₃ is 3660.

\$\endgroup\$
1
  • \$\begingroup\$ This is over the top. \$\endgroup\$ Aug 25, 2020 at 17:04
4
\$\begingroup\$

Setanta, 243 236 235 234 bytes

gniomh(n){gniomh S(n,c){gniomh Q(t){s:=0ma t s=1toradh s}toradh(n&[13,1,2,3,5,4,4,2,2,2][n%10]+[0,2,6,3][n%10&c%4]+3*Q((c==4|c==8)&n%10000)-13*(Q(n%10==0&c%4==0)+Q(n%100==0&c%4!=3))-Q(n==1&c%4==1)+S(n//10,c+1))|13*Q(!c)}toradh S(n,0)}

Try it here!

Notes:

  • This is a port of the Python 2 solution.
  • The correct keywords for 'function' and 'if' are gníomh and , respectively, but Setanta allows spelling them without the accents so I did that to save some bytes.
  • I had to define the Q function above because Setanta doesn't coerce booleans to integers. As far as I know, there's nothing equivalent to ternary expressions in this language.
  • There are no bitwise operations as far as I know, so I had to stick to array lookups.
\$\endgroup\$

Your Answer

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

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