Not Quite Roman Ternary

23

Given an integer n ≥ 0 , output it in a non-positional base-3 notation, using digits 139ABCDE… and a 1-character separator. Every digit is a consecutive power of 3 and the digits on the left side of the separator are negated, e.g. A931|B → 81−(1+3+9+27) → 41. A digit may appear only once.

Rigorously, let the value of a digit be:

  • its value if the digit is 1, 3 or 9
  • 27 if the digit is A
  • 3 times the value of the digit right before it for B..Z

Your output should satisfy sum(value of digits to the right of |) - sum(value of digits to the left of |) == input .

Examples

input     output
----------------
0         |
1         |1
7         3|91
730       |D1
9999      FEDC|GA9

You may use a different non-space character as a separator. You are also allowed to have no separator, in which case the largest digit starts the positive sequence. You don’t need to handle anything larger than 232−1 (PMIGDCBA9|RQNLH3).

You may write a full program or function, and input and output may be provided on any of the usual channels.

This is , so the shorter your answer the better!

FrownyFrog

Posted 2018-03-28T13:42:40.940

Reputation: 3 112

Related – Leaky Nun – 2018-03-28T13:45:24.747

Also related. – Martin Ender – 2018-03-28T13:45:47.637

Balanced ternary is a positional notation. – FrownyFrog – 2018-03-28T13:46:19.970

@FrownyFrog just de-position it and you get roman ternary – Leaky Nun – 2018-03-28T13:46:39.493

