Convert to Suzhou numerals

Suzhou numerals (蘇州碼子; also 花碼) are Chinese decimal numerals:

0 〇
1 〡 一
2 〢 二
3 〣 三
4 〤
5 〥
6 〦
7 〧
8 〨
9 〩


They pretty much work like Arabic numerals, except that when there are consecutive digits belonging to the set {1, 2, 3}, the digits alternate between vertical stroke notation {〡,〢,〣} and horizontal stroke notation {一,二,三} to avoid ambiguity. The first digit of such a consecutive group is always written with vertical stroke notation.

The task is to convert a positive integer into Suzhou numerals.

Test cases

1          〡
11         〡一
25         〢〥
50         〥〇
99         〩〩
111        〡一〡
511        〥〡一
2018       〢〇〡〨
123321     〡二〣三〢一
1234321    〡二〣〤〣二〡
9876543210 〩〨〧〦〥〤〣二〡〇


Shortest code in bytes wins.

• I've been in Suzhou 3 times for longer period of time (quite a nice city) but didn't know about Suzhou numerals. You have my +1 – Thomas Weller Dec 13 '18 at 12:19
• @ThomasWeller For me it's the opposite: before writing this task I knew what the numerals were, but not that they were named "Suzhou numerals". In fact I've never heard them called this name (or any name at all). I've seen them in markets and on handwritten Chinese medicine prescriptions. – user58632 Dec 13 '18 at 13:51
• Can you take input in the form of a char array? – Embodiment of Ignorance Dec 13 '18 at 16:46
• @EmbodimentofIgnorance Yes. Well, enough people are taking string input anyway. – user58632 Dec 15 '18 at 7:26

Jelly, 35 bytes

9Ḷ;-26ż“/Ẉ8‘+⁽ȷc¤ṃ@ɓD_2ỊŒgÄFị"+⁽-FỌ


Try it online!

R, 138 bytes

I'll bet there's an easier way to do this. Use gsub to get the alternating numeric positions.

function(x,r=-48+~x)Reduce(paste0,ifelse(58<~gsub("[123]{2}","0a",x),"123"["一二三",r],'0-9'["〇〡-〩",r]))
"~"=utf8ToInt
"["=chartr


Try it online!

s=>s.replace(/./g,c=>(p=14>>c&!p)|c>3?eval("\\u302${c}"):'〇一二三'[c],p=0)  Try it online! Using 14>>c saves 3 bytes. Thanks to Arnauld. Retina, 46 bytes /[1-3]{2}|./_Td〇〡-〩^. T123一二三  Try it online! Link includes test cases. Explanation: /[1-3]{2}|./  Match either two digits 1-3 or any other digit. _Td〇〡-〩^.  Replace the first character of each match with its Suzhou. T123一二三  Replace any remaining digits with horizontal Suzhou. 51 bytes in Retina 0.8.2: M![1-3]{2}|. mTd〇〡-〩^. T¶123_一二三  Try it online! Link includes test cases. Explanation: M![1-3]{2}|.  Split the input into individual digits or pairs of digits if they are both 1-3. mTd〇〡-〩^.  Replace the first character of each line with its Suzhou. T¶123_一二三  Join the lines back together and replace any remaining digits with horizontal Suzhou. Perl 5-pl -Mutf8, 53 46 bytes -7 bytes thanks to Grimy s/[123]{2}|./OS&$&/ge;y//〇〡-〰一二三/c


Try it online!

Explanation

# Binary AND two consecutive digits 1-3 (ASCII 0x31-0x33)
# or any other single digit (ASCII 0x30-0x39) with string "OS"
# (ASCII 0x4F 0x53). This converts the first digit to 0x00-0x09
# and the second digit, if present, to 0x11-0x13.
s/[123]{2}|./OS&$&/ge; # Translate empty complemented searchlist (0x00-0x13) to # respective Unicode characters. y//〇〡-〰一二三/c  • -3 bytes with s/[123]\K[123]/$&^$;/ge;y/--</一二三〇〡-〩/ (TIO) – Grimmy Dec 13 '18 at 14:17 • 49: s/[123]{2}/$&^v0.28/ge;y/--</一二三〇〡-〩/ (TIO). 48: s/[123]{2}/$&^"\0\34"/ge;y/--</一二三〇〡-〩/ (requires using literal control characters instead of \0\34, idk how to do this on TIO) – Grimmy Dec 13 '18 at 14:28 • 46: s/[123]{2}|./OS&$&/ge;y//〇〡-〰一二三/c (TIO) – Grimmy Dec 13 '18 at 15:01

