• A string containing printable ASCII (excluding spaces, tabs and new-lines)
  • A boolean


The parts of the String are divided into four groups:

  • Lowercase letters
  • Uppercase letters
  • Digits
  • Other

Based on the boolean, we either output the highest occurrence of one (or multiple) of these four groups, or the lowest, replacing everything else with spaces.

For example:

Input: "Just_A_Test!"
It contains:
- 3 uppercase letters: JAT
- 6 lowercase letters: ustest
- 0 digits
- 3 other: __!

These would be the outputs for true or false:

true:   " ust    est "

// digits have the lowest occurrence (none), so everything is replaced with a space
false:  "            "

(Note: You are allowed to ignore trailing spaces, so the outputs can also be " ust est" and "" respectively.)

Challenge rules:

  • The input will never be empty or contain spaces, and will only consist of printable ASCII in the range 33-126 or '!' through '~'.
  • You are allowed to take the input and/or outputs as character-array or list if you want to.
  • Any two consistent and distinct values for the boolean are allowed: true/false; 1/0; 'H'/'L'; "highest"/"lowest"; etc. Note that these distinct values should be used (somewhat) as a boolean! So it's not allowed to input two complete programs, one that gives the correct result for true and the other for false, and then having your actual code only be <run input with parameter>. Relevant new default loophole I've added, although it can still use a lot of finetuning regarding the definitions..
  • If the occurrence of two or more groups is the same, we output all those occurrences.
  • The necessary trailing spaces are optional, and a single trailing new-line is optional as well. Necessary leading spaces are mandatory. And any other leading spaces or new-lines aren't allowed.

General rules:

  • This is , so shortest answer in bytes wins.
    Don't let code-golf languages discourage you from posting answers with non-codegolfing languages. Try to come up with an as short as possible answer for 'any' programming language.
  • Standard rules apply for your answer, so you are allowed to use STDIN/STDOUT, functions/method with the proper parameters, full programs. Your call.
  • Default Loopholes are forbidden.
  • If possible, please add a link with a test for your code.
  • Also, please add an explanation if necessary.

Test cases:

Inputs:                              Output:

"Just_A_Test!", true                 " ust    est "     (or " ust    est")
"Just_A_Test!", false                "            "     (or "")
"Aa1!Bb2@Cc3#Dd4$", either           "Aa1!Bb2@Cc3#Dd4$"
"H@$h!n9_!$_fun?", true              " @$ !  _!$_   ?"
"H@$h!n9_!$_fun?", false             "H     9        "  (or "H     9")
"A", true                            "A"
"A", false                           " "                (or "")
"H.ngm.n", true                      "  ngm n"
"H.ngm.n", false                     "       "          (or "")
"H.ngm4n", false                     "H.   4 "          (or "H.   4")
  • \$\begingroup\$ Is it acceptable to output the most/fewest as separate entries? For example, for the "hashing is fun" test case, can "H " and " 9 " (with appropriate spaces) be output instead of "H 9"? \$\endgroup\$ Oct 31, 2017 at 14:53
  • \$\begingroup\$ @AdmBorkBork I don't get what you mean; both the H and 9 are part of the "fewest". \$\endgroup\$ Oct 31, 2017 at 15:08
  • \$\begingroup\$ Can the boolean input value be "max"/"min", which is then used as Math[b] to refer to Math.max or Math.min? \$\endgroup\$ Nov 1, 2017 at 1:21
  • \$\begingroup\$ @JustinMariner You know.. I changed my mind sorry about that. I guess it's for JS? I think a lot of programming languages can utilize something like this, so too many of the existing answers should be changed. So sorry, you'll have to keep the b?"max":"min" in your answer.. It's a fine line I guess, maybe I should just use a truthy/falsey value next time.. \$\endgroup\$ Nov 1, 2017 at 8:07

16 Answers 16


Jelly, 31 bytes


Try it online!

The boolean values are 2 and 1 (or any other positive even/odd pair), which represent True and False respectively. I will try to add an explanation after further golfing.