2(related doesn't mean duplicate, calm down) – Leaky Nun – 2018-03-28T13:47:02.927

8Am I the only one who has no clue what's being asked here? – Shaggy – 2018-03-28T13:47:25.707

3@Shaggy Express the input as a sum of powers of 3 and their negatives. Put the negatives left of a | and the positives to the right of it. – Martin Ender – 2018-03-28T13:48:06.450

Do the digits need to be sorted from largest to smallest? – Martin Ender – 2018-03-28T13:50:46.713

1Thanks, @MartinEnder; that explains the 0, 1 & 7 test cases but I still don't see where/how the letters come into play. – Shaggy – 2018-03-28T13:51:05.600

1Maybe some additional information on non-positional base-3 notation would be helpful here – musicman523 – 2018-03-28T13:51:37.540

1@Shaggy 1 is 1, 3 is 3, 9 is 9, A is 27, B is 81, C is 243, ... – Leaky Nun – 2018-03-28T13:51:42.487

@Shaggy They're just used for "digits" which can't be represented by a single decimal digit. A = 27, B = 81, etc. – Martin Ender – 2018-03-28T13:51:49.070

@MartinEnder no, the order is free. – FrownyFrog – 2018-03-28T13:53:57.747

Is the order strict (as in does it matter if we output CDEF|9AG, CDEF|GA9, FEDC|9AG, or FEDC|GA9)? If it is, all three current answers are invalid.. – Kevin Cruijssen – 2018-03-28T15:35:09.623

You are also allowed to have no separator, in which case the largest digit starts the positive sequence. What? So, if we don't use a separator, the output is to be computed differently so as for the largest digit to be the one which starts the positive sequence? – Erik the Outgolfer – 2018-03-28T15:35:41.650

@EriktheOutgolfer Because the order is not important... – user202729 – 2018-03-28T15:39:10.467

2@KevinCruijssen "no, the order is free." -- OP – user202729 – 2018-03-28T15:39:27.837

3@user202729 Ah, missed that comment. Thanks. That's what happens when rules are in the comments instead of edited into the challenge.. (FrownyFrog, could you add that rule to the challenge: either order on either side of the delimiter is fine?) – Kevin Cruijssen – 2018-03-28T15:51:34.933

@KevinCruijssen Isn't that inferrable from the rules? – user202729 – 2018-03-28T15:55:11.937

1@EriktheOutgolfer If you don't use a separator, the largest digit in the right sequence has to go first. The largest digit in the right sequence is guaranteed to be the largest overall, because the input is positive. – FrownyFrog – 2018-03-28T16:06:31.140

@FrownyFrog Well, it kind of makes the order matter then, but I guess it's not really avoidable, given no separator.. – Erik the Outgolfer – 2018-03-28T16:10:24.490

Is it also OK to have (positive digits, no separator, negative digits) and the largest digit ends the positive sequence? Is space between digits OK? – nwellnhof – 2018-03-28T16:47:43.840

@nwellnhof no, lets keep it vaguely Roman and entirely space-free – FrownyFrog – 2018-03-28T16:50:28.817

FYI instead of posting "X_____________________X" in the sandbox when deleting, just paste in the URL of your main post (I already fixed it). – Magic Octopus Urn – 2018-03-28T17:10:54.983

Clarification please: What is the largest value that we need to be able to convert? 0x7fffffff (signed 32 bit int), 0xffffffff (unsigned 32 bit int), or 0x100000000 (some 64 bit type, most likely)? The last option is what your wording requires currently, but I'm positive that you meant one of the other two possibilities. – cmaster - reinstate monica – 2018-03-29T19:47:15.493

@cmaster I wasn’t sure what the options even were, thanks for spelling them out. It’s the second one. – FrownyFrog – 2018-03-30T03:13:39.740

Order matter should go main for some comment folded – l4m2 – 2018-03-31T13:04:39.090

Why did you accept my Java answer? :S It's definitely not the shortest. ;) The 26-bytes Jelly answer is. – Kevin Cruijssen – 2018-05-15T10:04:49.343

@KevinCruijssen 9 edits worth of effort – FrownyFrog – 2018-05-15T10:09:35.297

@FrownyFrog Usually it's best practice to accept the shortest answer in code-golf challenges. I still really appreciate the gesture, though. Thanks. :) – Kevin Cruijssen – 2018-05-15T10:13:46.103

Answers

5

Java 10, 120 113 112 109 107 102 bytes

n->{var r="|";for(char c=49;n++>0;c=(char)(c+=c>64?1:c*4%22%9),n/=3)r=n%3<1?c+r:n%3>1?r+c:r;return r;}

-3 bytes by using part of the trick of @Arnauld's JavaScript (ES6) answer,
changing i=0 and i++<1?49:i<3?51:i<4?57:i+61 to i=4 and ++i>9?i+55:i>8?57:++i+43.
-6 bytes thanks to @Arnauld directly, by getting rid of i.

Order of output: Highest-to-lowest, |-delimiter, lowest-to-highest.

Explanation:

Try it online.

n->{              // Method with integer parameter and String return-type
  var r="|";      //  Result-String, starting at the delimiter "|"
  for(char c=49;  //  Character, starting at '1'
      n++>0       //  Loop as long as `n` is larger than 0
                  //  Increasing it by 1 with `n++` at the start of every iteration
      ;           //    After every iteration:
       c=(char)(  //     Change character `c` to:
          c+=c>64?//      If the current `c` is an uppercase letter:
              1   //       Simpy go to the next letter using `c+1`
             :    //      Else:
              c*4%22%9),
                  //       Change '1' to '3', '3' to '9', or '9' to 'A' 
       n/=3)      //     Integer-divide `n` by 3
     r=           //     Change the result to:
       n%3<1?     //      If `n` modulo-3 is 0:
        c+r       //       Prepend the character to the result
       :n%3>1?    //      Else-if `n` modulo-3 is 2:
        r+c       //       Append the character to the result
       :          //      Else:
        r;        //       Leave `r` unchanged
   return r;}     //  Return the result-String

Kevin Cruijssen

Posted 2018-03-28T13:42:40.940

Reputation: 67 575

1

I think this works: 103 bytes

– Arnauld – 2018-03-29T09:59:41.917

@Arnauld Nice one! And -1 more byte by putting r in the loop body. Thanks! – Kevin Cruijssen – 2018-03-29T10:06:12.430

@Arnauld Out of curiosity, what does the brute-forcers look like you've used for these last two magic numbers (when you still used i, and when you re-use c)? – Kevin Cruijssen – 2018-03-29T10:11:25.980

1

I've already thrown it away ... :-/ But here is the last one. (Very inefficient, but that's OK for such small values.)

– Arnauld – 2018-03-29T10:22:25.627

