17
\$\begingroup\$

Your task is to write 2 functions/programs. They may share code.

The first function must, given a string and a character, output a new string that does not contain that character.

The second function must, given the string from the previous function and the excluded character, reconstruct the original string.

All input strings will be ASCII printable, all output strings must be ASCII printable. You may choose to include or exclude whitespace, if you exclude whitespace it must be excluded from both the input and the output.

Examples

One could try to use replace the character with an escape sequence. For example \#bcda with excluded character becomes \\#bcd\# then you'd know to replace \# with whatever character is excluded. However, the excluded character might itself be # or \ so if one uses this method they'd need to be able to dynamically switch to a different escape sequence in that case.

There is no length limit for the censored string so you could try to encode the entire string in unary, but be careful that the unary char you choose may not be the excluded char.

This is code golf, shortest answer in each language wins. Brownie points for interesting solutions even if they are not the shortest though.

\$\endgroup\$
5
  • 3
    \$\begingroup\$ "You may choose to include or exclude whitespace." It might be worth clarifying that the same choice needs to be made for the input and output--feels like it should be obvious that it would subvert the challenge otherwise, but a solution that looks like it unconditionally uses spaces as delimiters seems to have inspired a solution that actually unconditionally uses spaces as delimiters. \$\endgroup\$ Nov 6 at 19:24
  • \$\begingroup\$ Is it possible for input strings to be empty? \$\endgroup\$
    – lyxal
    Nov 6 at 23:34
  • \$\begingroup\$ Are we allowed to use different representations of a string for input and output? For example, my Python answer currently does bytes -> str -> list of ints. \$\endgroup\$ Nov 6 at 23:42
  • \$\begingroup\$ It could be interesting challenge if the second function does not receive the character as input. \$\endgroup\$
    – tsh
    Nov 7 at 0:50
  • \$\begingroup\$ @tsh that will be part 2 \$\endgroup\$
    – mousetail
    Nov 7 at 6:57

12 Answers 12

11
\$\begingroup\$

JavaScript (ES6), 59 bytes

Encoder (30 bytes)

Expects (string)(char).

s=>c=>btoa(s).split(c).join`#`

Try it online!

Decoder (29 bytes)

Expects (encoded_string)(char).

