Print Real Invisible Text

My previous challenge, Print invisible text was quite popular, likely due to how trivial it is.

However those more observant of you may have noticed that you're not really printing invisible text, because it's impossible to read what was inputted given only the output.

So I figured how about a real invisible text challenge.

Given a string consisting of only printable ASCII characters (0x20-0x7E), convert each character to a distinct Unicode character (in UTF-8 encoding) that is not one of the 95 printable ASCII characters (any UTF-8 character outside of the 0x20-0x7E range)

Input

A string of printable ASCII characters, either as a string or character array/list

Output

The input string with each character replaced with a distinct non-printable character. Each given character must have a corresponding non-printable character that is not used as the substitute for any other character.

If you are unable to print non-printable characters, you can output the character values instead.

For example if your code replaces all lowercase a's with 0x01, you may not use 0x01 as the substitution for any other characters.

Your code must also be deterministic. This means that if, given the string Hello, all lowercase l's are replaced with 0x03, your code must also replace all lowercase l's with 0x03 given any other string.

Testcases

It's somewhat difficult to write testcases for this challenge, so I'll simply show the output as a list of hexcodes

input     -> output
"Hello"   -> [0x01, 0x02, 0x03, 0x03, 0x04]
"Hi!"     -> [0x01, 0x05, 0x06]
""        -> []
"   H   " -> [0x07, 0x07, 0x07, 0x01, 0x07, 0x07, 0x07]
"yo! "    -> [0x08, 0x04, 0x06, 0x07]


Here is a Stack Snippet to generate both a regular leaderboard and an overview of winners by language.

/* Configuration */

var QUESTION_ID = 123447; // Obtain this from the url
// It will be like https://XYZ.stackexchange.com/questions/QUESTION_ID/... on any question page
var COMMENT_FILTER = "!)Q2B_A2kjfAiU78X(md6BoYk";
var OVERRIDE_USER = 48934; // This should be the user ID of the challenge author.

/* App */

return "https://api.stackexchange.com/2.2/questions/" +  QUESTION_ID + "/answers?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + ANSWER_FILTER;
}

}

jQuery.ajax({
method: "get",
dataType: "jsonp",
crossDomain: true,
success: function (data) {
data.items.forEach(function(a) {
});
comment_page = 1;
}
});
}

jQuery.ajax({
method: "get",
dataType: "jsonp",
crossDomain: true,
success: function (data) {
data.items.forEach(function(c) {
if (c.owner.user_id === OVERRIDE_USER)
});
else process();
}
});
}

var SCORE_REG = /<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/;

function getAuthorName(a) {
return a.owner.display_name;
}

function process() {
var valid = [];

var body = a.body;
if(OVERRIDE_REG.test(c.body))
body = '<h1>' + c.body.replace(OVERRIDE_REG, '') + '</h1>';
});

var match = body.match(SCORE_REG);
if (match)
valid.push({
user: getAuthorName(a),
size: +match[2],
language: match[1],
});

});

valid.sort(function (a, b) {
var aB = a.size,
bB = b.size;
return aB - bB
});

var languages = {};
var place = 1;
var lastSize = null;
var lastPlace = 1;
valid.forEach(function (a) {
if (a.size != lastSize)
lastPlace = place;
lastSize = a.size;
++place;

.replace("{{NAME}}", a.user)
.replace("{{LANGUAGE}}", a.language)
.replace("{{SIZE}}", a.size)

var lang = a.language;
if (/<a/.test(lang)) lang = jQuery(lang).text();

languages[lang] = languages[lang] || {lang: a.language, user: a.user, size: a.size, link: a.link};
});

var langs = [];
for (var lang in languages)
if (languages.hasOwnProperty(lang))
langs.push(languages[lang]);

langs.sort(function (a, b) {
if (a.lang > b.lang) return 1;
if (a.lang < b.lang) return -1;
return 0;
});

