# Anonymize names - sort of

Edit: There will be a bonus of -20 for random masking

Hello fellow Whistleblowers and carriers of secret knowledge.

I am due to an interview by mail and I - of course - cannot say specific names in full length. But as I want to disclose it in a rather not-so-obvious-way I need your help.

I will provide these names in the form of

"evil_company_that_makes_me_shiver"

but surely I do not want to spell it out completely. Your task to help me and the whole world is that you provide a nice program that turns the above into

"ev**************************"


or

"**il************************"


or even

"****_c**********************"


I think you get it. But there is one flaw: I want to disclose the name in total, so I need to pass the number of occurrences and the word itself to the script and it will disclose the letters bit by bit. An example could be

~$./whistle NSA 3 > "N**" > "**A" > "*S*"  or ~$ ./whistle nuclear 3
> "nu*****"
> "***lea*"
> "**c***r"


or

~$./whistle nuclear 2 > "nuc****" > "***lear"  I hope you can help me and as we know that size matters the shortest code wins. Help make this world a better place! • You give no details about how the masking should happen, so you should get whatever is shortest code, no matter your examples. Apr 3, 2014 at 8:47 • I thought that it was clear that the shortest code wins? Hmmm, maybe I need to improve my english grammar according to this :( /Edit: I don't really care on how the masking ist done, the above is just an example, but it should be "unreadable" if you read one accorrence alone" Apr 3, 2014 at 9:13 • Yes, shortest code was clear. But you will mostly get "regular" masking, not like your samples. Perhaps, since you don't mind anyway, change your sample outputs to regular ones. Specifically your second example of it running, plus the example in your question Apr 3, 2014 at 9:19 • Or you could a bonus for random masking. Apr 3, 2014 at 10:45 ## 18 Answers ### GolfScript, 26 charaters Since no specific way of disclosure was specified I decided to go for the shortest: :f,\{{\)f%.!@42if}%\;n}+%  You may experiment with this code online. Example: > "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 7 ******G******N******U***** *****F******M******T****** ****E******L******S******Z ***D******K******R******Y* **C******J******Q******X** *B******I******P******W*** A******H******O******V****  Commented code: :f # Save the second input to variable f , # Makes the array [0 1 2 .... f-1] \{ # \´{}+% builds a code block where # first the input string is pushed (done # via the + operator and afterwards the whole # block is applied to above array, i.e. # For each line 0, 1, ... # Here, the stack contains the line number (counter) # and the string { # {}% -> map to each character of the string # Stack contains counter and current character \)f% # Increase the counter by one and calculate modulo f . # Duplicate counter (will be used for the if below) ! # and test for zero # I.e. if counter==0 @ # Then Current character 42 # Else 42 (which is '*') if # }% # The mapping operation masked most of the characters \; # Discard the counter n # Add newline }+%  • Nice! Could you explain what each part of the code does? Apr 3, 2014 at 21:46 • Can't see why. GolfScript is so self-documenting. Apr 3, 2014 at 22:34 • @JohnOdom Nevertheless I added some comments to the code. Apr 4, 2014 at 14:25 • if you randomly decide whether or not you're going to shift all *'s by one to the right, does that count for the random masking bonus? Apr 4, 2014 at 14:42 ## PHP - 80 bytes <?for(;($c=$argv[1][~-$i+=1])?:$k^++$j;)echo$c?$i%$k=&$argv[2]^$j?'*':$c:$i=' ';  Sample usage: $ php whistle-blower.php ABCDEFGHIJKLMNOPQRSTUVWXYZ 7
******G******N******U*****
A******H******O******V****
*B******I******P******W***
**C******J******Q******X**
***D******K******R******Y*
****E******L******S******Z
*****F******M******T******


# Python (157149139 138-20=118):

def f(a,b):
from random import*;c=[["*"]*len(a) for i in range(b)]
for d in range(len(a)):choice(c)[d]=a[d]
for e in c:print "".join(e)


# Cheesy python (55 35):

You didn't tell me the required distribution ;)

g=lambda a,b:["*"*len(a)]*(b-1)+[a]


# Uniform python (129123 122):

def h(a,b):
c=[["*"]*len(a)for i in range(b)]
for d in range(len(a)):c=c[1:]+c[:1];c[0][d]=a[d]
for e in c:print"".join(e)


# Output:

a="Synthetica 'Evil' the Second"
b=7
f(a,b)
print
for i in g(a,b): print i
print
h(a,b)


gives

***t***i***'***** *********d
******************t** ******
******t*** ***********Se**n*
S**************l****e*******
*y******c***E************o**
**n*h****a**************c***
*****e*******vi*'**h********

****************************
****************************
****************************
****************************
****************************
****************************
Synthetica 'Evil' the Second

******t******v******e******d
S******i******i****** ******
*y******c******l******S*****
**n******a******'******e****
***t****** ****** ******c***
****h******'******t******o**
*****e******E******h******n*

• Your cheesy python can be shortened to g=lambda a,b:[a]+["*"*len(a)]*(b-1). :P Apr 3, 2014 at 9:44
• @Trimsty I see, thanks :) Apr 3, 2014 at 9:48
• I think but surely I do not want to spell it out completely clearly invalidates a solution which does not ensure that at least one character is masked in each line of output. Apr 3, 2014 at 12:00
• @Howard I know it is stretching the rules, but disclose the letters bit by bit can also mean first, disclose the first bit (that just happens to be the entire thing), repeat until the b is met. This can also happen for the first answer, so just claim you used that one, and it juuust happend to produce this result. (I just realized that you could also write it as ["*"*len(a)]*(b-1)+[a], so that you start by disclosing bits that just happen to contain 0 bytes of actual information, and on the last line, you disclose another bit.) I do however, realize, that this is stretching to the max. Apr 3, 2014 at 13:04

