18
\$\begingroup\$

In typography, a counter is the area of a letter that is entirely or partially enclosed by a letter form or a symbol. A closed counter is a counter that is entirely enclosed by a letter form or symbol. You must write a program takes a string as input and prints the total number of closed counters in the text.

Your input:

  • May be a command line input, or from STDIN, but you must specify which.

  • Will consist entirely of the printable ASCII characters, meaning all ASCII values between 32 and 126 inclusive. This does include spaces. More information.

Now, this does vary slightly between fonts. For example, the font you're reading this in considers 'g' to have one closed counter, whereas the google font has 'g' with two closed counters. So that this is not an issue, here are the official number of closed counters per character.

All the symbols with no closed counters:

 !"'()*+,-./12357:;<=>?CEFGHIJKLMNSTUVWXYZ[\]^_`cfhijklmnrstuvwxyz{|}~

Note that this includes space.

Here are all the symbols with one closed counter:

#0469@ADOPQRabdegopq

And here are all the symbols with 2 closed counters:

$%&8B

And last but not least, here are some sample inputs and outputs.

Programming Puzzles and Code-Golf should print 13

4 8 15 16 23 42 should print 5

All your base are belong to us should print 12

Standard loopholes apply should print 12

Shortest answer in bytes is the winner! should print 8

\$\endgroup\$
  • 1
    \$\begingroup\$ Two answers have submitted functions instead of full programs. While that is allowed by default, your wording suggests otherwise. Could you clarify? \$\endgroup\$ – Dennis Jun 19 '15 at 22:07
  • \$\begingroup\$ Would you mind disclosing which going you used to count the counters? \$\endgroup\$ – Martin Ender Jun 20 '15 at 13:06
  • 3
    \$\begingroup\$ None of the fonts I'm viewing the question in corresponds to the counts you have given. E.g. in the browser, the zero has a diagonal slash through it, giving two counters. The font in the android app doesn't, but here the g has two closed counters. Did you determine the counters based on any particular font? \$\endgroup\$ – Martin Ender Jun 20 '15 at 14:10
  • 1
    \$\begingroup\$ @DJMcMayhem 'g' has 1; though where listed in code, g has 2. Slightly confusing to read, but I don't think it's different by location. \$\endgroup\$ – OJFord Jun 21 '15 at 21:45
  • 1
    \$\begingroup\$ Doesn't 0 have 2 closed counters in certain fonts, especially many monospace fonts? \$\endgroup\$ – vsz Jun 22 '15 at 6:17

24 Answers 24

10
\$\begingroup\$

Pyth, 31 bytes

sm@tjC"cúÁ-ÈN%³rØ|­"3Cdz

Demonstration.

Note that the code may not be displayed properly due to the use of non-ASCII characters. The correct code is at the link.

I made a lookup table of the output desired for each input character, rotated it by 32 to make use of Pyth's modular indexing, stuck a 1 at the beginning, and interpreted it as a base 3 number, giving the number 2229617581140564569750295263480330834137283757. I then converted this number to base 256 and converted it to a string, which is the string used in the answer.

\$\endgroup\$
29
\$\begingroup\$

Python 3, 63

print(sum(map(input().count,"#0469@ADOPQRabdegopq$%&8B$%&8B")))

A straightforward approach. Iterates over each character with a closed counter, summing the number of occurrences, doing so twice for characters with two closed counters. It would be the same length to instead write

"#0469@ADOPQRabdegopq"+"$%&8B"*2

Python 3 is needed to avoid raw_input.

\$\endgroup\$
12
\$\begingroup\$

CJam, 41 39 37 34 bytes

"$%&8Badopq#0469@Rbeg"_A<eu+qfe=1b

Thanks to @jimmy23013 for golfing off 3 bytes!

Try it online.

How it works

"$%&8Badopq#0469@Rbeg"             e# Push that string.
                      _A<          e# Retrieve the first 10 characters.
                         eu+       e# Convert to uppercase and append.
                                   e# This pushes "$%&8Badopq#0469@Rbeg$%&8BADOPQ".
                            q      e# Read from STDIN.
                             fe=   e# Count the occurrences of each character. 
                                1b e# Base 1 conversion (sum).
