39
\$\begingroup\$

Your program must take as input a line of characters, like this:

@+.0

And output the characters sorted by how dark they are, like this:

.+0@

Requirements:

  • You must use a monospaced font for darkness detection.

  • You must find out how many pixels each character takes up. You must actually draw the character and count pixels, i.e. you can't just hardcode pixel amounts.

    • As a more concrete rule: if you switched fonts, your program should still work. Furthermore, your program should be able to switch fonts by simply changing a variable or value or string in the code.
  • If you use antialiasing, you must count pixels as percentages of a fully black pixel. For example, an rgb(32, 32, 32) pixel will count as 1/8 of a full pixel. Disregard this rule if your characters are not antialiased.

  • After counting pixels, you must sort the characters by the amount of pixels, and output them in order.

  • This is , so the shortest code in bytes will win.

\$\endgroup\$
3
  • 7
    \$\begingroup\$ Strangely enough, this is also appropriate for RPG.se! \$\endgroup\$
    – corsiKa
    Mar 7, 2014 at 18:12
  • 1
    \$\begingroup\$ What, no correction for display gamma in the darkness calculation? \$\endgroup\$ Mar 7, 2014 at 18:46
  • 6
    \$\begingroup\$ Can we use a white font? If so, I think I have this one wrapped up. \$\endgroup\$
    – Paul
    Mar 7, 2014 at 22:46

14 Answers 14

26
\$\begingroup\$

Bash + ImageMagick: 164 147 148 characters