# Bash, 80 bytes

m=${1//?/*} for((i=1;d=i*${#1}/$2,i++<=$2;c=d)){
echo "${m:0:c}${1:c:d-c}${m:d}" }  In action: $ ./anon.sh stackoverflow.com 6
st***************
**ack************
*****ove*********
********rfl******
***********ow.***
**************com
$ ## C#, 226 This can be trimmed down if you replace the random stuff with a more simple distribution (think modulus) but the randomness is what got me interested. =) Anyway, putting everything on just one line I get 226 characters. In readable code it looks like this: private void F(string n, int i) { var l = n.Length; var r = new Random(); var a = new string[i]; for (var p = 0; p < l; p++) { var x = r.Next(0, i); for (var j = 0; j < i; j++) { a[j] += j == x ? n.Substring(p, 1) : "*"; } } for (var j = 0; j < i; j++) { Console.WriteLine(a[j]); } }  Sample output: Anonymize.exe ABCDEFGHIJKLMNOPQRSTUVWXYZ 7 *****F**I**********T**W*** A*****************S****X** **CDE**H*JKL************** *************N************ **************O*****U****Z *B****G*****M***QR******** ***************P*****V**Y*  • Does it guarantee at least on character from the input always? Apr 3, 2014 at 10:08 • Hmm.. no, probably not; good catch. For short strings and low values of i you probably could end up with ***/test. Apr 3, 2014 at 14:12 # Perl, 24 $_ x=<>;s/(.|
)./\1*/g


Requires the -p switch, which accounts for two of the bytes. Reads from STDIN.

### How it works

• Because of the -p switch, Perl reads the first input lie and stores its contents in $_. • The command $_ x=<>; duplicates the first input line the number of times the second input line (<>) specifies.

• The command s/(.|\n)./\1*/g; consumes two characters and replaces the second (which cannot be a newline) with an asterisk. It does this until it has consumed the entire contents of $_. Since newlines count as the first character, this will obfuscate all even characters on the first line and all odd characters on the remaining lines. • Because of the -p switch, Perl prints the contents of $_.