Java (JDK), 120 bytes

s->{for(int i=0,p=0,c;i<s.length;)s[i]+=(p>0&p<4&(c=s[i++]-48)>0&c<4)?"A䷏乚䷖".charAt(c+(p=0)):(p=c)<1?12247:12272;}


Try it online!

Credits

• c=s[i]-48;if(p>0&p<4&c>0&c<4) can be if(p>0&p<4&(c=s[i]-48)>0&c<4), and then you can also drop the brackets around the loop. Also, else{p=c;s[i]+=c<1?12247:12272;} can be else s[i]+=(p=c)<1?12247:12272; – Kevin Cruijssen Dec 13 '18 at 10:27
• @KevinCruijssen Thank you! I was still golfing this answer, but it helped me nonetheless ^^ Now I think I'm done golfing it. – Olivier Grégoire Dec 13 '18 at 10:50

JavaScript (ES6),  95 89  88 bytes

Saved 6 bytes thanks to @ShieruAsakoto

Takes input as a string.

s=>s.replace(i=/./g,c=>'三二一〇〡〢〣〤〥〦〧〨〩'[i=112>>i&c<4?3-c:+c+3])


Try it online!

• 89 bytes – Shieru Asakoto Dec 13 '18 at 2:34
• @ShieruAsakoto That's much better! Thanks a lot! – Arnauld Dec 13 '18 at 2:56

Python 3, 102 bytes

f=0
for i in input():f=i in'123'and 9-f;print(end='〇一二三〤〥〦〧〨〩〡〢〣'[int(i)+f])


Try it online!

mypetlion reminded me of a trivial golf. -4 bytes.

Clean, 181 165 bytes

All octal escapes can be replaced by the equivalent single-byte characters (and are counted as one byte each), but used for readability and because otherwise it breaks TIO and SE with invalid UTF-8.

import StdEnv
u=map\c={'\343','\200',c}
?s=((!!)["〇":s++u['\244\245\246\247\250']])o digitToInt
$[]=[]$[h:t]=[?(u['\241\242\243'])h:if(h-'1'<'\003')f$t] f[]=[] f[h:t]=[?["一","二","三"]h:$t]


Try it online!

An encoding-unaware compiler is both a blessing and a curse.

Red, 198 171 bytes