s=>c=>atob(s.replace(/#/g,c))

Try it online!

Explanation

The encoder turns the input string s into a base-64 string, which may only contain [a-z], [A-Z], [0-9], +, / and =. Then it replaces all occurrences of the forbidden character c with #. If c is not a base-64 character, this 2nd step has no effect at all. (In particular, note that the base-64 string can't possibly contain any #.)

The decoder does the exact opposite: it replaces all occurrences of # with the forbidden character and decodes this as a base-64 string.

\$\endgroup\$
2
  • \$\begingroup\$ What if the char is #? \$\endgroup\$
    – mousetail
    Nov 6 at 10:31
  • 6
    \$\begingroup\$ @mousetail Then it won't appear in the base-64 string and nothing will be replaced. \$\endgroup\$
    – Arnauld
    Nov 6 at 10:47
4
\$\begingroup\$

Ruby, 59 bytes

f=->s,c{"#{s.bytes}".tr c,?.}
g=->s,c{(eval s.tr ?.,c).pack"U*"}

Try it online!

TIO says 64 byes, because 2 function headers and a newline are included.

Inspired by Arnauld's JS answer.

Encode

First convert the string to a sequence of 8-bit values, then convert it to a string again, and finally replace the character to be excluded with a dot. The first step is equivalent to unpack using UTF-8 byte values, only slightly shorter.

Decode

Reverse order: first replace the character that was excluded, then convert it to an array of 8-bit values, and finally pack it to a string. This is equivalent to mapping the &:chr method on the list, and then joining the characters, only slightly shorter.

\$\endgroup\$
1
  • \$\begingroup\$ eval(s.tr ?.,c).pack"U*" gives -1 \$\endgroup\$
    – Value Ink
    Nov 15 at 0:38
4
\$\begingroup\$

Jelly, 4 + 5 = 9 bytes

(4 + 4 = 8 bytes if we do not need to handle the empty string*, remove .)

Uses the same approach as others, port of bsoelch's Python answer.

Encoder

A dyadic Link that accepts the plaintext on the left and the character on the right and yields the ciphertext.

OṾṣK - Link: plaintext, P; character, C  e.g. P="20 cats"; C='0'
O    - cast P to ordinals                      [50,48,32,99,97,116,115]
 Ṿ   - unevaluate                               "50,48,32,99,97,116,115"
  ṣ  - split at occurrences of C             ["5",",48,32,99,97,116,115"]
   K - join with spaces                         "5 ,48,32,99,97,116,115"

                     (note that if C=' ' we get "50,48,32,99,97,116,115")

Decoder

A dyadic Link that accepts the ciphertext on the left and the character on the right and yields the plaintext.

ḲjVỌ€ - Link: cyphertext X; character C  e.g. X="5 ,48,32,99,97,116,115", C='0'
Ḳ     - split X at spaces                    ["5",",48,32,99,97,116,115"]
 j    - join with C                             "50,48,32,99,97,116,115"
  V   - evaluate as Jelly code                  [50,48,32,99,97,116,115]
   Ọ€ - cast each to a character                "20 cats"

* An empty ciphertext evaluates (V) to the integer zero, so without the cast () will convert that to a NULL byte terminated empty string (in all other cases it will vectorise automatically but will implicitly cast this edge-case zero to an empty range first).

\$\endgroup\$
6
  • \$\begingroup\$ This seems to return an inconsistent output format. Outputting as a list of character codes is fine but [[], [], [], [98]] does not seem like a reasonable way to represent a list of character literals to me \$\endgroup\$
    – mousetail
    Nov 6 at 13:23
  • \$\begingroup\$ Or if you intend it to be read as a string instead then it fails if the excluded character is ] or [ or , \$\endgroup\$
    – mousetail
    Nov 6 at 13:26
  • \$\begingroup\$ Those TIO links don't work for me \$\endgroup\$
    – mousetail
    Nov 6 at 13:32
  • \$\begingroup\$ I don't think your output can be considered a reasonable representation of a string, since you explicitly use out of band info to encode information. A flat array of integers would be fine but [[65, 32, 108, 105, 115, 116, 32, 105, 115, 32, 102, 105, 110, 101, 58, 32], []] seems cheaty to me. \$\endgroup\$
    – mousetail
    Nov 6 at 13:35
  • 1
    \$\begingroup\$ @mousetail total rewrite. \$\endgroup\$ Nov 6 at 21:29
3
\$\begingroup\$

05AB1E, 7 6 + 5 = 11 bytes

Encoder:

-1 thanks to @KevinCruijssen

≠sÇ₁β×

Attempt This Online!

Decoder:

g₁вçJ

Attempt This Online!

J could be removed for -1 to output a list of characters instead of a string.

Uses unary, with 0 if the character is 1 and 1 otherwise.

\$\endgroup\$
1
  • \$\begingroup\$ иJ can be × for -1 byte. Although outputting as a list of characters when a string is being asked is allowed by default, so the J in the second program could be removed. \$\endgroup\$ Nov 6 at 11:49
3
\$\begingroup\$

Python, 86 bytes

inspired by Arnauld's JavaScript solution

-25 bytes, thanks to corvus_192

Encoder:

lambda s,c:str([*map(ord,s)]).replace(c,"#")

Decoder:

lambda s,c:map(chr,eval(s.replace("#",c)))

Attempt This Online!

encodes string as an array sequence of character-codes


Python, 182 bytes

def q(S,N,K,n,d):
 k=sum((ord(c)&K)*N**i for i,c in enumerate(S));s=[]
 while k:s+=[chr(k%n+d)];k//=n
 return s
e=lambda s,c:q(s,128,-1,2,48+2*(c in"01"))
d=lambda s,c:q(s,2,1,128,0)

Attempt This Online!

The encoder (e(s,c)) interprets the string as a base 128 number and converts it to binary (using 2 and 3 if the excluded character is 0 or 1).

The decoder (d(s,c)) converts a binary number to base 128

\$\endgroup\$
4
  • \$\begingroup\$ Seems to have the same issue as Arnould's solution that if the excluded character is # it will fail \$\endgroup\$
    – mousetail
    Nov 6 at 10:32
  • \$\begingroup\$ @mousetail as far as I can tell it does work, do you have a test-case where it fails? \$\endgroup\$
    – bsoelch
    Nov 6 at 10:46
  • \$\begingroup\$ My bad, I understand how this works now \$\endgroup\$
    – mousetail
    Nov 6 at 10:53
  • \$\begingroup\$ -25 bytes: e=lambda s,c:str([*map(ord,s)]).replace(c,"#"); d=lambda s,c:map(chr,eval(s.replace("#",c))) \$\endgroup\$
    – corvus_192
    Nov 6 at 16:38
2
\$\begingroup\$

Vyxal Ss, 5 + 4 = 9 bytes

C?¬S*

Try it Online!

Input list of characters and then normal input. Outputs a string

⌈@ꜝC

Try it Online!

Input the string from the encoder.

\$\endgroup\$
7
  • \$\begingroup\$ @emanresuA it can handle 0s. \$\endgroup\$
    – lyxal
    Nov 6 at 21:37
  • \$\begingroup\$ @emanresuA you could have at the very least mentioned what it was that isn't printable ascii. \$\endgroup\$
    – lyxal
    Nov 6 at 21:42
  • \$\begingroup\$ Does this work for the empty string? (I tried inputting "⟨ ⟩" to the decoder which didn't work.) Also, the output seems to have a trailing space one must remove, is that the web app doing that? \$\endgroup\$ Nov 6 at 23:05
  • \$\begingroup\$ @emanresuA I realise that now after I fixed my solution (obviously). I more meant at the time instead of being cryptically vague. \$\endgroup\$
    – lyxal
    Nov 6 at 23:30
  • 1
    \$\begingroup\$ @JonathanAllan the empty string would be input as [] \$\endgroup\$
    – lyxal
    Nov 6 at 23:36
2
\$\begingroup\$

Jelly, 9 + 4 bytes

Encoder

OḅȷịØṖxɗ/

Decoder

LbȷỌ

Try it online!

Both are monadic links. The encoder takes a pair of Jelly strings (character followed by string to encode). The decoder takes the encoded string as the first argument and ignores the second argument (but doesn’t mind if it is the excluded character). This takes inspiration from @CommandMaster’s 05AB1E answer so be sure to upvote that one too!

Now fixed (properly!) to handle all printable ASCII and return strings. Thanks to @JonathanAllan for his patience in highlighting problems with previous versions.

Explanations

Encoder

O         | Codepoints of strings
 ḅȷ       | Convert both from base 1000
       ɗ/ | Reduce using the following as a dyad:
   ịØṖ    | - Index (the codepoint of the single character) into the list of printable ASCII (will always be a different character)
      x   | - Repeat this as many times as the base-1000 decoded string

Decoder

L    | Length
 bȷ  | Convert to base 1000
   Ọ | Convert from code points to ASCII
\$\endgroup\$
7
  • \$\begingroup\$ Very clever I/O hack! \$\endgroup\$ Nov 6 at 19:17
  • 3
    \$\begingroup\$ ...Although it seems to break in multiple ways if the forbidden character is a space. \$\endgroup\$ Nov 6 at 19:22
  • 1
    \$\begingroup\$ @UnrelatedString thanks, and good point. I’ve modified to say that I’m excluding printable whitespace, as permitted in the question \$\endgroup\$ Nov 6 at 19:34
  • 1
    \$\begingroup\$ Seems your output still contains spaces, I clarified that excluding white space must apply to both the input and the output. \$\endgroup\$
    – mousetail
    Nov 6 at 19:37
  • 1
    \$\begingroup\$ Thanks for your patience @JonathanAllan. Hopefully now actually fixed. \$\endgroup\$ Nov 7 at 8:28
1
\$\begingroup\$

Charcoal, 9 + 9 = 18 bytes

Encoder, 9 bytes:

⍘⍘S⁺¶γ⁻γS

Try it online! Link is to verbose version of code. Explanation: "Decodes" the input as base 97 and "encodes" it as base 95. (Base 97 is needed to prevent the input from having leading zeros.)

Decoder, 9 bytes:

⍘⍘S⁻γS⁺¶γ

Try it online! Link is to verbose version of code. Explanation: "Decodes" the input as base 95 and "encodes" it as base 97.

\$\endgroup\$
1
\$\begingroup\$

Python, 68 bytes

e=lambda s,c:str([*s]).replace(c,'#')
d=lambda s,c:eval(s.replace('#',c))

Attempt This Online!

Inspired by Arnauld's JS answer. The encoder converts to a stringified list of code points, replacing the excluded character with a "#". The decoder replaces "#" with the excluded character and returns a list of code points.

Python, 73 bytes

e=lambda s,c:s.hex().replace(c,'#')
d=lambda s,c:b''.fromhex(s.replace('#',c))

Attempt This Online!

Also inspired by Arnauld's JS answer. Converts to/from hex, replacing the excluded character with a "#".

\$\endgroup\$
3
  • \$\begingroup\$ Is taking bytes as input legal here? IMO the task is about console-like input, even if you make it a function to save a few bytes (asking, 'cause I just switched to py2 to work around long .decode() and .encode() for a hex-based solution...) \$\endgroup\$
    – STerliakov
    Nov 6 at 21:57
  • \$\begingroup\$ @STerliakov Our consensus for what a string is (it's also stated in the default I/O methods) \$\endgroup\$ Nov 6 at 22:05
  • \$\begingroup\$ This takes bytes plaintext to a string ciphertext back to a list of integers plaintext; using bytes in all cases or a list of integers in all cases would both seem reasonable, but using multiple string-like things across the answer does seem a little counter to the spirit of the question. Probably worth adding casts and asking about this in the meantime I would think. \$\endgroup\$ Nov 6 at 23:18
0
\$\begingroup\$

JavaScript (Node.js), 61 bytes

e=s=>c=>(s+"").split(c).join`#`
d=s=>c=>s.replace(/#/g,c).split`,`

Attempt This Online!

(It's five bytes less than on ATO because of e=, newline and d=.). Encodes as a list of ascii codes, which normally contain only digits and commas. Then the forbidden character is replaced with #.

The decoder does the reverse and returns an array of strings of ascii codes, like ["72","101", ...]. There are JS functions that interpret this as a sequence of integers (in particular new Uint8Array). An iterable of integers is a string.

\$\endgroup\$
0
\$\begingroup\$

brainfuck, 122 + 68 = 190 bytes

>-[-[-<]>>+<]>[->+>+>+>+<<<<]>>->>->+>+>,[-<<<-<->>>>]<<<[>>-<<[-]]<[>>-<<[-]]>>[->+<]>[<<<<++<++>>>>>-]<<<,[[-<<.>>]<.>,]

Try it online!

,[->+>+<<]>>[>+<[-<-<+>>]<[>>->.[-]<<<[-<+>]]>>[>+<[-]]<<<[->+<]>>,]

Try it online!

A Simple Unary encoder. Typically uses ! as the unary char and space as the separator, though if either of those chars are the exclusion char, uses # and " instead. (Char codes 32, 33, 34, and 35 respectively)

With Comments

Encoder

>-[-[-<]>>+<]>    Load 33
[->+>+>+>+<<<<]>  Smear it across 4 places
                  This is used to check the exclusion character as well as to store them
>->>->            Lower to 32
+>+>              Load two not flags
,                 Load the Exclusion Char
[-<<<-<->>>>]     Subtract any equals will be zeroed
<<<[>>-<<[-]]<[>>-<<[-]]  Invert the invert flags
>>[->+<]                  Add them essentially an or
>[<<<<++<++>>>>>-]        If either are true increment both chars by 2
<<<,
[                 Main Loop WHILE we have input
  [-              decrement the char
    <<.>>         Output the unary char
  ]
  <.>             Output the seperator
  ,               Read the next char
]

Decoder

,[->+>+<<] Load the first char as the unary char and spread it to the working char as well
>>[ While we have working chars
    >+< Set up the inverse flag
    [-<-<+>>]   Compare to the unary char
    <[ if theyre not equal
      >>- flip the invert flag
      >.[-] output and clear the running total
      <<<[-<+>]]
    >>[ Otherwise
      >+<
      [-]] Increment the running total
    <<<[->+<] Restore the test char
    >> Go home
,] Repeat
\$\endgroup\$
0
\$\begingroup\$

Uiua, 19 + 20 = 39 bytes

Encode:

++@0×2<@2∶♭⋯-@\0⊂@a

Decode:

↘1+@\0⍘⋯↯~7-+@0×2<@2

Try them online!


To encode, we prepend "a" to the string, convert it to bitwise representation, and then:

  • if the excluded letter is less than ASCII '2', we use '2' and '3' for the bits
  • otherwise we use '0' and '1' for the bits

The "a" is prepended to the string because it was the most character-efficient way of ensuring that the characters are always encoded into 7 bits each, otherwise a string like "1234" would be encoded as 6-byte characters

\$\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.