### Example input

codegolf
4


### Example output

c*d*g*l*
*o*e*o*f
*o*e*o*f
*o*e*o*f

• duplicate strings, not very clean...
– CSᵠ
Apr 4, 2014 at 10:44
• What made you think you could mess around with the input to have it on two lines? Apr 4, 2014 at 10:48
• @BillWoodger: The question specifies no form of input. Apr 4, 2014 at 12:34
• Fair enough. could lets you through :-) Apr 4, 2014 at 13:06
• Since newlines count as the first character, this will obfuscate all even characters on the first line and all odd characters on the remaining lines. Sounds like it will fail for codegolf\n1 Apr 4, 2014 at 14:40

# Java - 490

Yeah, not really golfed or anything. Oh well, here it is:

import java.util.ArrayList;public class Anonymise {public static void  main(String[] args){ArrayList[] c=new ArrayList[Integer.parseInt(args[1])];for(int i=0;i<c.length;i++)c[i]=new ArrayList();for(char a:args[0].toCharArray()){int f=new java.util.Random().nextInt(c.length);c[f].add(a);for(int i=0;i<c.length;i++)if(i!=f)c[i].add('*');}ArrayList<String> b = new ArrayList<String>();for(ArrayList a:c){String s = "";for(Object o : a)s += o;b.add(s);}for(String s : b)System.out.println(s);}}


In a nice, legible format:

import java.util.ArrayList;

public class Anonymise {

public static void main(String[] args){
ArrayList[] c = new ArrayList[Integer.parseInt(args[1])];
for(int i=0;i<c.length;i++)
c[i]=new ArrayList();
for(char a:args[0].toCharArray()){
int f = new java.util.Random().nextInt(c.length);
for(int i=0;i<c.length;i++)
}
ArrayList<String> b = new ArrayList<String>();
for(ArrayList a:c){
String s = "";
for(Object o : a)
}
for(String s : b)
System.out.println(s);
}
}


Sample usage (When run from the compiled class)

> java Anonymise OnlinePerson 3
*n****P***o*
O*li***e****
****ne**rs*n

• Are you running that or typing? How does P get to be p? Apr 3, 2014 at 8:45
• No problem. Just tidy away the used comments now Apr 3, 2014 at 8:46
• Just import java.util.* Also, ArrayList<String> = new ArrayList<>(); will work. You can also remove a lot of spaces. And most of the time, you can use List instead of ArrayList. Apr 3, 2014 at 20:55

# Python 3 - 187 (-20 = 167)

Took me a while to write, could probably be golfed more.

import sys,random as a
b=sys.argv
x=[""for i in' '*int(b[2])]
for i in range(len(b[1])):n=a.randrange(len(x));x=[c+["*",b[1][len(c)]][j==n]for j,c in enumerate(x)]
for i in x:print(i)


Sample usage:

$python tx.py super_anonymous_user 3 s******n**y*ou****e* **p***a**n*m**s_us*r *u*er_**o***********  ## As a function - 161 (-20 = 141) from random import* def f(c,n): x=[""for i in' '*n] for i in range(len(c)):n=randrange(len(x));x=[v+["*",c[len(v)]][j==n]for j,v in enumerate(x)] return x  • Does it guarantee at least on character from the input always? Apr 3, 2014 at 10:09 • @BillWoodger No. Apr 3, 2014 at 10:10 • Sorry, personally I regard it as not working then. then. Apr 3, 2014 at 10:33 • Some simple ways to shorten things: Unpack sys.argv in the assignment, and initialize x as x=['']*int(b[2]) (with b[2] replaced by whatever you name the variable you use in the unpacking). Apr 3, 2014 at 10:57 • I think but surely I do not want to spell it out completely clearly invalidates a solution which does not ensure that at least one character is masked in each line of output. Apr 3, 2014 at 11:58 # C# 184 namespace System.Linq{class P{static void Main(string[] a){int n=int.Parse(a[1]);for(int i=0;i<n;i++)Console.WriteLine(new String(a[0].Select((c,x)=>(i+x)%n==0?c:'*').ToArray()));}}}  Non-golfed namespace System.Linq { class P { static void Main(string[] a) { int n = int.Parse(a[1]); for (int i = 0; i < n; i++) Console.WriteLine(new String(a[0].Select((c, x) => (i + x) % n == 0 ? c : '*').ToArray())); } } }  Sample: > Anonymize.exe "qwertyuio" 4 q***t***o ***r***i* **e***u** *w***y***  # Perl 6 (77 bytes) This may count as cheating, but it solves the problem (it doesn't print the string entirely). ($_,$!)=@*ARGS;.say for .chop~"*","*"x.chars-1~.substr(*-1),"*"x.chars xx$!-2