for (var i = 0; i < langs.length; ++i)
{
var language = jQuery("#language-template").html();
var lang = langs[i];
language = language.replace("{{LANGUAGE}}", lang.lang)
.replace("{{NAME}}", lang.user)
.replace("{{SIZE}}", lang.size)
language = jQuery(language);
jQuery("#languages").append(language);
}

}
body { text-align: left !important}

width: 290px;
float: left;
}

#language-list {
width: 290px;
float: left;
}

font-weight: bold;
}

table td {
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr>

</tbody>
</table>
</div>
<div id="language-list">
<h2>Winners by Language</h2>
<table class="language-list">
<tr><td>Language</td><td>User</td><td>Score</td></tr>
<tbody id="languages">

</tbody>
</table>
</div>
<table style="display: none">
</tbody>
</table>
<table style="display: none">
<tbody id="language-template">
</tbody>
</table>

• There is no such thing as a UTF-8 character: UTF-8 is a serialisation of Unicode, not a charset. And if "non-printable" makes sense in a Unicode context, it's certainly much narrower than "all but 95 of the hundreds of thousands of allocated codepoints". May 30, 2017 at 9:49
• @PeterTaylor Given we're talking in terms of character hexcodes here, I assumed it was clear that when I say UTF-8 character I mean a Unicode character in the UTF-8 encoding. ASCII is also an encoding standard, not a charset, yet people have no issue with the term "ASCII character". Anyway, I'll edit the wording to clarify. May 30, 2017 at 9:53
• Any particular reason UTF-8 is required? May 30, 2017 at 19:30
• Can the input be empty? May 31, 2017 at 17:18
• +1 for "If you are unable to print non-printable characters" Jun 2, 2017 at 6:17

Whitespace, 39 36 bytes





Try it online!

Explanation

nssn  ; label 'loop'
ssstssn ; push 4 to use as a multiplication operand
sns   ; dup 4 to use as a heap address
sns   ; dup 4 to use as a heap address
tnts  ; getchar and store at address 4
ttt   ; retrieve the value at address 4
tssn  ; multiply the character value by 4
tnss  ; putchar output the new character
nsnn  ; jmp 'loop'


Originally I wanted to multiply by -0 or -1 since they would be the shortest digits possible to declare in Whitespace. TIO does not differentiate between -0 and +0 so that's out. Unfortunately while the tutorial/spec is ambiguous about how to interpret a negative value as a char TIO (rightly) throws an error about the invalid argument so that also isn't an option.

The next shortest working constant is 4 so we end up performing the same basic approach as the Powershell/Pyth solutions.

Whitespace, 56 53 bytes - maps to tag characters





Try it online!

Explanation

Effectively the same approach as the previous version except this uses 0xE0000 as the constant and adds instead of multiplies. This maps the visible ASCII characters to the corresponding Unicode Tag Character (the range U+E0000-U+E007F). The intended use for this range was to indicate the language of the text in a plaintext file however that use is discouraged. This code will output valid labels if you prefix strings with a 0x01 character.

The Unicode Standard says that characters in this range have no visible rendering so I feel this meets the spirit of the challenge better than the previous approach.

• Using an invisible program to print invisible text. I like.
– Mark
May 30, 2017 at 20:58

Jelly, 3 bytes

O²Ọ


Try it online!

Squares each codepoint.

• Very similar to the Japt solution I came up with, expect I cubed the codepoint instead of squaring it - you OK with me posting it? May 30, 2017 at 9:53
• @Shaggy Japt ≠ Jelly, so you can post it. May 30, 2017 at 9:54
• Clever solution, didn't think of squaring. May 30, 2017 at 9:55
• Grand, just wanted to be sure, lest you thought I was just ripping off your solution :) May 30, 2017 at 10:00
• @Shaggy there's no guideline against porting a solution to another language ; if there was it would be terrible since there is usually one optimal algorithm which gives optimal implementations in most languages, and nobody but the first poster wouldn't be able to prove they came with the algorithm on their own. Of course if you indeed port someone else's solution, it's only fairplay to mention their answer. May 30, 2017 at 15:47

Japt, 5 2 bytes

cp


Try it online

Explanation

     :Implicit input of string U