Thanks to caird coinheringaahing for saving 2 bytes, and to Lynn for saving 4 bytes! Thanks to one of Erik's tricks, which inspired me to save 4 bytes!

How it works

Note that this is the explanation for the 35-byte version. The new one does roughly the same (but tweaked a bit by Lynn), so I won't change it.

ØBUs26f€³µ³ḟØBW,µẎLİ⁴¡$ÐṀF - Niladic helper link.
ØB                         - String of base digits: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
  U                        - Reverse.
   s26                     - Chop into sublists of length 26, preserving shorter
                             trailing substrings.
      f€³                  - For each, keep the common characters with the input.
            ØB             - Base digits.
          ³ḟ               - Get the signs in the input. Filter the characters of the
                             input that aren't alphanumeric.
              W,µẎ         - Concatenate (wrap, two element list, tighten).
                       ÐṀ  - Keep the elements with maximal link value.
                  L        - Length.
                    ⁴¡     - Do N times, where N is the second input.
                   İ       - Inverse. Computes 1 ÷ Length. 2 maps to the length itself,
                             because 1 ÷ (1 ÷ Length) = length; 1 yields
                             (1 ÷ Length), swapping the maximal numbers with minimal ones.
                         F - Flatten.

¹⁶e¢$?€ - Main link.
      € - For each character.
   e¢?  - If it is contained by the last link (called niladically), then:
¹       - Identity, the character itself, else:
 ⁶      - A space.
  • \$\begingroup\$ 40 bytes \$\endgroup\$ Oct 31, 2017 at 13:40
  • \$\begingroup\$ @cairdcoinheringaahing Thanks! :) I wanted to golf that part ever since I posted the answer, but I couldn't figure out why it didn't work... I had an extraneous µ D: \$\endgroup\$
    – Mr. Xcoder
    Oct 31, 2017 at 13:42
  • \$\begingroup\$ 31 bytes: generate the classes as ØṖḟØBṭØBUs26¤, and then test membership with f and Ç instead of e¢$. \$\endgroup\$
    – Lynn
    Oct 31, 2017 at 16:55

Python 2, 166 158 bytes

t=lambda c:('@'<c<'[','`'<c<'{','/'<c<':',1-c.isalnum())
def f(s,b):x=map(sum,zip(*map(t,s)));print''.join([' ',c][x[t(c).index(1)]==sorted(x)[-b]]for c in s)

Try it online!

  • \$\begingroup\$ 158 bytes \$\endgroup\$
    – Mr. Xcoder
    Oct 31, 2017 at 13:27
  • \$\begingroup\$ @Mr.Xcoder Thanks; Just got that as well :) \$\endgroup\$
    – TFeld
    Oct 31, 2017 at 13:28

R, 193 186 179 158 bytes

-7 bytes thanks to NofP and his suggestion of cbind

-6 bytes using outer, -1 byte switching [^a-zA-Z0-9] with [[:punct:]]

-21 bytes thanks to MickyT for pointing out a list of characters is allowed

S[!colSums(y[(s=rowSums(y))=="if"(B,max,min)(s),,drop=F])]=" "

Verify all test cases

Takes 1/T as truthy (max) and 0/F as falsey (min), and takes S as a list of single characters.

Try it online!