And the sample output.

> ./script.p6 ultimately_awesome 6
ultimately_awesom*
*****************e
******************
******************
******************
******************


# JavaScript - 170

function s(a,e){o="",b=a.length,x=b/e|0,y=b-x*e;for(c=1;c<=e;c++){r="*";o+=Array((c-1)*x+1).join(r)+a.substr(c*x-x,c==e?x+y:x)+Array(c<e?b-c*x+1:0).join(r)+"\n"}return o}


Quick one, breaks if you supply a name and ask for more pieces than characters, but works up to that point and down to 1. Could be golfed more I assume, so may revise or take advice, as it's a bit of a mess.

Edit: Golfed further (from 187) with removal of brackets (@Synthetica) and Math.floor replacement from @toothbrush. For loop change as suggested results in errors in the output.

• function s(d,a){o="";b=d.length;x=Math.floor(b/a);y=b-x*a;for(c=1;c<=a;c++)r="*",o+=Array((c-1)*x+1).join(r)+d.substr(c*x-x,c==a?x+y:x)+Array(c<a?b-c*x+1:0).join(r)+"\n";return o}; closure-compiler.appspot.com/home was capable of shaving off 8 chars ;) Apr 3, 2014 at 8:50
• Are you saying if the text is five chars and it is asked for six replacements it breaks? Apr 3, 2014 at 10:13
• @BillWoodger He saying that using the closure compiler he shaved 8 chars from the total length of the code. Apr 3, 2014 at 10:27
• @EduardFlorinescu, sorry, I was unclear, I meant this bit breaks if you supply a name and ask for more pieces than characters Apr 3, 2014 at 10:35
• Use x=b/e|0 instead of x=Math.floor(b/e). Also, for(c=1;c<=e;c++) can be shortened to for(c=1;c++<=e;). Apr 3, 2014 at 11:48

# R, (120-20)=100 bytes (2 different solutions of same length)

Beside the conciseness, R for sure features the best random number generator! :) Here the 1st solution of length 120:

function(s,n){
c=strsplit(s,"")[[1]]
e=sample(n,nchar(s),T)
sapply(1:n,function(i)paste(ifelse(e==i,c,"*"),collapse=""))
}


If the function is named as f, the output looks like:

> f("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 7)
"******GH*****************Z"
"ABCD*********N************"
"***********L**O***********"
"*********J**M**P*RS*U**X**"
"**********K***********W***"
"*****F**I*******Q*********"
"****E**************T*V**Y*"


And here the 2nd solution, again with random masking, and again 120 bytes long:

function(s,n){
x=matrix("*",n,(m=nchar(s)))
x[cbind(sample(n,m,T),1:m)]=strsplit(s,"")[[1]]
apply(x,1,paste,collapse="")
}


# Scala (1) 177 bytes

After the solution in R I also found one in Scala, here it is:

def f(s:String, n:Int)={
def c=s map (z=>z.toString)
def r=(1 to s.length) map (v=>v%n+1)
(for (i<-1 to n) yield s zip r map (t=>if(t._2==i) t._1 else "*") mkString "") map println
}


# Scala (2) 129 bytes

After a short research I found the method zipWithIndex. Wonderful :)