\$\endgroup\$
  • 2
    \$\begingroup\$ "$%&8Badopq#0469@Rbeg"_A<eu+. \$\endgroup\$ – jimmy23013 Jun 20 '15 at 6:11
  • \$\begingroup\$ @jimmy23013: I had tried a few variations of eu and el, but I never found that. Thanks! \$\endgroup\$ – Dennis Jun 20 '15 at 6:17
8
\$\begingroup\$

sed, 51

With golfing help from @manatwork and @TobySpeight:

s/[$%&8B]/oo/g
s/[^#0469@ADOPQRabdegopq]//g
s/./1/g

Input from STDIN. With this meta-question in mind, the output is in unary:

$ echo 'Programming Puzzles and Code-Golf
4 8 15 16 23 42
All your base are belong to us
Standard loopholes apply
Shortest answer in bytes is the winner!' | sed -f countercounter.sed
1111111111111
11111
111111111111
111111111111
11111111
$ 
\$\endgroup\$
7
\$\begingroup\$

Perl, 41

$_=y/#0469@ADOPQRabdegopq//+y/$%&8B//*2

41 characters +1 for the -p flag.

This uses y/// to count the characters.

echo 'Programming Puzzles and Code-Golf' | perl -pe'$_=y/#0469@ADOPQRabdegopq//+y/$%&8B//*2'
\$\endgroup\$
6
\$\begingroup\$

GNU APL, 39 bytes

+/⌈20÷⍨26-'$%&8B#0469@ADOPQRabdegopq'⍳⍞

Try it online in GNU APL.js.

How it works

                                      ⍞ Read input.
          '$%&8B#0469@ADOPQRabdegopq'⍳  Compute the indexes of the input characters
                                        in this string. Indexes are 1-based.
                                        26 == 'not found'
       26-                              Subtract each index from 26.
   20÷⍨                                 Divide those differences by 20.
  ⌈                                     Round up to the nearest integer.
+/                                      Add the results.
\$\endgroup\$
6
\$\begingroup\$

JavaScript, 86

I/O via popup. Run the snippet in any drecent browser to test.

for(c of prompt(t=0))c='$%&8B#0469@ADOPQRabdegopq'.indexOf(c),~c?t+=1+(c<5):0;alert(t)

\$\endgroup\$
6
\$\begingroup\$

K, 54 43 42 37 bytes

+//(30#"$%&8B#0469@ADOPQRabdegopq"=)'

Cut off 5 bytes thanks to @JohnE!

Older version:

f:+/(#&"#0469@ADOPQRabdegopq$%&8B$%&8B"=)'

Original:

f:+/{2-(5="$%&8B"?x;20="#0469@ADOPQRabdegopq"?x;0)?0}'
\$\endgroup\$
  • \$\begingroup\$ The #& inside the parens could just as easily be +/, which means you could go further with +//"#0469@ADOPQRabdegopq$%&8B$%&8B"=\:. Finally, it's not necessary to have the f: since the function can be used in tacit form. That would bring you down to 38! \$\endgroup\$ – JohnE Jun 20 '15 at 0:19
  • \$\begingroup\$ Unfortunately the trick a few other solutions have employed to compact the lookup table comes out dead even with the current 38 byte solution: +//(30#"$%&8B#0469@ADOPQRabdegopq")=\:. This may be the best we can do. \$\endgroup\$ – JohnE Jun 20 '15 at 2:46
  • \$\begingroup\$ haha, no sooner did I post that than I saved a character: +//(30#"$%&8B#0469@ADOPQRabdegopq"=)' \$\endgroup\$ – JohnE Jun 20 '15 at 2:51
5
\$\begingroup\$

C, 127 bytes

n;f(char*i){char*o="#0469@ADOPQRabdegopq",*t="$%&8B";for(;*i;i++)if(strchr(o,*i))n++;else if(strchr(t,*i))n+=2;printf("%d",n);}

Pretty straightforward. Ungolfed version:

int num = 0;
void f(char* input)
{
    char *one="#0469@ADOPQRabdegopq";
    char *two="$%&8B";

    for(;*input;input++)
        if(strchr(one, *input))     //If current character is found in first array
            num ++;
        else if(strchr(two, *input))//If cuurent character is found in second array
            num += 2;

    printf("%d", num);
}

Test it here

If function arguments are not allowed, then the stdin version takes up to 141 bytes:

n;f(){char*o="#0469@ADOPQRabdegopq",*t="$%&8B",i[99],*p;gets(i);for(p=i;*p;p++)if(strchr(o,*p))n++;else if(strchr(t,*p))n+=2;printf("%d",n);}

Note that the above version assumes that the input is atmost 98 characters long.

Test it here

Command-line arguments version (143 bytes):

n;main(c,v)char**v;{char*o="#0469@ADOPQRabdegopq",*t="$%&8B",*p=v[1];for(;*p;p++)if(strchr(o,*p))n++;else if(strchr(t,*p))n+=2;printf("%d",n);}

Test it here

\$\endgroup\$
  • 1
    \$\begingroup\$ @DJMcMayhem C is really not that bad. Try golfing in Fortran 77. ;) \$\endgroup\$ – Alex A. Jun 22 '15 at 13:11
5
\$\begingroup\$

Python 2, 96 90 75 67+2 = 69 Bytes

Can't think of any other way to do this... is what I would have thought until I saw xnor's solution. I'll post what I had anyways.

Thanks to FryAmTheEggman for saving 6 bytes

Alright, now I'm happy with this.

Thanks to xnor for the find trick, saving 4 bytes.

Added two bytes since input needs to be enclosed in quotes.

print sum('#0469@ADOPQRabdegopq$%&8B'.find(x)/20+1for x in input())
\$\endgroup\$
  • 1
    \$\begingroup\$ I like the clever use of indexes! Also, python 3's a bit shorter because it uses input instead of raw_input. \$\endgroup\$ – DJMcMayhem Jun 19 '15 at 19:25
  • \$\begingroup\$ @manatwork It works fine in Python 2 with enclosing quotes.. \$\endgroup\$ – Kade Jun 22 '15 at 14:30
  • \$\begingroup\$ Oh, I see. Sorry, I combined it with @DJMcMayhem's Python 3 comment. \$\endgroup\$ – manatwork Jun 22 '15 at 14:31
4
\$\begingroup\$

Java, 162

class C{public static void main(String[]a){System.out.print(-a[0].length()+a[0].replaceAll("[#0469@ADOPQRabdegopq]","..").replaceAll("[$%&8B]","...").length());}}

Well if it has to be a full program... It's just a one-liner that matches characters and replaces them with a longer string. Then, returns the difference in length from the original. Unfortunately, java doesn't really have anything to just count the number of matches.

Here it is with line breaks:

class C{
    public static void main(String[]a){
        System.out.print(
                -a[0].length() +
                a[0].replaceAll("[#0469@ADOPQRabdegopq]","..")
                .replaceAll("[$%&8B]","...")
                .length()
                        );
    }
}
\$\endgroup\$
4
\$\begingroup\$

Pyth - 35 bytes

Uses the obvious method of in first + *2 in second. Thanks @FryTheEggman.

s/Lz+"#0469@ADOPQRabdegopq"*2"$%&8B

Try it here online.

\$\endgroup\$
4
\$\begingroup\$

Javascript, 114 95 bytes

alert(prompt().replace(/[#046@ADOPQRabdegopq]/g,9).replace(/[$%&8B]/g,99).match‌​(/9/g).length)

Thanks to Ismael Miguel for helping me golf this.

\$\endgroup\$
  • 2
    \$\begingroup\$ 93 bytes: alert(prompt().replace(/[#046@ADOPQRabdegopq]/g,9).replace(/[$%&8B]/g,99).match(/9/g).length) \$\endgroup\$ – Ismael Miguel Jun 19 '15 at 23:59
  • \$\begingroup\$ Sorry for counting badly. Yes, its 95. \$\endgroup\$ – Ismael Miguel Jun 20 '15 at 0:34
3
\$\begingroup\$

Ruby, 59 bytes

a=gets;p a.count('#0469@ADOPQRabdegopq')+2*a.count('$%&8B')

Input from command line or stdin. Shortest so far using a non-esoteric language.

Update: chilemagic beat me

\$\endgroup\$
3
\$\begingroup\$

Retina, 44 bytes

1

[#0469@ADOPQRabdegopq]
1
[$%&8B]
11
[^1]
<empty line>

Gives output in unary.

Each line should go to its own file or you can use the -s flag. E.g.:

> echo "pp&cg"|retina -s counter
11111

The pairs of lines (pattern - substitute pairs) do the following substitution steps:

  • Remove 1's
  • Substitute 1-counter letters with 1
  • Substitute 2-counter letters with 11
  • Remove everything but the 1's
\$\endgroup\$
3
\$\begingroup\$

J, 43

As a function:

   f=:[:+/[:,30$'$%&8B#0469@ADOPQRabdegopq'=/]
   f 'text goes here'
6

46 bytes (command line)

As a standalone command-line program:

echo+/,30$'$%&8B#0469@ADOPQRabdegopq'=/>{:ARGV

Save the above line as counter2.ijs and call from the command line:

$ jconsole counter2.ijs 'Programming Puzzles and Code Golf'
13
\$\endgroup\$
  • \$\begingroup\$ Copy-pasting the input into the code isn't allowed but a function which can take the input as an argument is ok. E.g. f=:your_function_code. \$\endgroup\$ – randomra Jun 20 '15 at 7:57
2
\$\begingroup\$

Julia, 77 74 bytes

t=readline();length(join(t∩"#0469@ADOPQRabdegopq")*join(t∩"\$%&8B")^2)

This reads text from STDIN and prints the result to STDOUT.

Ungolfed + explanation:

# Read text from STDIN
t = readline()

# Print (implied) to STDOUT the length of the intersection of t with the
# 1-closed counter list joined with the duplicated intersection of t with
# the 2-closed counter list
length(join(t ∩ "#0469@ADOPQRabdegopq") * join(t ∩ "\$%&8B")^2)

Example:

julia> t=readline();length(join(t∩"#0469@ADOPQRabdegopq")*join(t∩"\$%&8B")^2)
Programming Puzzles and Code Golf
13
\$\endgroup\$
2
\$\begingroup\$

rs, 56 bytes

_/
[#0469@ADOPQRabdegopq]/_
[$%&8B]/__
[^_]/
(_+)/(^^\1)

Live demo.

\$\endgroup\$
  • \$\begingroup\$ Just an fyi: I created a stub esolangs page for rs. You may wish to add to it: esolangs.org/wiki/Rs \$\endgroup\$ – mbomb007 Sep 21 '15 at 20:01
  • \$\begingroup\$ @mbomb007 WOW!! That just made my day. :D \$\endgroup\$ – kirbyfan64sos Sep 21 '15 at 20:10
  • \$\begingroup\$ Well, "rs" doesn't come up in Google or anything since it's only two letters. This way, people can find it. :) \$\endgroup\$ – mbomb007 Sep 21 '15 at 20:12
2
\$\begingroup\$

GNU APL, 37 characters

+/,⍞∘.=30⍴'$%&8B#0469@ADOPQRabdegopq'

construct a character vector which contains 2-counter characters twice (30⍴)

compare each input char with every character in the vector (∘.=)

sum up ravelled matches (+/,)

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

Javascript 159, 130 Bytes

function c(b){return b.split("").map(function(a){return-1!="$%&8B".indexOf(a)?2:-1!="#0469@ADOPQRabdegopq".indexOf(a)?1:0}).reduce(function(a,b){return a+b})};

unminified:

function c(b) {
    return b.split("").map(function(a) {
        return -1 != "$%&8B".indexOf(a) ? 2 : -1 != "#0469@ADOPQRabdegopq".indexOf(a) ? 1 : 0
    }).reduce(function(a, b) {
        return a + b
    })
};

With the help of @edc65:

function c(b){return b.split('').reduce(function(t,a){return t+(~"$%&8B".indexOf(a)?2:~"#0469@ADOPQRabdegopq".indexOf(a)?1:0)},0)}
\$\endgroup\$
  • 2
    \$\begingroup\$ As ~ -1 == 0, you can write ~x? instead of -1 != x?. See me answer for an example of use. \$\endgroup\$ – edc65 Jun 20 '15 at 0:09
  • 2
    \$\begingroup\$ function c(b){return b.split('').reduce(function(t,a){return t+(~"$%&8B".indexOf(a)?2:~"#0469@ADOPQRabdegopq".indexOf(a)?1:0)},0)} No need to have map then reduce \$\endgroup\$ – edc65 Jun 20 '15 at 0:18
1
\$\begingroup\$

Haskell, 117

a="#0469@ADOPQRabdegopq"
b="$%&8B"
c n[]=n
c n(x:s)
 |e a=f 1
 |e b=f 2
 |True=f 0
 where
 e y=x`elem`y
 f y=c(n+y)s

c is a function c :: Int -> String -> Int which takes a counter and a string and goes through the string one letter at a time checking if the current letter is a member of the 1 point array or the 2 point array and calls itself for the rest of the string after incrementing the counter the appropriate amount.

Call with counter = 0 in ghci:

ghci> c 0 "All your base are belong to us"
12
\$\endgroup\$
1
\$\begingroup\$

C#, 157

void Main(string s){int v=0;v=s.ToCharArray().Count(c=>{return "#0469@ADOPQRabdegopq".Contains(c)||"$%&8B".Contains(c)?++v is int:false;});Console.Write(v);}

Ungolfed:

void Main(string s)
{
    int v = 0;

    v = s.ToCharArray()
    .Count(c => 
    {
        return "#0469@ADOPQRabdegopq".Contains(c) || "$%&8B".Contains(c) ? ++v is int:false;
    });

    Console.Write(v);
}

Converting the string into a char array, then seeing if each char is in either counter. If it is in the second one, I just have it incrementing the counter again.

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

Erlang, 103 bytes

This is a complete program that runs using escript. The first line of the file must be blank (adding 1 byte).

main([L])->io:write(c(L,"#0469@ADOPQRabdegopq")+2*c(L,"$%&8B")).
c(L,S)->length([X||X<-L,S--[X]/=S]).

Sample run:

$ escript closed.erl 'Shortest answer in bytes is the winner!'
8$
\$\endgroup\$
  • \$\begingroup\$ Welcome to PPCG, c(L,"#0469@ADOPQRabdegopq")+2*c(L,"$%&8B") is longer than c(L,"#0469@ADOPQRabdegopq$%&8B$%&8B") by 5 bytes :). \$\endgroup\$ – Katenkyo Jun 22 '15 at 8:24
  • \$\begingroup\$ @Katyenko, thanks for the suggestion. Regrettably it doesn't work correctly for certain inputs. "$%&8B" counts for 5, but should be 10. The c/2 function works by filtering out the chars of the string that do not belong to a set of chars, such as "$%&8B". It checks for set inclusion by deleting the char to be tested from the set, then comparing the result with the original set. If they are not equal, then the char was in the set, and it is included. Multiple copies of chars in the set don't have any effect. \$\endgroup\$ – Edwin Fine Jun 22 '15 at 15:40
  • \$\begingroup\$ Ho, I see, I don't know erlang, was thinking you were using a string to count the counter :3. Anyway, nevermind, and well done :) \$\endgroup\$ – Katenkyo Jun 22 '15 at 17:53
0
\$\begingroup\$

C, 99 bytes

n;f(char*i){for(char*o="#0469@ADOPQRabdegopq$%&8B",*t=o+20;*i;)n+=2-!strchr(o,*i)-!strchr(t,*i++);}

Explanation

I further golfed the answer of Cool Guy; it got too long to be a comment. Instead of the if/else, I took advantage of ! converting a pointer to bool. I also made o include t so that I could add "is in o" and "is in t" for the total number of counters.

Expanded code

#include <string.h>
int num = 0;

void f(char* input)
{
    char *one = "#0469@ADOPQRabdegopq"  "$%&8B";
    char *two = strchr(one, '$');

    while (*input) {
        num += 2 - !strchr(one, *input) - !strchr(two, *input));
        ++input;
    }
}

Output is in num, which must be cleared before each call.

Test program and results

#include <stdio.h>
int main() {
    const char* a[] = {
        "Programming Puzzles and Code-Golf",
        "4 8 15 16 23 42",
        "All your base are belong to us",
        "Standard loopholes apply",
        "Shortest answer in bytes is the winner!",
        NULL
    };
    for (const char** p = a;  *p;  ++p) {
        n=0;f(*p);
        printf("%3d: %s\n", n, *p);
    }
    return 0;
}
 13: Programming Puzzles and Code-Golf
  5: 4 8 15 16 23 42
 12: All your base are belong to us
 12: Standard loopholes apply
  8: Shortest answer in bytes is the winner!
 37: n;f(char*i){for(char*o="#0469@ADOPQRabdegopq$%&8B",*t=o+20;*i;)n+=2-!strchr(o,*i)-!strchr(t,*i++);}

The code itself contains 37 counters by its own metric.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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