c    :Map over the character codes of the string.
p    :Square them.
:Implicit output of result.

• Hmm, after closer examination, it seems that 126 ** 3 == 2000376, which isn't in the range [0..1114111]. You can still square though :) That's because UTF-8 ends there, while UTF-16 continues. May 30, 2017 at 10:00
• @EriktheOutgolfer Ehm. UTF-8 has exactly the same range as UTF-16 by definition. (In theory, UTF-8 could store higher codepoints, using 5 or 6 bytes per codepoint, but that is illegal.) May 31, 2017 at 6:49

Brain-Flak, 33 bytes

Includes +1 for -c

{((({}){}){}<>)<>}<>{({}<>)<>}<>


Try it online!

# For each character
{
# Multiply by 4 and move to the other stack
((({}){}){}<>)

# End loop
<>}

# For each character on the other stack
<>{

# Copy it back (reverse the stack)
({}<>)<>

# End loop
}<>


Braingolf v0.6, 17 bytes

VRl1-M[R.*>v]R&@


Squares each char value then prints.

-1 byte thanks to Erik the Outgolfer's squaring solution

Braingolf v0.7, 6 bytes [non-competing]

{.*}&@


Also squares each value then prints, but v0.7 has the "foreach" {} loop

Mathematica, 48 bytes