In my original version (with NofP's suggestions), the matrix y is constructed by evaluating grepl(regex, S) for each regex, then concatenating them together as columns of a matrix. This results in multiple calls to grepl, but as S is fixed, it seemed that something else needed to be done. As I noted:

There are potentially shorter approaches; mapply, for example:


unfortunately, this will not simplify as a matrix in the 1-character example of "A".

I used outer rather than mapply, which always returns an array (a matrix in this case), and was forced to Vectorize grepl, which is really just an mapply wrapper around it.

I also discovered the predefined character group [:punct:] which matches punctuation (non-space, non-alphanumeric) characters.

  • 1
    \$\begingroup\$ If you replace the matrix with a cbind, you could reduce to 186 bytes: y=cbind(g("[a-z]",S),g("[A-Z]",S),g("\\d",S),g("[^a-zA-Z0-9]",S)) \$\endgroup\$
    – NofP
    Oct 31, 2017 at 15:16
  • \$\begingroup\$ @NofP oh, very nice. Also, you can surround code with backticks (`) to have it display like this. :) \$\endgroup\$
    – Giuseppe
    Oct 31, 2017 at 15:22
  • \$\begingroup\$ The rules state that you can use a character array or list as input , so you could probably remove the S=el(strsplit(G,"")) \$\endgroup\$
    – MickyT
    Oct 31, 2017 at 22:10
  • \$\begingroup\$ @MickyT ah, I overlooked that, thank you. \$\endgroup\$
    – Giuseppe
    Nov 1, 2017 at 13:15

Husk, 27 26 24 22 bytes

-2 bytes thanks to Zgarb

-2 bytes thanks to Leo

Takes ' ' as False and 'a' as True (In Husk, whitespace in Fasly and all other characters are Truthy)


Try it online!

How does it work?

Fż▲→ġ#¬Ö#≡⁰Ṫḟë½D±o¬□m;   Function, takes a character c and a string S as arguments
                    m;   Wrap each character in S into it's own string
             ë           List of four functions returning booleans:
              ½D±o¬      Lower case?,Upper case?,Digit?,Not alphanumeric?
           Ṫḟ            Outer product with find†
       Ö#≡⁰              Sort on how many characters have the same Truthyness as c
    ġ#¬                  Group strings with equal numbers of spaces
   →                     Take the last group
Fż▲                      Squash it all into one list

is a function that takes a predicate p and a list L and returns the first element of L that satisfies p. If no element satisfies p a default argument is returned. In this case ' '. By applying to a one character string, we are essentially saying if p c then c else ' '.

Is function that takes a function f and two lists L1,L2. It returns a table of f applied over all pairs of L1 and L2. In this case f is , L1 is our list of 4 functions, and L2 is the list of one character strings.

After Ṫḟ we have a list of strings where each string is the result of replacing characters which don't satisfy one of the rules with a ' '.

NB: In newer versions of Husk, ġ#¬Ö#≡⁰ can be replaced by k#≡⁰ for a 3 byte saving!

  • \$\begingroup\$ Out of curiosity: why ' ' and 'a'? Maybe I understand it better when the explanation is added, because I can't read Husk. ;) \$\endgroup\$ Oct 31, 2017 at 20:05
  • \$\begingroup\$ Nice! Here's 24 bytes using . \$\endgroup\$
    – Zgarb
    Oct 31, 2017 at 21:56
  • \$\begingroup\$ @Zgarb Thanks! I didn't really understand what Mmm was doing myself :) \$\endgroup\$
    – H.PWiz
    Oct 31, 2017 at 22:00
  • \$\begingroup\$ Unfortunately this doesn't really help with golfing, but S`?' might be simpler as ?IK' \$\endgroup\$
    – Leo
    Oct 31, 2017 at 22:59
  • \$\begingroup\$ I tend to avoid using I, sometimes it makes the interpreter take forever. It also seems wasteful. \$\endgroup\$
    – H.PWiz
    Oct 31, 2017 at 23:05

Husk, 31 29 28 bytes

SMS?' `ṁ§foSM≠?▲▼⁰M#Kë½D±o¬□

Uses 0 for minimal and 1 for maximal character counts. Try it online!


Lists of functions are cool.

SMS?' `ṁ§foSM≠?▲▼⁰M#Kë½D±o¬□  Inputs are bit B and string S.
                     ë        Make a list L of the four functions
                      ½       is-lowercase-letter,
                       D      is-uppercase-letter,
                        ±     is-digit, and
                         o¬□  not is-alphanumeric.
                  M#          For each of them, take number of matches in S,
              ?▲▼⁰            take maximum or minimum depending on B,
          oSM≠                and mark those entries that are not equal to it.
        §f          K         Remove from L the functions that correspond to marked entries, call the result L2.
                              These functions test whether a character should be replaced by a space.
SM                            Do this for each character C in S:
      `ṁ                      Apply each function in L2 to C and sum the results.
  S?'                         If the result is positive, return space, otherwise return C.

Python 2, 140 bytes

g=lambda x:x.isalnum()-~(x>'Z')*x.isalpha()
def f(s,m):k=map(g,s).count;print''.join([' ',c][k(g(c))==sorted(map(k,range(4)))[m]]for c in s)

Try it online!

Jonathan Frech saved a byte. Thanks!

Highest is m=-1, lowest is m=0.

  • 1
    \$\begingroup\$ I think you can save a byte by replacing +x.isalpha()*-~(x>'Z') with -~(x>'Z')*x.isalpha(). \$\endgroup\$ Nov 1, 2017 at 17:30

Jelly, 35 bytes


Try it online!


Java (OpenJDK 8), 448 439 432 362 361 354 352 348 343 320 bytes

s->b->{int w[]=new int[4],m=0,n=-1>>>1,l;s.chars().forEach(c->{if(c>96&c<123)w[0]++;else if(c>64&c<91)w[1]++;else if(c>47&c<58)w[2]++;else++w[3];});for(int W:w){m=W>m?W:m;n=W<n?W:n;}l=m-n;m=b?m:n;return l<1?s:s.replaceAll("["+(w[0]!=m?"a-z":"")+(w[1]!=m?"A-Z":"")+(w[2]!=m?"\\d]":"]")+(w[3]!=m?"|[^a-zA-Z0-9]":"")," ");}

Try it online!

  • \$\begingroup\$ 366 bytes \$\endgroup\$ Oct 31, 2017 at 13:56
  • \$\begingroup\$ You can remove the + in \\|+$ for an additional -1 byte. \$\endgroup\$ Oct 31, 2017 at 14:05
  • \$\begingroup\$ You can save three more bytes by changing the last part to String r=(w[0]!=m?"[a-z]|":"")+(w[1]!=m?"[A-Z]|":"")+(w[2]!=m?"[0-9]|":"")+(w[3]!=m?"[^a-zA-Z0-9]|":"");return r.isEmpty()?s:s.replaceAll(r.replaceAll(".$","")," ");}. \$\endgroup\$ Oct 31, 2017 at 14:17
  • \$\begingroup\$ Oh, and n=s.length() can be n=-1>>>1 for an additional -4. \$\endgroup\$ Oct 31, 2017 at 14:22
  • \$\begingroup\$ Oh, one more small thing to golf: [0-9] -> \\d \$\endgroup\$ Oct 31, 2017 at 15:23

Ruby, 118 116 bytes

Takes 0 (lowest) or -1 (highest) for its second argument.

-2 bytes thanks to Lynn.

->s,t{s.gsub(/./){|c|[/\d/,/[a-z]/,/[A-Z]/,/[^\da-z]/i].group_by{|x|s.scan(x).size}.sort[t][1].any?{|x|x=~c}?c:" "}}

Try it online!


  s.gsub(/./) {|c|
    [ /\d/,
    .group_by {|x| s.scan(x).size }
    .any? {|x| x =~ c } ? c : " "
  • \$\begingroup\$ Very cool answer! You can use -1 as the “highest” value and replace minmax[t] by sort[t]. \$\endgroup\$
    – Lynn
    Nov 1, 2017 at 17:10

Python 2, 190 183 174 173 bytes

Thanks to Jonathan Frech for shortening it

from re import*
def f(i,c):
 q='[a-z]','[A-Z]','\d','[\W_]';l=[len(set(finditer(p,i)))for p in q]
 for j,k in enumerate(l):
	if k-eval(c):i=compile(q[j]).sub(' ',i)
 print i

This takes the strings 'max(l)' and 'min(l)' as true and false. (I don't think this breaks the rules...?) This is longer than the other two python answers but different so I thought I'd post it. I'm not a great golfer so I'm guessing this could be improved further but all the things I tried didn't work.

Try it online!

  • \$\begingroup\$ Hello, and welcome to the site! When I try to run this, I get errors, and I'm not entirely sure why. One reason could be that sum(1for m... should be sum(1 for m..., but I think there are other problems too. Could you provide a link to an online interpreter (such as tio) to demonstrate how you're calling this, and to show it isn't erroring? \$\endgroup\$
    – DJMcMayhem
    Nov 1, 2017 at 17:06
  • \$\begingroup\$ @DJMcMayhem I just added a link, thanks for providing the link I wasn't sure how to do it. I am not getting an error when I run it there. \$\endgroup\$
    – dylnan
    Nov 1, 2017 at 17:13
  • \$\begingroup\$ Ah, I couldn't tell that you were inputting max(l) and min(l) as strings, that's why I was getting errors. Thanks for clearing that up! Although now, this is on the edge of violating rule #3, ` Note that these distinct values should be used (somewhat) as a boolean`, but it's definitely a little bit of a gray area. \$\endgroup\$
    – DJMcMayhem
    Nov 1, 2017 at 17:17
  • \$\begingroup\$ BTW, here's a TIO pro-tip: If you put your function calls in the footer field, they won't be counted towards your byte count, so you can easily see how long your answer is: Try it online! \$\endgroup\$
    – DJMcMayhem
    Nov 1, 2017 at 17:19
  • \$\begingroup\$ @DJMcMayhem Ah thanks. I agree it's kind of a gray area. I could take 'max' and 'min' as true false then do eval(c+'(l)') which adds 6 Bytes and seems more acceptable but until OP disallows my answer I'm assuming it's okay. \$\endgroup\$
    – dylnan
    Nov 1, 2017 at 17:21

PHP, 161 158 bytes

for([,$s,$z]=$argv;~$c=$s[$i++];)foreach([punct,upper,lower,digit]as$f)(ctype_.$f)($c)?$$i=$f:$g[$f]++;while(~$c=$s[$k++])echo$g[$$k]-($z?min:max)($g)?" ":$c;

Run with -nr or try it online.

  • first loop: for each position, remember the group of the character
    and count the occurences of groups that the current character is not in.
    (that negation saved 3 bytes)
  • depending on second parameter, pick min non-count for truthy, max non-count for falsy.
  • second loop: if (group of current character) non-count differs
    from min/max non-count then print space, else print character.
  • 1
    \$\begingroup\$ @KevinCruijssen Make sure you have the latest PHP version (7.1.0) selected. \$\endgroup\$
    – Titus
    Nov 1, 2017 at 2:05

JavaScript (ES6), 151 149 bytes

(s,f,a=[/\d/,/[A-Z]/,/[a-z]/,/[_\W]/],b=a.map(r=>s.split(r).length))=>s.replace(/./g,c=>b[a.findIndex(r=>r.test(c))]-Math[f?"max":"min"](...b)?' ':c)
<input id=s oninput=o.textContent=g(s.value,f.checked)><input id=f type=checkbox onclick=o.textContent=g(s.value,f.checked)><pre id=o>

Sadly the rules probably don't allow me to pass Math.max or Math.min as the flag. Edit: Saved 2 bytes thanks to @JustinMariner.


Jelly, 37 bytes


Try it online!

-6 bytes "borrowing" from Erik's post :D

  • \$\begingroup\$ Lol preserving the spaces is essentially half the program :D \$\endgroup\$
    – Mr. Xcoder
    Oct 31, 2017 at 13:21
  • \$\begingroup\$ Could you add an explanation, or are you still working on golfing it first? \$\endgroup\$ Oct 31, 2017 at 13:21
  • \$\begingroup\$ @KevinCruijssen Golfing first :D \$\endgroup\$
    – hyper-neutrino
    Oct 31, 2017 at 13:38

Java (OpenJDK 8), 307 + 34 306 + 27 295 bytes

My "interesting" take on the challenge.

Thanks to Kevin Cruijssen for cutting down the import bytes removing the import entirely!

s->b->{String t=s.replaceAll("\\d","2").replaceAll("[a-z]","0").replaceAll("[A-Z]","1").replaceAll("\\D","3"),v="";int a[]={0,0,0,0},i=0,z=0,y=-1>>>1;t.chars().forEach(j->{a[j%4]++;});for(int x:a){z=x>z?x:z;y=x<y?x:y;}for(;i<s.length();i++)v+=a[t.charAt(i)%4]!=(b?z:y)?" ":s.charAt(i);return v;}

Try it online!


String t=s.replaceAll("\\d","2")

First replaces each group with an integer between 0 and 3 using some simple regex and stores this in a new String.

int a[]={0,0,0,0},m,i=0,z=0,y=-1>>>1;

Initialises an array of integers as well as a couple of other integers to use later. Sets the y variable to the max int size using unsigned right bit shift.


For each character in the modified string, this uses its ASCII value modulo 4 to calculate the index of the aforementioned array to increment.

for(int x:a){

This then loops through the counts of each group stored in the array and calculates the minimum (y) and the maximum (z).

    v+=a[t.charAt(i)%4]!=(b?z:y)?" ":s.charAt(i);

Loops through every character in the String again, checking if the group of that characters group is equal to the min/max (using the modulo trick mentioned earlier). If it isn't equal, then a space is added to the new String in the characters place, otherwise the original character is added.

return v;

Finally return the new String!


Bash, 229 227 212 bytes

LANG=C;g="A-Z a-z 0-9 !A-Za-z0-9";declare -A H
f()(((i=$2?99:-1));r=$1;for h in $g;{ t=${r//[$h]};t=${#t};(($2?t<i:t>i))&&i=$t&&H=([$h]=1);((t-i))||H[$h]=1;};for h in $g;{((${H[$h]}))||r=${r//[$h]/ };};echo "$r")

Try it Online

  • \$\begingroup\$ I'm not sure how the spaces around brackets and square-blocks work in Bash, but it still seems to work without the space at f(){((. \$\endgroup\$ Oct 31, 2017 at 16:11
  • 1
    \$\begingroup\$ yes, space is mandatory generally except for (, also 2 bytes could be saved using ( instead of {, degrading performance because creating a subshell \$\endgroup\$ Oct 31, 2017 at 17:44

JavaScript (ES6), 139 bytes

s=>b=>s.map(c=>++a[g(c)]&&c,a=[0,0,0,0],g=c=>c>-1?0:/[a-z]/i.test(c)?c<"a"?2:1:3).map(c=>a.map(v=>v-Math[b?"max":"min"](...a))[g(c)]?" ":c)

Input and output is an array of characters. Takes actual boolean values for input.

A different approach from @Neil's answer; almost avoiding regular expressions. Instead, I used a series of checks to determine the category of each character:

  • Digits return true for c>-1 because non-digits fail mathematical comparisons
  • Uppercase letters match the regex /[a-z]/i and have codepoints less than "a"
  • Lowercase letters match that regex but have codepoints not less than "a"
  • Symbols pass none of those tests

Test Cases

s=>b=>s.map(c=>++a[g(c)]&&c,a=[0,0,0,0],g=c=>c>-1?0:/[a-z]/i.test(c)?c<"a"?2:1:3).map(c=>a.map(v=>v-Math[b?"max":"min"](...a))[g(c)]?" ":c)

;[["Just_A_Test!", true],["Just_A_Test!", false],["Aa1!Bb2@Cc3#Dd4$", true],["Aa1!Bb2@Cc3#Dd4$", false],["H@$h!n9_!$_fun?", true],["H@$h!n9_!$_fun?", false],["A", true],["A", false],["H.ngm.n", true],["H.ngm.n", false],["H.ngm4n", false]]
.forEach(([S,B])=>console.log(`"${S}", ${B} -> "${f(S.split``)(B).join``}"`))


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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