while read -n1 c
do
o=`convert -size 20x15 xc: +antialias -font cour.ttf -draw "text 0,10 '$c'" xpm:-`
o=${o//[^ ]}
a[${#o}]+=$c
done
echo "${a[@]}"

Sample run:

bash-4.1$ echo -n '@+.0' | bash graysort.sh
. + 0 @

Separators are inserted between grayness groups. Characters with identical grayness level are not separated:

bash-4.1$ echo -n 'abcdefghijklmnopqrstuvwxyz' | bash graysort.sh
i cl jortz esv ax u df bgnpq y hk w m
\$\endgroup\$
1
  • \$\begingroup\$ +1 for sorting using bash array indices instead of sort \$\endgroup\$ Mar 7, 2014 at 16:16
22
\$\begingroup\$

Mathematica, 112 110 108 bytes

This can still likely be golfed further. Assumes the string is in variable s.

And now uses a correct syntax to sort one list by another list.
Lucky test cases -> "Oh yeah, that works" -> Facepalm
Thanks for the sharp eyes, David Carraher.

Update: Replaced OCR A with Menlo because I realized that on OSX the OCR A font family name is actually OCR A Std. So I was sorting a default font instead of the real deal. Menlo is also monospaced with the same byte count, so no net gain or loss.

I've put up a hosted CDF export of the notebook, so you can see the code in action if you wish. I'm still figuring out how to add some interactivity to web-hosted CDFs, so for now it's just static.

c=Characters@s;Last/@Sort[Transpose@{Total[1-#&/@ImageData@Rasterize@Style[#,FontFamily->"Menlo"],3]&/@c,c}]

Output for s = FromCharacterCode /@ Range[33, 135]; with "Courier"

enter image description here

Output for same, but with FontFamily "Monospace":

enter image description here

Note that the final results are shown in MM's internal font, not in the font being sorted. Hence, you see the differences in the font chosen reflected in the sort. The CDF link shows both, though, for the completists.

Ungolfed code:

s = FromCharacterCode /@ Range[33, 135];
c = Characters@s;
Last /@ Sort[
    Transpose@{Total[1 - # & /@ 
        ImageData@Rasterize@Style[#, FontFamily -> "Menlo"], 3] & /@ c, c}]
\$\endgroup\$
10
  • 1
    \$\begingroup\$ Examine for this input: "" <> (FromCharacterCode /@ Range[33, 135]) \$\endgroup\$
    – DavidC
    Mar 7, 2014 at 14:56
  • \$\begingroup\$ Default family or not, the specs required "your program should be able to switch fonts by simply changing a variable". Because of anti-aliasing values, it is possible to get some sorts that look wrong, but I'll have another look at the full range and see if anything is wrong. \$\endgroup\$ Mar 7, 2014 at 15:23
  • 1
    \$\begingroup\$ what a terrific language! just found out about the new programming language effort by Wolfram the other day, looking forward to that. \$\endgroup\$
    – Randy L
    Mar 7, 2014 at 17:15
  • 1
    \$\begingroup\$ You can save two bytes by changing Characters[] to Characters@ and Reverse[] to Reverse@. \$\endgroup\$ Mar 7, 2014 at 17:41
  • 2
    \$\begingroup\$ I don't want to post a similar answer, but I tried myself and it came up shorter StringJoin@SortBy[Characters@"@+.0",ImageData@Binarize@Rasterize@Style[#,FontFamily->"Monospace"]~Total~2&] \$\endgroup\$
    – swish
    Mar 7, 2014 at 19:57
10
\$\begingroup\$

QBasic, 259 bytes

SCREEN 1
DIM a(255)
FOR i = 32 TO 255
    CLS
    PRINT CHR$(i);
    FOR p = 0 TO 64
        a(i) = a(i) + POINT(p MOD 8, p \ 8)
    NEXT p
NEXT i
FOR p = 0 TO 96
    FOR i = 32 TO 255
        IF a(i) = p THEN PRINT CHR$(i);
    NEXT i
NEXT p

I did this for fun, so it's technically non-compliant to the rules in one way. It doesn't take a list of characters, but instead prints all characters from 32-255 and uses that instead. If you really want to see a version which complies with this rule, please tell me.

It also fails another technicality: "Furthermore, your program should be able to switch fonts by simply changing a variable or value or string in the code." There is no easy way to do this from within QBasic. However, the program will of course work fine with any codepage of your choosing.

Lastly, I could squeeze away a few characters (mostly whitespace that the QBasic IDE helpfully inserts,) but it's probably not worth it since this answer stands no chance of winning anyway.

QBasic sort characters by darkness

\$\endgroup\$
1
  • \$\begingroup\$ Plus one for doing it for fun! \$\endgroup\$ Mar 7, 2014 at 23:56
9
\$\begingroup\$

Javascript + Canvas and Browser DOM (280 237 235 bytes)

Updated version with suggetions from Fors and toothbrush in comments:

function m(x){a=document.createElement('canvas').getContext('2d');a.font='9px Monaco';a.fillText(x,y=i=0,20);for(;i<3600;)y+=a.getImageData(0,0,30,30).data[i++];return y}alert(s.split('').sort(function(a,b){return m(a)-m(b)}).join(''))

More readable version:

// Scoring function - Calculates darkness for single character
function m(x) {
    a = document.createElement('canvas').getContext('2d');
    a.font = '9px Monaco';
    a.fillText(x, y = i = 0, 20);
    for (; i < 3600;) y += a.getImageData(0, 0, 30, 30).data[i++];
    return y
}
// Assume input is in variable s and alert as output. Comparison function now expression.
alert(s.split('').sort(function (a, b) {
    return m(a) - m(b)
}).join(''))

Can maybe be golfed more.

I'm new to this site, so I am unsure how input is normally read for Javascript answers. I assume input is contained in a variable named s. If this is not OK, I will update the answer and the char count.

JSFiddle of updated version.

JSFiddle of first version.

\$\endgroup\$
5
  • \$\begingroup\$ I really like it, however there are many possible improvements here. The variables c and s are unnecessary (a=document.createElement('canvas').getContext('2d') and y+=a.getImageData(0,0,30,30).data[i]), the element doesn't need to be appended, the zero initialisation of y can be combined with that of i (i=y=0), the post-increment of i can be combined with the adding of y (for(...;y+=...[i++]);), and the typeface Monaco is mono-spaced and has a shorter name than Courier. \$\endgroup\$
    – Fors
    Mar 7, 2014 at 19:04
  • \$\begingroup\$ Thanks! I'm not a seasoned golfer yet, so your comments help a lot. I have incorporated them into the code now. \$\endgroup\$
    – waxwing
    Mar 8, 2014 at 7:41
  • \$\begingroup\$ Javascript answers here normally read input through prompt(); but this is also fine. \$\endgroup\$
    – Kartik
    Mar 8, 2014 at 12:48
  • \$\begingroup\$ You can remove y=i=0; and change a.fillText(x,0,20) to a.fillText(x,y=i=0,20). \$\endgroup\$
    – Toothbrush
    Mar 9, 2014 at 1:06
  • \$\begingroup\$ Thanks, added that as well! Two characters are two characters! \$\endgroup\$
    – waxwing
    Mar 13, 2014 at 20:10
3
\$\begingroup\$

PHP, 298 characters

I've added a few line breaks so you can see it in all its hideousness:

<?php
$s=@$_GET[s];$a=array();$v=imagecreate(16,16);$f='imagecolorallocate';
$f($v,0,0,0);for($i=0;$i<strlen($s);$i++){$c=$f($v,$i,0,1);
imagechar($v,5,2,$n=0,$s[$i],$c);for($y=16;$y--;)
for($x=16;$x--;)$n+=($c==imagecolorat($v,$x,$y));
$a[]=ord($s[$i])+($n<<8);}sort($a);foreach($a as $v)echo chr($v);

This code uses the GD fonts that come built-in with PHP. The second argument of imagechar() selects the font (numbers from 1 to 5 are valid).

Example:

Input:  !@#$%^&*-=WEIX,./'
Output: '-.,^=!/*IE%X#$&@W

If you insert the following on top of the code shown above, then you'll be able to supply the list of characters in your web browser.

<?php
define("FONT_SIZE",5);
if(@$_SERVER['PATH_INFO']=='/a.png') {
  $s = $_GET['s'];
  $im = imagecreate(strlen($s)*(FONT_SIZE+4)+4,FONT_SIZE+12);
  imagecolorallocate($im,255,255,128);
  $c = imagecolorallocate($im,0,0,0);
  imagestring($im,FONT_SIZE,2,0,$s,$c);
  header("Content-Type: image/png");
  imagepng($im);
  imagedestroy($im);
  exit();
}
$me = $_SERVER['PHP_SELF'];
$t1 = $img = "";
if ($t1=htmlspecialchars(@$_GET['s'])) {
  $t2=urlencode($_GET['s']);
  $img="<p><img src=\"$me/a.png?s=$t2\" /></p>";
}
echo <<<END_HTML
<html>
<body>
$img
<form action="$me" method="get">
<input type="text" name="s" size="40" value="$t1" />
<input type="submit" value="Go" />
</form>
END_HTML;
if(!isset($_GET['s'])) exit();
?>
\$\endgroup\$
1
  • 1
    \$\begingroup\$ if you use imagecreatetruecolor you can drop the first allocate and use the function name directly on the second, for -11. [] instead of array(). and foreach($a as$v) works as well \$\endgroup\$
    – Einacio
    Mar 7, 2014 at 16:38
3
\$\begingroup\$

GTB

This has code to be the second hardest code I've ever written for a calculator. No hard-coded pixel values, it actually draws the text on a graph and loops to count each pixel.

0→I`_%I<l?_T;1,1,s;_,I,1
C;pT;{0,1,2,3,4,5},{0,1,2}→L1(I
0→I%I<l?_T;1,C;L1(I)>L1,I

Input

,O.i

Output

.,iO
\$\endgroup\$
5
  • \$\begingroup\$ If this works, it is the best answer so far. Whatever is GTB? \$\endgroup\$
    – slater
    Mar 8, 2014 at 17:52
  • \$\begingroup\$ @slater Why don't you click on the link and find out? \$\endgroup\$
    – Timtech
    Mar 8, 2014 at 18:00
  • \$\begingroup\$ Thank you for pointing out that your post title is indeed a link. \$\endgroup\$
    – slater
    Mar 8, 2014 at 18:07
  • 2
    \$\begingroup\$ The domain to get the software necessary to decrypt your compiler is dead. Dead here too. I translated the code by hand for the fun of testing it out, but you seem to have opened 10 parens and closed only one, so I am unsure how to resolve that. The compiler in my head says: "Error::unmatched identifier" :) \$\endgroup\$ Mar 9, 2014 at 3:06
  • 1
    \$\begingroup\$ @JonathanVanMatre Don't worry; TI-84 automatically closes them for you. \$\endgroup\$
    – Timtech
    Mar 13, 2014 at 21:00
3
\$\begingroup\$

Java - 468 450 444

public static void main(String[]a){class c implements Comparable<c>{char d;c(char e){d=e;}public int compareTo(c o){return e(d)>e(o.d)?1:-1;}int e(char f){int a=0,x,y;BufferedImage img=new BufferedImage(99,99,1);img.getGraphics().drawString(""+f,9,80);for(y=0;y<99;y++)for(x=0;x<99;x++)a+=img.getRGB(x,y);return a;}}c[]s=new c[a[0].length()];int i=0;for(char d:a[0].toCharArray())s[i++]=new c(d);Arrays.sort(s);for(c d:s)System.out.print(d.d);}

@+.0abcdefghijklmnopqrstuvwxyz -> .irl+jcvtfxyzsuonkheaqpdb0wgm@

Ungolfed:

    public static void main(String[] a) {
    a = new String[]{"@+.0abcdefghijklmnopqrstuvwxyz"};
    class c implements Comparable<c> {
        char    d;

        c(char e) {
            d = e;
        }

        @Override
        public int compareTo(c o) {
            return e(d) > e(o.d)? 1 : -1;
        }

        int e(char f) {
            int a = 0, x, y;
            BufferedImage img = new BufferedImage(99, 99, 1);
            img.getGraphics().drawString("" + f, 9, 80);
            for (y = 0; y < 99; y++)
                for (x = 0; x < 99; x++)
                    a += img.getRGB(x, y);
            return a;
        }
    }
    c[] s = new c[a[0].length()];
    int i = 0;
    for (char d : a[0].toCharArray())
        s[i++] = new c(d);
    Arrays.sort(s);
    for (c d : s)
        System.out.print(d.d);
}
\$\endgroup\$
3
  • \$\begingroup\$ Tip: avoid public or private modifiers as far as possible; that saves unnecessary bytes \$\endgroup\$
    – masterX244
    Mar 8, 2014 at 20:41
  • \$\begingroup\$ forgot all about that \$\endgroup\$ Mar 9, 2014 at 11:49
  • \$\begingroup\$ @MarkJeronimus this was the way I was going t otackle it, but I wanted to try using FontRenderingContext. \$\endgroup\$ Jan 24, 2017 at 20:44
3
\$\begingroup\$

Postscript, 381

Here's something completely different, just for fun. As most fonts are vector anyway, 'counting pixels' is a little odd, isn't it. Calculating glyph shape area, while being correct way, is not that easy. An alternative can be scanning a rectangle and counting 'hits' when a point is inside a glyph shape, and Postscript has operators for this kind of checks. Though, true, scanning and insideness-testing is just a weird way of counting pixels.

(%stdin)(r)file token pop/Courier 99 selectfont[1 index length{0}repeat]0 1 99{0 1 99{0 1 5 index length 1 sub{newpath 9 19 moveto 3 copy 7 index exch 1 getinterval false charpath infill{3 index exch 2 copy get 1 add put}{pop}ifelse}for pop}for pop}for 0 1 99 dup mul{0 1 3 index length 1 sub{dup 3 index exch get 2 index eq{3 index exch 1 getinterval print}{pop}ifelse}for pop}for

.

(%stdin) (r) file token pop
/Courier 99 selectfont
%/DejaVuSansMono 99 selectfont
%/UbuntuMono-Regular 99 selectfont
[ 1 index length {0} repeat ]   % str []
0 1 99 {
    0 1 99 {
        0 1 5 index length 1 sub {
            newpath 
            9 19 moveto
            3 copy              % str [] n m i n m i
            7 index exch        % str [] n m i n m str i
            1 getinterval       % str [] n m i n m s
            false charpath      % str [] n m i n m
            infill              % str [] n m i bool
            {3 index exch 2 copy get 1 add put} {pop} ifelse
        } for
        pop
    } for
    pop
} for
% un-comment next line to print number of 'hits' for each glyph
%
% dup {=} forall
%
% next is 'lazy sort'
0 1 99 dup mul {                % str [] i
    0 1 3 index length 1 sub {  % str [] i j
        dup 3 index exch        % str [] i j [] j
        get 2 index eq          % str [] i j bool
        {3 index exch 1 getinterval print} {pop} ifelse
    } for
    pop
} for
()=

And here are results for 3 different fonts (selection of which can be un-commented, above):

$ echo '(.-?@AByz01)' | gs -q -dBATCH d.ps
.-?1z0yA@B
$ echo '(.-?@AByz01)' | gs -q -dBATCH d.ps
.-?z1yA0B@
$ echo '(.-?@AByz01)' | gs -q -dBATCH d.ps
.-?1zyA0B@
\$\endgroup\$
1
  • \$\begingroup\$ Counting pixels...counting pixel intersections...tomato....tomahto.... \$\endgroup\$ Mar 10, 2014 at 14:13
2
\$\begingroup\$

Perl (with GD) (159)

use GD;sub i{$i=new GD'Image 5,8;$B=colorExact$i 9,9,9;colorExact$i 0,0,0;char$i gdTinyFont,0,0,@_,1;$_=unpack"B*",wbmp$i 0;y/0//c}print+sort{i($a)-i($b)}@ARGV

usage:

> perl dark.pl 1 2 3 @ # . , : ~ $ M i I s S
.,~:i13Is2S$M@#

edit: shortened to 159 chars

\$\endgroup\$
2
\$\begingroup\$

Java, 584

Wow... This was not a good language to do this in.

import java.awt.geom.*;import java.util.*;class F{static void n(final String f,List<Character> s){Collections.sort(s,new Comparator<Character>(){public int compare(Character a,Character b){return d(f,""+a) - d(f,""+b);}});}static int d(String f,String s){int i=0;PathIterator p=new java.awt.Font(f,0,12).createGlyphVector(((java.awt.Graphics2D)new java.awt.image.BufferedImage(8,8,2).getGraphics()).getFontRenderContext(),s).getGlyphOutline(0).getPathIterator(AffineTransform.getRotateInstance(0.0, 0.0));while(!p.isDone()){i+=p.currentSegment(new double[99])/2;p.next();}return i;}}

Usage:

import java.awt.geom.*;
import java.util.*;
public class F {
    public static void main(String[]args){
        List<Character> s = new ArrayList<Character>(0);
        s.add('@');
        s.add('+');
        s.add('.');
        s.add('0');
        n("Calibri", s);
        System.out.println(s);
    }
    static void n(final String f,List<Character> s){
        Collections.sort(s,new Comparator<Character>(){
            public int compare(Character a,Character b){
                return d(f,""+a) - d(f,""+b);
            }
        });
    }
    
    static int d(String f,String s){
        int i=0;
        PathIterator p=new java.awt.Font(f,0,12).createGlyphVector(((java.awt.Graphics2D)new java.awt.image.BufferedImage(8,8,2).getGraphics()).getFontRenderContext(),s).getGlyphOutline(0).getPathIterator(AffineTransform.getRotateInstance(0.0, 0.0));
        while(!p.isDone()){
            i+=p.currentSegment(new double[99])/2;
            p.next();
        }
        return i;
    }
}

This setup results in:

[., +, 0, @]

The only line here that needs explanation:

PathIterator p=new java.awt.Font(f,0,12).createGlyphVector(((java.awt.Graphics2D)new java.awt.image.BufferedImage(8,8,2).getGraphics()).getFontRenderContext(),s).getGlyphOutline(0).getPathIterator(AffineTransform.getRotateInstance(0.0, 0.0));
  • Initialize the 12pt font object with the passed font.
  • Create a new BufferedImage obejct to create a Graphics2D object linked to a GraphicsContext.
  • Get the font rendering context of the 2D graphics context for the string s.
  • Get the first glyph (only glyph) in the string.
  • Get the path iterator (list of points).

Then this final piece brings it together...

while(!p.isDone()){
    i+=p.currentSegment(new double[99])/2;
    p.next();
}

By iterating through all points and summing count of points. This density information is passed back up to the comparator and is used for sorting.

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

R, 195 characters

A=strsplit(scan(,""),"")[[1]];cat(A[order(sapply(A,function(x){png('a',a='none',fa='monospace');frame();text(0,0,x);dev.off();sum(apply(png::readPNG('a'),c(1,2),function(x)any(x!=1)))}))],sep="")

Indented with comments:

A=strsplit(scan(,""),"")[[1]] #Take characters as strings and split into single chars
cat(A[order(sapply(A,function(x){ #Apply the following function to each char and order accordingly
                 png('a',a='none',fa='monospace'); #Open empty png without antialiasing and with monospace font
                 frame(); #create empty plot
                 text(0,0,x); #add the char as text to the plot
                 dev.off(); #close png device
                 sum(apply(png::readPNG('a'), #read it back as rbga 3d matrix
                           c(1,2), #check every layer (R, G, B, A)
                           function(x)any(x!=1))) #if any are not 1, send TRUE
                 }))], #Sum all TRUEs
    sep="") #Prints to output

Example:

> A=strsplit(scan(,""),"")[[1]];cat(A[order(sapply(A,function(x){png('a',a='none',fa='monospace');frame();text(0,0,x);dev.off();sum(apply(png::readPNG('a'),c(1,2),function(x)any(x!=1)))}))],sep="")
1: @+.0
2: 
Read 1 item
.+0@
> A=strsplit(scan(,""),"")[[1]];cat(A[order(sapply(A,function(x){png('a',a='none',fa='monospace');frame();text(0,0,x);dev.off();sum(apply(png::readPNG('a'),c(1,2),function(x)any(x!=1)))}))],sep="")
1: 1234567890
2: 
Read 1 item
1723450689

The gestion of fonts in R plots being platform-dependent, I cannot guarantee that it works on PC, but it does on a Mac (OS X 10.7.5, R 2.14.2).

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

SmileBASIC, 179 176 173 bytes

INPUT S$DIM Q$[0],A[0],Z[0]WHILE""<S$C$=POP(S$)GCLS
PUSH Q$,C$
GPUTCHR.,0,C$
GSAVE.,0,8,8,A,0S=0FOR I=0TO 63S=S+A[I]NEXT
PUSH Z,S
WEND
RSORT Z,Q$
WHILE LEN(Q$)?POP(Q$);
WEND

Uses the currently loaded font. Fonts can be loaded with LOAD"GRPF:filename".

More readable code:

INPUT STRING$
DIM CHARS$[0],PIXELS[0],SIZES[0]
WHILE STRING$>""
 CHAR$=POP(STRING$)
 PUSH CHARS$,CHAR$
 GCLS
 GPUTCHR 0,0,CHAR$
 GSAVE 0,0,8,8,PIXELS
 SIZE=0
 FOR I=0 TO 63
  INC SIZE,PIXELS[I]
 NEXT
 PUSH SIZES,SIZE
WEND
RSORT SIZES,CHARS$
WHILE LEN(CHARS$)
 PRINT POP(CHARS$);
WEND
\$\endgroup\$
0
\$\begingroup\$

PHP - 485

Demo:

$ php pcg-23362.php "@+.0"
.+0@

Code:

<?php $f='x.ttf';$d=array();foreach(str_split($argv[1]) as$_){$B=imagettfbbox(50,0,$f,$_);$w=abs($B[4]-$B[0]);$h=abs($B[5]-$B[1]);$im=imagecreate($w,$h);imagecolorallocate($im,255,255,255);imagettftext($im,50,0,0,$h-$B[1],imagecolorallocate($im,0,0,0),$f,$_);$b=$w*$h;for($x=0;$x<$w;$x++)for($y=0;$y<$h;$y++){$z=imagecolorsforindex($im,imagecolorat($im,$x,$y));$color=$z['red']*$z['green']*$z['blue'];$b-=$color/0x1000000;}$d[$_]=$b / ($w * $h);}asort($d);echo implode(array_keys($d));
\$\endgroup\$
1
  • \$\begingroup\$ Hey, send me a copy of X.TTF...the shortest font I have is OCR A. ;-D \$\endgroup\$ Mar 9, 2014 at 3:08
0
\$\begingroup\$

Python + freetype-py: 147

import sys,freetype as F;f=F.Face('m.ttf');f.set_char_size(99);print(sorted([(f.load_char(c)or sum(f.glyph.bitmap.buffer),c)for c in raw_input()]))
\$\endgroup\$

Your Answer

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

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