(Also, I really should test whether p=1 and don't include *1 in the code if it is -- even though it doesn't lead to a better formula in that case.) – Arnauld – 2018-03-29T10:30:51.980

@Arnauld Ah, that's more basic than I was expecting, thanks for sharing though. I guess it can quickly become more complex when more values are being asked, and where you end up needing more modulos, bitwise operations, base-conversions and/or standard arithmetic operations. Is there any general rule of thumb you can give as a tip to determine what is best to try next? For example, if your linked code wouldn't have given a result, would you add a third modulo? Just trying to understand it a bit more so I can hopefully do something myself with future challenges. :) (Thanks for your time, btw) – Kevin Cruijssen – 2018-03-29T10:36:48.163

I rely more on intuition than on rigorous maths to find a correct formula, so it's quite hard to write a tip. A multiply followed by one or several modulos works reasonably well most of the time, especially when combined with a lookup table. Here, we want to find the correct value directly, so it could indeed become more complex very quickly. Maybe someday I'll try again to work on a more clever and more generic brute-forcer that starts with quick and easy formulae and gradually increases the complexity. – Arnauld – 2018-03-29T10:56:07.820

5

Python 3, 103 99 91 bytes

4 bytes thanks to Lynn.

8 bytes thanks to ovs.

def f(n,s="|",b=0):c=('139'+chr(b+62)*b)[b];return n and f(-~n//3,[s,s+c,c+s][n%3],b+1)or s

Try it online!

Credits to xnor for the logic.

Leaky Nun

Posted 2018-03-28T13:42:40.940

Reputation: 45 011

5

JavaScript (ES6), 82 80 79 bytes

Outputs in lowercase, which should hopefully be fine.

f=(n,s=(k=4,'|'),c=++k>8?k.toString(36):++k-5)=>n?f(++n/3|0,[c+s,s,s+c][n%3]):s

Try it online!

Similar to Leaky "Ninja Master" Nun's answer and also based on xnor's answer.

Digit conversion

We start with k = 4. While k is less than 9, we increment it twice at each iteration and subtract 5. After that, we increment it only once and convert it to base-36.

  k  | ++k > 8       | k.toString(36) | ++k - 5  | result
-----+---------------+----------------+----------+--------
  4  | k=5  -> false |                | k=6 -> 1 | 1
  6  | k=7  -> false |                | k=8 -> 3 | 3
  8  | k=9  -> true  | '9'            |          | '9'
  9  | k=10 -> true  | 'a'            |          | 'a'
  10 | k=11 -> true  | 'b'            |          | 'b'
 ... | ...           | ...            | ...      | ...

Arnauld

Posted 2018-03-28T13:42:40.940

Reputation: 111 334

4

Jelly, 26 bytes

‘:3Ɗ⁹С‘%3ẹЀ0,2ị139D;ØA¤Y

Try it online!

Use a newline as the separator.

user202729

Posted 2018-03-28T13:42:40.940

Reputation: 14 620

26 bytes – Leaky Nun – 2018-03-29T00:53:46.680

25 bytes – Leaky Nun – 2018-03-29T00:56:51.043

2

Perl 6, 80 bytes

{map({|(1,3,9,|('A'..'Z'))[grep :k,*%3==$^v,($_,-+^*div 3...0)]},1,2).join.flip}

Try it online!

No separator. Based on xnor's answer.

nwellnhof

Posted 2018-03-28T13:42:40.940

Reputation: 10 037

2

Stax, 30 29 bytes

£└≤☻╘pÿ╖╡A[ô%æτ⌐}►ºôßHl4⌡π%^ 

Run and debug it

Port of my Stax answer in Balanced Ternary Converter.

Explanation

Uses the unpacked version to explain.

139$VA+cz{;3%+,^3/~;wY1|I@'|ay2|I@L
139$VA+c                               "139AB...Z", make a copy
        z                              Empty array to store the digits
          {         w                  Do the following until 0.
           ;3%+                           Append `b%3` to the digits
                                          Originally, `b` is the input
              ,^3/                        `b=(b+1)/3`
                  ~;                       Make a copy of `b` which is used as the condition for the loop

                     Y                 Save array of digits in `y` for later use
                      1|I              Find index of 1's
                         @             Find the characters in "139AB...Z" corresponding to those indices
                          '|           A bar
                            ay2|I@     Do the same for 2's
                                  L    Join the two strings and the bar and implicit output

Weijun Zhou

Posted 2018-03-28T13:42:40.940

Reputation: 3 396

1

Ruby, 87 84 82 bytes

Saved 2 bytes thanks to @benj2240.

->n,s=[?1,?3,?9,*?A..?Z],r=[""]*3{r[-m=n%3]+=s.shift
n=n/3+m/2
n>0?redo:r[1,2]*?|}

Try it online!

Reinstate Monica -- notmaynard

Posted 2018-03-28T13:42:40.940

Reputation: 1 053

I'd be lying if I said I'm completely following this code, but I do know you shave off 2 bytes with the redo trick: Try it online!

– benj2240 – 2018-03-28T22:26:38.567

1

C# .NET, 103 bytes

n=>{var r="|";for(var c='1';n++>0;c=(char)(c>64?c+1:c+c*4%22%9),n/=3)r=n%3<1?c+r:n%3>1?r+c:r;return r;}

Port of my Java 10 answer. If a direct port (except for n-> to n=>) would have been possible, I would have edited my Java answer with this polyglot. Unfortunately however, c+= on characters or having c=49 isn't possible in C#, hence this loose ported answer.

Try it online.

Kevin Cruijssen

Posted 2018-03-28T13:42:40.940

Reputation: 67 575

1

Perl 5 -p, 71 69 bytes

uses no separator. The negative and positive parts are in "roman order" (largest digit first)

#!/usr/bin/perl -p
$n=$_}{s/@{[$n++%3]}\K/]/,$n/=3,y/?-]/>-]/for($_=21)x31;y/>?@12/139/d