FromCharacterCode[4Mod[Hash/@Characters@#,978]]&


Explanation:

                             Characters@#      & - Convert string to array of characters
Hash/@                    - Hash them all using default hash
Mod[                  ,978]   - apply a modulus which uniquely transforms each potential character's hash into a number
4                              - times by 4 to move values out of 0x20-0x7E.
FromCharacterCode[                            ]  - Convert array of numbers back to string


Interestingly of the two modulus options less than 1000 which changed the 96 characters into 96 unique values with modulus 978 the lowest two values were 7 then 33. Luckily times by 4 converts this to 28 and 132 which both just fall outside the visible range. If I used the other modulus of 784 then I needed to multiply by 18 to move the numbers outside the range.

Test case.

Note: extra backslashes in there as escape characters for " and \. Also character 0x7E doesn't seem to want to paste correctly.

Input: "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~"  Output: The use of Hash came about as ToCharacterCode is really long. However hashing it was nearly as expensive. The easy mathematica way to do this would be 49 bytes: FromCharacterCode[4ToCharacterCode@Characters@#]&  Vyxals, 3 bytes C²C  Try it Online! That is, C # string to character codes ²C # square each and convert back to letters # the s flag joins as a single string.  but if IO was a little less restrictive: using K flag, 1 byte ²  Try it Online! Keg is weird okay. It just is.  # the K flag enables Keg mode, which makes integers and characters interchangeable. It's a tribute to my first golfing language and existed before I saw this challenge ² # square the code point of each character # output that as characters because K flag.  CJam, 8 5 bytes l95f+  Try it online! Adds 95 to each codepoint. • Isn't there a way to multiply or square instead? – Maya May 31, 2017 at 14:49 • @NieDzejkob No, this is based on the fact that Character + Long = chr(ord(Character) + Long). Character * Long = [Character] * Long. Character # Long = error (# is exponentiation in CJam). May 31, 2017 at 14:54 Pyth, 6 bytes smC*4C  Try it here. Multiplies each codepoint by 4. PowerShell, 32 31 Bytes -1 Thanks to neil, 99+ to 4* [char[]]"$args"|%{[char](4*\$_)}


multiplies 9 by each character code and prints it back.

• Out of interest, would multiplying by a small number (4-9) work?
– Neil
May 30, 2017 at 10:23
• the lowest printable times 4 is greater than the largest printable, that's -1 - thanks! May 30, 2017 at 10:47

=ArrayFormula(Join("",IfError(Char(Code(Mid(A1,Row(A:A),1))^2),"")))


I wanted to post this to show how awkward it is to do some basic functions in Sheets. Do you want to do an operation to every character in a cell and out the concatenated result? You're at 42 bytes before you even act on those characters.

=ArrayFormula(Join("",Mid(A1,Row(A:A),1)))


Otherwise, this is the same as other solutions: square the code point of each character.

05AB1E, 4 bytes

ÇnçJ


Try it online!

Squares each codepoint.

• The J can be left out, as a list of characters is also a string. Feb 1, 2021 at 23:58

CJam, 4 bytes

lWf^


XORs each code point with -1. CJam's characters are 16 bits wide, so this maps code point n to code point 65535 - n.

Try it online!

Decimal, 37 bytes

91D31030030012255D412D590D543D301291D


Explanation:

91D             ; declare jump 1
310         ; push user input to stack
300         ; duplicate
300         ; duplicate
12255D      ; push EOF to stack
412D        ; compare top two values, pop, push result
5 90D 5     ; if result == true, quit
43D         ; multiply top two values, pop, push result
301         ; print
2           ; pop
91D             ; goto jump 1


Try it online!

• so jumping to zero (90D) terminates? Jun 2, 2017 at 7:49
• @Mayube Exactly. Jun 2, 2017 at 14:20

Husk, 5 bytes

m(c□c


Try it online!

brainfuck, 12 bytes

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


Try it online!

Negate assuming latin1 output

• -5: ,[>.<,]. Replaces each character with null Apr 14, 2021 at 21:57
• @Makonede Each given character must have a corresponding non-printable character that is not used as the substitute for any other character.
– l4m2
Apr 15, 2021 at 1:49
• My bad, did not notice that. Apr 15, 2021 at 2:04

Grok, 14 bytes

:j
}lI_p+wYp
q


Modification of this cat program. It adds 95 to every character before it gets outputted, changing the character range from 0x0A - 0x7E to 0x7F - 0xDD.

Subleq (8-bit), 11 bytes

-1 15 -128
2 15 -1
15 -1 9
15 15


Subleq Emulator

Explanation

-1 15 -128 '0: input to 15:, if null exit; use -128 for exit so I can be used to add 128
'   in the next line saving a byte
2 15 -1    '3: 15: = 15: + 128
15 -1 9    '6: output 15:
15 15      '9: 15: = 0, goto 0; final 0 is not entered saving a byte


Python 3, 40 38 bytes

print([chr(ord(x)*9)for x in input()])


Try It Online!

C, 42 bytes

c;f(){while(~(c=getchar()))putwchar(c*c);}


Assumes a UTF-8 locale. Input is squared.

Try it online!

Clean, 25 bytes

import StdEnv

map((+)'~')


A partial function literal.

Try it online!

Realistically:

f s = {# c+'~' \\ c <-: s}


Unboxed array comprehension over an unboxed array of the same type ({#Char} -> {#Char}). Clean will be able to determine that the uniqueness is transferrable (!u:{#Char} -> u:{#Char}), and that the size is the same as the input size. This means that if you pass a *String, every character will be destructively updated with the corresponding one in the output, meaning no memory allocation or movement is done and the graph node is fully reused.

Try it online!

Erlang (escript), 18 bytes

One of the rare cases that Erlang is ever competitive.

f(X)->[I*I||I<-X].


Try it online!

Explanation

f(X)-> % Function taking a single string as input
[I*I|| % Square the code-point ...
I<-X]. % ... for every character taken from the string


Kotlin, 25 bytes

Subtract 99 from each character value. This ends up moving space to FFBD (as Kotlin uses UTF-16) and most of the ASCII set to control characters and unreadable garbage.

{fold(""){s,c->s+(c-99)}}


Try it online!

Duocentehexaquinquagesimal, 5 bytes

SYÍ’ü


Try it online!

Python 3, 47 bytes

lambda x:''.join(chr(y-32+(y>63)*98)for y in x)


Input must be bytes. I know this doesn't win, I'm posting it because the output could fit into an 8-bit ascii extension.

Try it online!

• The lambda x: must be included in the bytecount. Apr 15, 2021 at 18:51
• Also, you can save 14 with lambda s:[_*4for _ in s]`. Apr 15, 2021 at 19:01