Take the 2-minute tour ×
Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. It's 100% free, no registration required.

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!

share|improve this question
1  
You give no details about how the masking should happen, so you should get whatever is shortest code, no matter your examples. –  Bill Woodger Apr 3 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" –  german_guy Apr 3 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 –  Bill Woodger Apr 3 at 9:19
1  
Or you could a bonus for random masking. –  ɐɔıʇǝɥʇuʎs Apr 3 at 10:45

18 Answers 18

up vote 12 down vote accepted

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
}+%      
share|improve this answer
    
Nice! Could you explain what each part of the code does? –  John Odom Apr 3 at 21:46
8  
Can't see why. GolfScript is so self-documenting. –  Evpok Apr 3 at 22:34
    
@JohnOdom Nevertheless I added some comments to the code. –  Howard Apr 4 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? –  Cruncher Apr 4 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******
share|improve this answer

Python (157 149 139 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 (129 123 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*
share|improve this answer
    
Your cheesy python can be shortened to g=lambda a,b:[a]+["*"*len(a)]*(b-1). :P –  cjfaure Apr 3 at 9:44
    
@Trimsty I see, thanks :) –  ɐɔıʇǝɥʇuʎs Apr 3 at 9:48
3  
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. –  Howard Apr 3 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. –  ɐɔıʇǝɥʇuʎs Apr 3 at 13:04
    
+1 for cheekiness :) –  ProgrammerDan Apr 3 at 14:13

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
$ 
share|improve this answer

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*
share|improve this answer
    
Does it guarantee at least on character from the input always? –  Bill Woodger Apr 3 at 10:08
    
Hmm.. no, probably not; good catch. For short strings and low values of i you probably could end up with ***/test. –  deroby Apr 3 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
share|improve this answer
    
duplicate strings, not very clean... –  CSᵠ Apr 4 at 10:44
    
What made you think you could mess around with the input to have it on two lines? –  Bill Woodger Apr 4 at 10:48
    
@BillWoodger: The question specifies no form of input. –  sudo Apr 4 at 12:34
    
Fair enough. could lets you through :-) –  Bill Woodger Apr 4 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 –  Cruncher Apr 4 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);
        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);
}
}

Sample usage (When run from the compiled class)

> java Anonymise OnlinePerson 3
*n****P***o*
O*li***e****
****ne**rs*n
share|improve this answer
    
Are you running that or typing? How does P get to be p? –  Bill Woodger Apr 3 at 8:45
    
No problem. Just tidy away the used comments now –  Bill Woodger Apr 3 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. –  Ypnypn Apr 3 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
share|improve this answer
    
Does it guarantee at least on character from the input always? –  Bill Woodger Apr 3 at 10:09
    
@BillWoodger No. –  cjfaure Apr 3 at 10:10
2  
Sorry, personally I regard it as not working then. then. –  Bill Woodger Apr 3 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). –  user2357112 Apr 3 at 10:57
2  
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. –  Howard Apr 3 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***
share|improve this answer

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
******************
******************
******************
******************
share|improve this answer

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.

share|improve this answer
    
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 ;) –  ɐɔıʇǝɥʇuʎs Apr 3 at 8:50
    
Are you saying if the text is five chars and it is asked for six replacements it breaks? –  Bill Woodger Apr 3 at 10:13
    
@BillWoodger He saying that using the closure compiler he shaved 8 chars from the total length of the code. –  Eduard Florinescu Apr 3 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 –  Bill Woodger Apr 3 at 10:35
1  
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;). –  toothbrush Apr 3 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="")
}
share|improve this answer

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***
share|improve this answer

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
share|improve this answer

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"
share|improve this answer

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", 
 "*********************************"]
share|improve this answer

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.

share|improve this answer

F# - 81 80 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
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.