Try it online!

Ton Hospel

Posted 2018-03-28T13:42:40.940

Reputation: 14 114

1

J, 129 bytes

f=:3 :0
a=.'139',u:65+i.26
s=.'|'while.y>0 do.if.1=c=.3|y do.s=.s,{.a end.y=.<.y%3
if.c=2 do.s=.s,~{.a 
y=.1+y end.a=.}.a end.s
)

Try it online!

Too lengthy, especially for a J program...

Explanation:

f =: 3 : 0
   a =. '139',u:65+i.26   NB. a list '139ABC...Z'
   s =. '|'               NB. initialize the list for the result  
   while. y>0 do.         NB. while the number is greater than 0
      c =. 3|y            NB. find the remainder (the number modulo 3)
      y =. <.y%3          NB. divide the number by 3 
      if. c = 1 do.       NB. if the remainder equals 1
         s =. s,{.a       NB. Append the current power of 3 to the result
      end.
      if. c = 2 do.       NB. if the remainder equals 2 
         s =. s,~{.a      NB. prepends the result with the current power of 3
         y =. 1+y         NB. and increase the number with 1
      end.
      a =. }.a            NB. next power of 3 
   end.
   s                      NB. return the result  
)

Galen Ivanov

Posted 2018-03-28T13:42:40.940

Reputation: 13 815

1

C, int: 138 123 bytes, long: 152 131 bytes

I have created two versions of this, as the challenges' limit of a working max input of 0x100000000 seemed a bit odd. One version works with 32 bit integers (which fails the limit for obvious reasons), the other version works with 64 bits (which goes way beyond the given limit, at the cost of 14 8 extra bytes).

32 bit version:

char b[22],*r=b;f(v,l)char*l;{v%3>1?*r++=*l,v++:0;v&&f(v/3,l+1);v%3?*r++=*l:0;}g(v){f(v,"139ABCDEFGHIJKLMNOPQR");*r=0;r=b;}

64 bit version:

char b[22],*r=b;f(long v,char*l){v%3>1?*r++=*l,v++:0;v&&f(v/3,l+1);v%3?*r++=*l:0;}g(long v){f(v,"139ABCDEFGHIJKLMNOPQR");*r=0;r=b;}

This is identical except that it declares the integer variable to be long (which is 64 bits on linux).

The ungolfed long version:

char buffer[22],*result=buffer;
f(long value,char*letter){
    if(value%3>1){
        *result++=*letter,value++;
    }
    if(value){
        f(value/3,letter+1);
    }
    if(value%3){
        *result++=*letter;
    }
}
g(long value){
    f(value,"139ABCDEFGHIJKLMNOPQR");
    *result=0;
    result=buffer;
}

As you can see, this works by recursive decent: If the remainder is 1, the respective character is appended to the output string after the recursive call. If the remainder is 2, the output is performed before the recursing. In this case, I also increment the value by one to handle the negative digit correctly. This has the added benefit of changing the remainder to zero, allowing me to use value%3 as the condition for the post-recursion if.

The result of the conversion is placed into the global buffer. The g() wrapper has the job of zero terminating the resulting string correctly, and to reset the result pointer to its start (which is also how g() "returns" the result).

Test the long version with this code:

#include <stdio.h>

char b[22],*r=b;f(long v,char*l){v%3>1?*r++=*l,v++:0;v&&f(v/3,l+1);v%3?*r++=*l:0;}g(long v){f(v,"139ABCDEFGHIJKLMNOPQR");*r=0;r=b;}

void printConversion(long value) {
    g(value);
    printf("%ld: %s\n", value, r);
}

int main() {
    for(long i = 0; i <= 40; i++) {
        printConversion(i);
    }
    printConversion(0x7fffffff);
    printConversion(0xffffffffu);
    printConversion(0x100000000);
}

Possible further, but destructive golfing:

  • -4 bytes: make the function a one-shot by removing the pointer reset in g().

  • -5 bytes: force the caller to perform the string termination, returning the string without termination in buffer, and the end of the string in result.

cmaster - reinstate monica

Posted 2018-03-28T13:42:40.940

Reputation: 381

1

Charcoal, 36 bytes

NθF³⊞υ⟦⟧F⁺139α«⊞§υθι≔÷⊕θ³θ»F²«×|ι↑⊟υ

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

Nθ

Input the value.

F³⊞υ⟦⟧

Push three empty lists to the predefined empty list.

F⁺139α«

Loop through the characters 139 and the uppercase alphabet.

⊞§υθι

Cyclically index the list of lists with the value and push the current character to it.

≔÷⊕θ³θ»

Divide the value by 3 but round it by adding 1 first.

F²«×|ι

Loop twice. The second time, print a |.

↑⊟υ

Each loop we pop the last entry from the list; the first time this gives us the entries that had a remainder of 2 (which corresponds to a balanced ternary digit of -1), while the second time this gives us the entries corresponding to a balanced ternary digit of 1. The resulting array would normally print vertically, but rotating the print direction upwards cancels that out.

Neil

Posted 2018-03-28T13:42:40.940

Reputation: 95 035

1

J, 69 64 58 bytes

('931',~u:90-i.26){~0(>,&I.<)(29$3)((+1&|.-3&*)]-*)^:_@#:]

Try it online!

FrownyFrog

Posted 2018-03-28T13:42:40.940

Reputation: 3 112

0

Perl 5, 92 89 bytes

Inspired by the java and python answers.

sub n{($n,$r,$c,@a)=(@_,'|',1,3,9,'A'..'Z');$n?n(int++$n/3,($c.$r,$r,$r.$c)[$n%3],@a):$r}

Try it online!

With some white space:

sub n {
  ($n, $r, $c, @_) = (@_, "|", 1, 3, 9, 'A' .. 'Z');
  $n ? n( int++$n/3, ($c.$r, $r, $r.$c)[$n%3], @_)
     : $r
}

Kjetil S.

Posted 2018-03-28T13:42:40.940

Reputation: 1 049

0

PHP, 73 bytes

for(;0|$n=&$argn;$n/=3)${$n++%3}.=_139[++$i]?:chr(61+$i);echo${2},_,${1};

port of xnor´s answer, 53 bytes

for(;0|$n=&$argn;$n/=3)$s="0+-"[$n++%3].$s;echo$s??0;

Run as pipe with -nr or try them online.

Titus

Posted 2018-03-28T13:42:40.940

Reputation: 13 814