func[n][s: charset"〡〢〣"forall n[n/1: either n/1 >#"0"[to-char 12272 + n/1][#"〇"]]parse
n[any[[s change copy t s(pick"一二三"do(to-char t)- 12320)fail]| skip]]n]


Try it online!

Jelly, 38 bytes

9Rż“øƓ“œ%“øƈ’;-25+⁽-EỌœị@DżD<4«Ɗ‘×¥\ƊƊ


Try it online!

C, 131 bytes

f(char*n){char*s="〇〡〢〣〤〥〦〧〨〩一二三",i=0,f=0,c,d;do{c=n[i++]-48;d=n[i]-48;printf("%.3s",s+c*3+f);f=c*d&&(c|d)<4&&!f?27:0;}while(n[i]);}


Try it online!

Explanation: First of all - I'm using char for all variables to make it short.

Array s holds all needed Suzhou characters.

The rest is pretty much iterating over the provided number, which is expressed as a string.

When writing to the terminal, I'm using the input number value (so the character - 48 in ASCII), multiplied by 3, because all these characters are 3 bytes long in UTF-8. The 'string' being printed is always 3 bytes long - so one real character.

Variables c and d are just 'shortcuts' to current and next input character(number).

Variable f holds 0 or 27 - it says if the next 1/2/3 character should be shifted to alternative one - 27 is the offset between regular and alternative character in the array.

f=c*d&&(c|d)<4&&!f?27:0 - write 27 to f if c*d != 0 and if they are both < 4 and if f isn't 0, otherwise write 0.

Could be rewritten as:

if( c && d && c < 4 && d < 4 && f == 0)
f = 27
else
f = 0


Maybe there are some bytes to shave off, but I'm no longer able to find anything obvious.

• – Jonathan Frech Dec 14 '18 at 17:58

Perl 6-p, 85 61 bytes

-13 bytes thanks to Jo King

s:g[(1|2|3)<((1|2|3)]=chr $/+57;tr/0..</〇〡..〩一二三/  Try it online! Ruby-p, 71 bytes $_=gsub(/[1-3]\K[1-3]/){|x|(x.ord+9).chr}.tr"0-<","〇〡-〩一二三"


Try it online!

K (ngn/k), 67 bytes

{,/(0N 3#"〇一二三〤〥〦〧〨〩〡〢〣")x+9*<\x&x<4}@10\


Try it online!

10\ get list of decimal digits

{ }@ apply the following function

x&x<4 boolean (0/1) list of where the argument is less than 4 and non-zero

<\ scan with less-than. this turns runs of consecutive 1s into alternating 1s and 0s

x+9* multiply by 9 and add x

juxtaposition is indexing, so use this as indices in...

0N 3#"〇一二三〤〥〦〧〨〩〡〢〣" the given string, split into a list of 3-byte strings. k is not unicode aware, so it sees only bytes

,/ concatenate

Wolfram Language (Mathematica), 117 bytes

FromCharacterCode[12320+(IntegerDigits@#/. 0->-25//.MapIndexed[{a___,c=#2[[1]],c,b___}->{a,c,#,b}&,{0,140,9}+7648])]&


Try it online!

Note that on TIO this outputs the result in escaped form. In the normal Wolfram front end, it will look like this:

• Can you implement horizontal stroke notation for twos and threes? E.g. f[123] should return 〡二〣. – user58632 Dec 15 '18 at 7:35

C# (.NET Core), 107 bytes, 81 chars

n=>{var t="〇一二三〤〥〦〧〨〩〡〢〣";var b=0;return n.Select(k=>t[k+(b+=k>0&k<4?1:b)%2*9]);}


Try it online!

Saved 17 bytes thanks to @Jo King

C# (.NET Core), 124 bytes, 98 chars

n=>{var t="〇一二三〤〥〦〧〨〩〡〢〣";var b=0<1;return n.Select(k=>{b=k>0&k<4?!b:0<1;return b?t[k]:t[k+9];});}


Try it online!

Takes input in the form of a List, and returns an IEnumerable. I don't know if this input/output is ok, so just let me know if it isn't.

Explanation

How this works is that it transforms all the integers to their respective Suzhou numeral form, but only if variable b is true. b is inverted whenever we meet an integer that is one, two, or three, and set to true otherwise. If b is false, we turn the integer to one of the vertical numerals.

Japt, 55 bytes

s"〇〡〢〣〤〥〦〧〨〩"
ð"[〡〢〣]" óÈ¥YÉÃ®ë2,1Ãc
£VøY ?Xd"〡一〢二〣三":X


Try it online!

It's worth noting that TIO gives a different byte count than my preferred interpreter, but I see no reason not to trust the one that gives me a lower score.

Explanation:

    Step 1:
s"〇〡〢〣〤〥〦〧〨〩"        Convert the input number to a string using these characters for digits

Step 2:
ð                            Find all indexes which match this regex:
"[〡〢〣]"                    A 1, 2, or 3 character
ó    Ã            Split the list between:
È¥YÉ              Non-consecutive numbers
®    Ã     For each group of consecutive [1,2,3] characters:
ë2,1      Get every-other one starting with the second
c    Flatten

Step 3:
£                              For each character from step 1:
VøY                           Check if its index is in the list from step 2
?                         If it is:
Xd"〡一〢二〣三"            Replace it with the horizontal version
:X        Otherwise leave it as-is


R, 104 bytes

function(x,[=chartr)"a-jBCD"["〇〡-〩一二三",gsub("[bcd]\\K([bcd])","\\U\\1","0-9"["a-j",x],,T)]


Try it online!

An alternative approach in R. Makes use of some Perl-style Regex features (the last T param in substitution function stands for perl=TRUE).

First, we translate numerals to alphabetic characters a-j, then use Regex substitution to convert duplicate occurrences of bcd (formerly 123) to uppercase, and finally translate characters to Suzhou numerals with different handling of lowercase and uppercase letters.

Credit to J.Doe for the preparation of test cases, as these were taken from his answer.

C#, 153 bytes

n=>Regex.Replace(n+"",@"[4-90]|[1-3]{1,2}",x=>"〇〡〢〣〤〥〦〧〨〩"[x.Value[0]-'0']+""+(x.Value.Length>1?"一二三"[x.Value[1]-'0'-1]+"":""))


Try it online!

• This is 153 bytes, by the way, characters don't always mean bytes. Some characters are worth multiple bytes. – Embodiment of Ignorance Dec 15 '18 at 4:21
• Oh well, I edited my answer. Thanks for the information :) – zruF Dec 17 '18 at 8:02