def f(s:String, n:Int)={
(for (i<-0 until n) yield s.zipWithIndex.map (t=>if(t._2 % n==i) t._1 else "*") mkString "") map println
}


Here the solution:

f("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 4)
***D***H***L***P***T***X**
A***E***I***M***Q***U***Y*
*B***F***J***N***R***V***Z
**C***G***K***O***S***W***


# Javascript - 166

function s(b,a){o="";k=Array(a).join("*");t=k+b+k;for(i=a;0<i;i--)o+=t.replace(RegExp(".{"+(a-1)+"}(.)","g"),k+"\$1").substr(i-1,b.length)+"\n",t=t.substr(1);return o}


Using regex.

Sample

> s("four",5)
f***
*o**
**u*
***r
****

> s("evil_company_that_makes_me_shiver",3)
e**l**o**a**_**a**m**e**m**s**v**
*v**_**m**n**t**t**a**s**e**h**e*
**i**c**p**y**h**_**k**_**_**i**r


# k [50-20=30 chars]

{.{@[y#"*";z;:;x z]}[x;c]'=_d*y%#d:<<(-d)?!d:c:#x}


### Explanation

1. Input: x=String which needs to be masked, y=number of lines
2. Count the number of characters c in string and create y buckets.
3. Place c mod y characters in each bucket.
4. For each bucket, print "*" for the remaining numbers not in that bucket.

### Example

{.{@[y#"*";z;:;x z]}[x;c]'=_d*y%#d:<<(-d)?!d:c:#x}["abcdefghijklmnopqrstuvwxyz";4]


### Output

"ab*d*f***j**********u*w***"
"**c*e*gh**k*****q*s*******"
"********i***mn*****t*v**y*"
"***********l**op*r*****x*z"


## JavaScript - 139-20=119

function e(s,c){r=[];for(i=c;i--;)r[i]='';for(i=0;i<s.length*c;i++)x=i%c,n=(x<1?Math.random()*c|0:n),r[x]+=(n!=x?'*':s[(i/c)|0]);return r}


Usage and sample output:

console.log(e("evil_company_that_makes_me_shiver", 3));

["**i*_**mp*****h**_ma**********v**",
"*****co**a***t*a******s*me_*hi*e*",
"ev*l******ny_***t***ke*_***s****r"]


As the masking is random, the output may also look like this:

console.log(e("evil_company_that_makes_me_shiver", 2));

["evil_company_that_makes_me_shiver",
"*********************************"]


# Julia 56-20=36 (works often)

f(s,n)=[join([rand()<1.9/n?c:"*" for c in s]) for r=1:n]

julia> f("evilcompany!!!",4)
4-element Array{Union(UTF8String,ASCIIString),1}:
"e**l**mp*ny*!*"
"***lco********"
"e*il**m*a*y!*!"
"evil**m*an***!"


Trying to go for a shorter random solution I decided to sacrifice guaranteed functioning for a shorter solution. It's really not a very good solution.

# F# - 8180 75

This is fairly similar to others that have been offered:

let a s n=[for i in 0..n-1->String.mapi(fun j c->[c;'*'].[min((j+i)%n)1])s]


Sample:

a "evil_company_that_makes_me_shiver" 7

e******m******h******e******h****
******o******t******k******s*****
*****c******_******a******_******
****_******y******m******e******r
***l******n******_******m******e*
**i******a******t******_******v**
*v******p******a******s******i***


As a full, stand-alone application it comes out to 160 155 156 characters:

[<EntryPoint>]let m (a:string array)=
let n=System.Int32.Parse a.[1]
for i in 0..n-1 do printfn"%s"(String.mapi(fun j c->[c;'*'].[min((j+i)%n)1])a.[0])
0