Stem-and-leaf plot

From Rosetta Code
Task
Stem-and-leaf plot
You are encouraged to solve this task according to the task description, using any language you may know.

Create a well-formatted stem-and-leaf plot from the following data set, where the leaves are the last digits:

12 127 28 42 39 113 42 18 44 118 44 37 113 124 37 48 127 36 29 31 125 139 131 115 105 132 104 123 35 113 122 42 117 119 58 109 23 105 63 27 44 105 99 41 128 121 116 125 32 61 37 127 29 113 121 58 114 126 53 114 96 25 109 7 31 141 46 13 27 43 117 116 27 7 68 40 31 115 124 42 128 52 71 118 117 38 27 106 33 117 116 111 40 119 47 105 57 122 109 124 115 43 120 43 27 27 18 28 48 125 107 114 34 133 45 120 30 127 31 116 146

The primary intent of this task is the presentation of information. It is acceptable to hardcode the data set or characteristics of it (such as what the stems are) in the example, insofar as it is impractical to make the example generic to any data set. For example, in a computation-less language like HTML the data set may be entirely prearranged within the example; the interesting characteristics are how the proper visual formatting is arranged.

If possible, the output should not be a bitmap image. Monospaced plain text is acceptable, but do better if you can. It may be a window, i.e. not a file.

Note: If you wish to try multiple data sets, you might try this generator.

Ada

GNAT used for sorting, could use any other sorting method. Does not handle negative stems properly. <lang Ada> with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; with Gnat.Heap_Sort_G; procedure stemleaf is data : array(Natural Range <>) of Integer := ( 0,12,127,28,42,39,113, 42,18,44,118,44,37,113,124,37,48,127,36,29,31, 125,139,131,115,105,132,104,123,35,113,122,42,117,119,58,109,23,105, 63,27,44,105,99,41,128,121,116,125,32,61,37,127,29,113,121,58,114,126, 53,114,96,25,109,7,31,141,46,13,27,43,117,116,27,7,68,40,31,115,124,42, 128,52,71,118,117,38,27,106,33,117,116,111,40,119,47,105,57,122,109, 124,115,43,120,43,27,27,18,28,48,125,107,114,34,133,45,120, 30,127, 31,116,146); -- Position 0 is used for storage during sorting, initialized as 0

procedure Move (from, to : in Natural) is begin data(to) := data(from); end Move;

function Cmp (p1, p2 : Natural) return Boolean is begin return data(p1)<data(p2); end Cmp;

package Sorty is new GNAT.Heap_Sort_G(Move,Cmp); min,max,p,stemw: Integer; begin Sorty.Sort(data'Last); min := data(1); max := data(data'Last); stemw := Integer'Image(max)'Length; p := 1; for stem in min/10..max/10 loop put(stem,Width=>stemw); put(" |"); Leaf_Loop: while data(p)/10=stem loop put(" "); put(data(p) mod 10,Width=>1); exit Leaf_loop when p=data'Last; p := p+1; end loop Leaf_Loop; new_line; end loop; end stemleaf; </lang> Output:

   0 | 7 7
   1 | 2 3 8 8
   2 | 3 5 7 7 7 7 7 7 8 8 9 9
   3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9
   4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8
   5 | 2 3 7 8 8
   6 | 1 3 8
   7 | 1
   8 |
   9 | 6 9
  10 | 4 5 5 5 5 6 7 9 9 9
  11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9
  12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8
  13 | 1 2 3 9
  14 | 1 6

C

<lang c>#include <stdio.h>

  1. include <stdlib.h>

int dataset[] = {

    12,127, 28, 42, 39,113, 42, 18, 44,118, 44, 37,113,124, 37, 48,127, 36,
    29, 31,125,139,131,115,105,132,104,123, 35,113,122, 42,117,119, 58,109,
    23,105, 63, 27, 44,105, 99, 41,128,121,116,125, 32, 61, 37,127, 29,113,
   121, 58,114,126, 53,114, 96, 25,109,  7, 31,141, 46, 13, 27, 43,117,116,
    27,  7, 68, 40, 31,115,124, 42,128, 52, 71,118,117, 38, 27,106, 33,117,
   116,111, 40,119, 47,105, 57,122,109,124,115, 43,120, 43, 27, 27, 18, 28,
    48,125,107,114, 34,133, 45,120, 30,127, 31,116,146

};

  1. define DATA_SIZE (sizeof(dataset)/sizeof(dataset[0]))

typedef struct valueNodeStruct *ValueNode;

struct valueNodeStruct {

   int value;
   ValueNode left, right;

};

FILE *fout;

void DspyNodeList( ValueNode node, const char *fmat) {

   if (NULL==node) return;
   DspyNodeList(node->left,fmat);
   fprintf(fout, fmat, node->value);
   DspyNodeList(node->right,fmat);

}

void doStemLeaf( int *dataset, int dssize, int dp) {

   int ix;
   int maxd, mind, ngroups, x;
   int divsr = 1;
   ValueNode * groups;
   char fmat[16];
   sprintf(fmat," %%%dd", dp );
   mind = maxd= dataset[0];
   for (ix=1; ix<dssize; ix++) {
       if (maxd < dataset[ix]) maxd = dataset[ix];
       if (mind > dataset[ix]) mind = dataset[ix];
   }
   x =(mind < 0) ? -1 : 0;
   for (ix=dp; ix; ix--) {
       maxd /= 10;
       mind /= 10;
       divsr *= 10;
   }
   mind += x;
   ngroups =  maxd - mind + 1;
   groups = calloc(ngroups , sizeof(ValueNode));
   for (ix=0; ix<dssize; ix++) {
       int stemval, leafval;
       ValueNode  node, *np;
       stemval = dataset[ix]/divsr;
       leafval = dataset[ix]%divsr;
       if (leafval < 0) { leafval += divsr; stemval -=1; }  // fix broken % & / operators
       node = malloc(sizeof(struct valueNodeStruct));
       node->value = leafval;
       node->left = node->right = NULL;
       for (np = &groups[stemval-mind]; (*np);
            np = (*np)->value < leafval? &((*np)->right) : &((*np)->left) );
       *np = node;
   }
   for (ix=0; ix<ngroups; ix++) {
       fprintf(fout,"\n%3d |", ix+mind);
       DspyNodeList( groups[ix], fmat );
   }
   fprintf(fout,"\n\n");

}


int main() {

   fout = fopen("stemleaf.out", "w");

// fout = stdout;

   doStemLeaf(dataset, DATA_SIZE, 1);

// doStemLeaf(dataset, DATA_SIZE, 2);

   fclose(fout);
   return 0;

}


</lang> Output:

  0 | 7 7
  1 | 2 3 8 8
  2 | 3 5 7 7 7 7 7 7 8 8 9 9
  3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9
  4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8
  5 | 2 3 7 8 8
  6 | 1 3 8
  7 | 1
  8 |
  9 | 6 9
 10 | 4 5 5 5 5 6 7 9 9 9
 11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9
 12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8
 13 | 1 2 3 9
 14 | 1 6

D

<lang d>import std.stdio;

uint[] dataset = [ 111, 107, 100, 107, 115, 111, 97, 112, 104, 106, 113, 109, 113, 128, 128, 118, 113, 124, 127, 136, 106,123, 124,126, 116, 127, 119, 97, 102, 110, 120, 103, 115, 93, 123, 79, 119, 110, 110, 107, 105, 105, 110, 77, 90, 114, 106];

uint[][] plot; void main() {

   uint stem;
   foreach(number; dataset)
   {
       stem = number / 10;
       if(plot.length <= stem)
           plot.length = stem + 1; // could use an associative array, but this handles the blank stems for us
       plot[stem] ~= number - stem * 10; // this discards all but the last digit
   }
   foreach(i, row; plot)
   {
       writef("%2d |", i);
       foreach(leaf; row.sort)
           writef(" %d", leaf);
       writefln();
   }

}</lang>

Output:

 0 | 7 7
 1 | 2 3 8 8
 2 | 3 5 7 7 7 7 7 7 8 8 9 9
 3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9
 4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8
 5 | 2 3 7 8 8
 6 | 1 3 8
 7 | 1
 8 |
 9 | 6 9
10 | 4 5 5 5 5 6 7 9 9 9
11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9
12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8
13 | 1 2 3 9
14 | 1 6

Forth

<lang forth>create data

  12 , 127 ,  28 ,  42 ,  39 , 113 ,  42 ,  18 ,  44 , 118 ,  44 ,
  37 , 113 , 124 ,  37 ,  48 , 127 ,  36 ,  29 ,  31 , 125 , 139 ,
 131 , 115 , 105 , 132 , 104 , 123 ,  35 , 113 , 122 ,  42 , 117 ,
 119 ,  58 , 109 ,  23 , 105 ,  63 ,  27 ,  44 , 105 ,  99 ,  41 ,
 128 , 121 , 116 , 125 ,  32 ,  61 ,  37 , 127 ,  29 , 113 , 121 ,
  58 , 114 , 126 ,  53 , 114 ,  96 ,  25 , 109 ,   7 ,  31 , 141 ,
  46 ,  13 ,  27 ,  43 , 117 , 116 ,  27 ,   7 ,  68 ,  40 ,  31 ,
 115 , 124 ,  42 , 128 ,  52 ,  71 , 118 , 117 ,  38 ,  27 , 106 ,
  33 , 117 , 116 , 111 ,  40 , 119 ,  47 , 105 ,  57 , 122 , 109 ,
 124 , 115 ,  43 , 120 ,  43 ,  27 ,  27 ,  18 ,  28 ,  48 , 125 ,
 107 , 114 ,  34 , 133 ,  45 , 120 ,  30 , 127 ,  31 , 116 , 146 ,

here constant data-end

sort ( end start -- )
 over cell - swap do
   dup i cell+ do
     i @ j @ < if
       i @ j @ i ! j !
     then
   cell +loop
 cell +loop drop ;
plot
 data-end data sort
 data
 data-end cell - @ 10 / 1+
            data @ 10 /
 do
   cr i 2 u.r ."  | "
   begin dup @ 10 /mod i = while  .  cell+  dup data-end = until else drop then
 loop
 drop ;

plot</lang> Output:

 0 | 7 7 
 1 | 2 3 8 8 
 2 | 3 5 7 7 7 7 7 7 8 8 9 9 
 3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9 
 4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8 
 5 | 2 3 7 8 8 
 6 | 1 3 8 
 7 | 1 
 8 | 
 9 | 6 9 
10 | 4 5 5 5 5 6 7 9 9 9 
11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9 
12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8 
13 | 1 2 3 9 
14 | 1 6 

Haskell

<lang haskell>import Data.List import Control.Arrow import Control.Monad

nlsRaw = "12 127 28 42 39 113 42 18 44 118 44 37 113 124 37 48 127 36 29 31"

 ++ " 125 139 131 115 105 132 104 123 35 113 122 42 117 119 58 109 23 105 63"
 ++ " 27 44 105 99 41 128 121 116 125 32 61 37 127 29 113 121 58 114 126 53"
 ++ " 114 96 25 109 7 31 141 46 13 27 43 117 116 27 7 68 40 31 115 124 42 128"
 ++ " 52 71 118 117 38 27 106 33 117 116 111 40 119 47 105 57 122 109 124 115"
 ++ " 43 120 43 27 27 18 28 48 125 107 114 34 133 45 120 30 127 31 116 146"

nls :: [Int] nls = map read $ words nlsRaw

groupWith f = takeWhile(not.null). unfoldr(Just. (partition =<< (. f). (==). f. head)) justifyR = foldl ((. return) . (++) . tail) . flip replicate ' '

task ds = mapM_ (putStrLn. showStemLeaves justifyR fb. (head *** sort.concat). unzip)

   $ groupWith fst $ stems ++ map (second return) stemLeaf
 where stemLeaf = map (`quotRem` 10) ds

stems = map (flip(,)[]) $ uncurry enumFromTo $ minimum &&& maximum $ fst $ unzip stemLeaf showStemLeaves f w (a,b) = f w (show a) ++ " |" ++ concatMap (f w. show) b fb = length $ show $ maximum $ map abs ds</lang> Output:

*Main> task  nls
  0 |  7  7
  1 |  2  3  8  8
  2 |  3  5  7  7  7  7  7  7  8  8  9  9
  3 |  0  1  1  1  1  2  3  4  5  6  7  7  7  8  9
  4 |  0  0  1  2  2  2  2  3  3  3  4  4  4  5  6  7  8  8
  5 |  2  3  7  8  8
  6 |  1  3  8
  7 |  1
  8 |
  9 |  6  9
 10 |  4  5  5  5  5  6  7  9  9  9
 11 |  1  3  3  3  3  4  4  4  5  5  5  6  6  6  6  7  7  7  7  8  8  9  9
 12 |  0  0  1  1  2  2  3  4  4  4  5  5  5  6  7  7  7  7  8  8
 13 |  1  2  3  9
 14 |  1  6

HicEst

The dialog prompts for bitmap or a text image, and for the stem base. Data are read in from clipboard. <lang HicEst>REAL :: workspace(1000), base=16

DLG(CHeckbox=bitmap, NameEdit=base, DNum, MIn=1, MAx=16) ! 1 <= stem base <= 16 READ(ClipBoard, ItemS=nData) workspace  ! get raw data

ALIAS(workspace,1, dataset,nData, stems,nData) SORT(Vector=dataset, Sorted=dataset) stems = (dataset - MOD(dataset,base)) / base dataset = dataset - base*stems max_stem = MAX(stems)

IF( bitmap ) AXIS() printed = 0 DO stem = 0, max_stem

 last = INDEX(stems, stem, 4) ! option 4: search backward
 IF( last > printed ) THEN
     nLeaves = last - printed
     IF(bitmap) THEN
       LINE(PenUp=1,W=8, x=0, y=stem, x=nLeaves, y=stem)
     ELSE
       ALIAS(dataset,printed+1,  leaves,nLeaves)
       WRITE(Format="i3, ':', 100Z2") stem, leaves
     ENDIF
     printed = printed + nLeaves
   ELSE
     WRITE(Format="i3, ':'") stem
   ENDIF

ENDDO</lang> Shown is the given example for bitmap=0 and base 16

  0 : 7 7 C D
  1 : 2 2 7 9 B B B B B B C C D D E F F F F
  2 : 0 1 2 3 4 5 5 5 6 7 8 8 9 A A A A B B B C C C D E F
  3 : 0 0 4 5 9 A A D F
  4 : 4 7
  5 :
  6 : 0 3 8 9 9 9 9 A B D D D F
  7 : 1 1 1 1 2 2 2 3 3 3 4 4 4 4 5 5 5 5 6 6 7 7 8 8 9 9 A A B C C C D D D E F F F F
  8 : 0 0 3 4 5 B D
  9 : 2

J

Solution: (Tacit) <lang j>stem =: <.@(%&10) leaf =: 10&| stemleaf =: (stem@{. ; leaf)/.~ stem expandStems =: <./ ([ + i.@>:@-~) >./ expandLeaves=: (expandStems e. ])@[ #inv ]

showStemLeaf=: (":@,.@expandStems@[ ; ":&>@expandLeaves)&>/@(>@{. ; <@{:)@|:@stemleaf@/:~</lang>

Solution: (Explicit) <lang j>stemleafX=: monad define

 leaves=. 10 | y
 stems=. y <.@:% 10
 leaves=. stems </. leaves                           NB. group leaves by stem
 (<"0 ~.stems),.leaves

)

showStemLeafX=: monad define

 'stems leaves'=. (>@{. ; <@{:)@|: stemleafX /:~ y
 xstems=. (<./ ([ + i.@>:@-~ ) >./) stems            NB. stems including those with no leaves
 xleaves=. (xstems e. stems) #inv leaves             NB. expand leaves to match xstems
 (": ,.xstems) ; ":&> xleaves

)</lang>

Example: <lang j> nls =: ; <@(_&".);._2 noun define 12 127 28 42 39 113 42 18 44 118 44 37 113 124 37 48 127 36 29 31 125 139 131 115 105 132 104 123 35 113 122 42 117 119 58 109 23 105 63 27 44 105 99 41 128 121 116 125 32 61 37 127 29 113 121 58 114 126 53 114 96 25 109 7 31 141 46 13 27 43 117 116 27 7 68 40 31 115 124 42 128 52 71 118 117 38 27 106 33 117 116 111 40 119 47 105 57 122 109 124 115 43 120 43 27 27 18 28 48 125 107 114 34 133 45 120 30 127 31 116 146 )

  stemleaf nls        NB. display has been abbreviated

┌──┬─────────────────────────────────────────────┐ │1 │2 8 3 8 │ ├──┼─────────────────────────────────────────────┤ │12│7 4 7 5 3 2 8 1 5 7 1 6 4 8 2 4 0 5 0 7 │ ├──┼─────────────────────────────────────────────┤ │2 │8 9 3 7 9 5 7 7 7 7 7 8 │ ...

  showStemLeaf nls

┌──┬─────────────────────────────────────────────┐ │ 0│7 7 │ │ 1│2 3 8 8 │ │ 2│3 5 7 7 7 7 7 7 8 8 9 9 │ │ 3│0 1 1 1 1 2 3 4 5 6 7 7 7 8 9 │ │ 4│0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8 │ │ 5│2 3 7 8 8 │ │ 6│1 3 8 │ │ 7│1 │ │ 8│ │ │ 9│6 9 │ │10│4 5 5 5 5 6 7 9 9 9 │ │11│1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9│ │12│0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8 │ │13│1 2 3 9 │ │14│1 6 │ └──┴─────────────────────────────────────────────┘

  (showStemLeaf -: showStemLeafX) nls   NB. both solutions give same result

1</lang>

JavaScript

It turns out that HTML+CSS renders the plot quite attractively.

<lang html4strict><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" > <title>stem and leaf plot</title> <script type='text/javascript'>

   function has_property(obj, propname) {
       return typeof(obj[propname]) == "undefined" ? false : true;
   }

   function compare_numbers(a, b) {return a-b;}

   function stemplot(data, target) {
       var stem_data = {};
       var all_stems = [];
       for (var i = 0; i < data.length; i++) {
           var stem = Math.floor(data[i] / 10);
           var leaf = Math.round(data[i] % 10);
           if (has_property(stem_data, stem)) {
               stem_data[stem].push(leaf);
           } else {
               stem_data[stem] = [leaf];
               all_stems.push(stem);
           }
       }
       all_stems.sort(compare_numbers);

       var min_stem = all_stems[0];
       var max_stem = all_stems[all_stems.length - 1];

       var table = document.createElement('table');
       for (var stem = min_stem; stem <= max_stem; stem++) {
           var row = document.createElement('tr');
           var label = document.createElement('th');
           row.appendChild(label);
           label.appendChild(document.createTextNode(stem));
           if (has_property(stem_data, stem)) {
               stem_data[stem].sort(compare_numbers);
               for (var i = 0; i < stem_data[stem].length; i++) {
                   var cell = document.createElement('td');
                   cell.appendChild(document.createTextNode(stem_data[stem][i]));
                   row.appendChild(cell);
               }
           }
           table.appendChild(row);
       }
       target.appendChild(table);
   }

</script> <style type='text/css'>

   body {font-family: monospace;}
   table {border-collapse: collapse;}
   th {border-right: 1px solid black; text-align: right;}
   td {text-align: right;}

</style> </head> <body>

<script type='text/javascript'>

   var data = [
       12,127,28,42,39,113,42,18,44,118,44,37,113,124,37,48,127,36,29,31,125,139,131,
       115,105,132,104,123,35,113,122,42,117,119,58,109,23,105,63,27,44,105,99,41,128,
       121,116,125,32,61,37,127,29,113,121,58,114,126,53,114,96,25,109,7,31,141,46,13,
       27,43,117,116,27,7,68,40,31,115,124,42,128,52,71,118,117,38,27,106,33,117,116,
       111,40,119,47,105,57,122,109,124,115,43,120,43,27,27,18,28,48,125,107,114,34,
       133,45,120,30,127,31,116,146
   ];
   stemplot(data, document.getElementById('target'));

</script>

</body> </html></lang>

The output looks like:


OCaml

The definition of the function unique below can be omited if one uses the extlib.

<lang ocaml>let unique li =

 let rec aux acc = function
 | [] -> (List.rev acc)
 | x::xs ->
     if List.mem x acc
     then aux acc xs
     else aux (x::acc) xs
 in
 aux [] li</lang>

<lang ocaml>let data =

 [ 12; 127; 28; 42; 39; 113; 42; 18; 44; 118; 44; 37; 113; 124; 37; 48;
   127; 36; 29; 31; 125; 139; 131; 115; 105; 132; 104; 123; 35; 113; 122;
   42; 117; 119; 58; 109; 23; 105; 63; 27; 44; 105; 99; 41; 128; 121; 116;
   125; 32; 61; 37; 127; 29; 113; 121; 58; 114; 126; 53; 114; 96; 25; 109;
   7; 31; 141; 46; 13; 27; 43; 117; 116; 27; 7; 68; 40; 31; 115; 124; 42;
   128; 52; 71; 118; 117; 38; 27; 106; 33; 117; 116; 111; 40; 119; 47; 105;
   57; 122; 109; 124; 115; 43; 120; 43; 27; 27; 18; 28; 48; 125; 107; 114;
   34; 133; 45; 120; 30; 127; 31; 116; 146 ]

let data =

 List.map (fun d ->
   let a = d / 10 in
   let b = d mod 10 in
   (a, b)
 ) data

let keys =

 List.sort compare (unique (List.map fst data))

let () =

 List.iter (fun key ->
   Printf.printf " %2d |" key;
   let vs = List.filter (fun (a,_) -> a = key) data in
   let vs = List.sort compare (List.map snd vs) in
   List.iter (Printf.printf " %d") vs;
   print_newline()
 ) keys</lang>

we can output the same latex code than the Perl example replacing the main function as follow:

<lang ocaml>let () =

 print_endline "\

\\documentclass{report} \\usepackage{fullpage} \\begin{document}

 \\begin{tabular}{ r | *{120}{c} }";
 List.iter (fun key ->
   Printf.printf "    %d" key;
   let vs = List.filter (fun (a,_) -> a = key) data in
   let vs = List.sort compare (List.map snd vs) in
   List.iter (Printf.printf " & %d") vs;
   print_endline " \\\\"
 ) keys;
 print_endline "\
 \\end{tabular}

\\end{document}"</lang>

Perl generating LaTeX

<lang perl>#!/usr/bin/perl -w

my @data = sort {$a <=> $b} qw( 12 127 28 42 39 113 42 18 44 118 44 37 113 124 37 48 127 36 29 31 125 139 131 115 105 132 104 123 35 113 122 42 117 119 58 109 23 105 63 27 44 105 99 41 128 121 116 125 32 61 37 127 29 113 121 58 114 126 53 114 96 25 109 7 31 141 46 13 27 43 117 116 27 7 68 40 31 115 124 42 128 52 71 118 117 38 27 106 33 117 116 111 40 119 47 105 57 122 109 124 115 43 120 43 27 27 18 28 48 125 107 114 34 133 45 120 30 127 31 116 );

  1. FIXME: This should count the maximum number of leaves in any one stem;
  2. instead it takes the total number of data items, which is usually
  3. a massive overestimate.

my $columns = @data;

print <<"EOT"; \\documentclass{report} \\usepackage{fullpage} \\begin{document}

 \\begin{tabular}{ r | *{$columns}{c} }

EOT

my $laststem = undef;

for my $value (@data) {

 my $stem = int($value / 10);
 my $leaf = $value % 10;
 while (not defined $laststem or $stem > $laststem) {
   if (not defined $laststem) {
     $laststem = $stem - 1;
   } else {
     print " \\\\\n";
   }
   $laststem++;
   print "    $laststem";
 }
 printf " & $leaf";

} print <<'EOT';

 \end{tabular}

\end{document} EOT</lang>

LaTeX output of the Perl program:

<lang latex>\documentclass{report} \usepackage{fullpage} \begin{document}

 \begin{tabular}{ r | *{120}{c} }
   0 & 7 & 7 \\
   1 & 2 & 3 & 8 & 8 \\
   2 & 3 & 5 & 7 & 7 & 7 & 7 & 7 & 7 & 8 & 8 & 9 & 9 \\
   ...
   13 & 1 & 2 & 3 & 9 \\
   14 & 1
 \end{tabular}

\end{document}</lang>

The parameter to the tabular environment defines the columns of the table. “r” and “c” are right- and center-aligned columns, “|” is a vertical rule, and “*{count}{cols}” repeats a column definition count times.

To get from the program above to a rendered PDF,

perl ./Stem-perl.pl > plot.tex && pdflatex plot.tex

and the output will be in plot.pdf. Output.

Perl 6

Translation of: Perl

Handles negative stems properly. <lang perl6>my @data = <

    12 127  28  42  39 113  42  18  44 118  44
    37 113 124  37  48 127  36  29  31 125 139
   131 115 105 132 104 123  35 113 122  42 117
   119  58 109  23 105  63  27  44 105  99  41
   128 121 116 125  32  61  37 127  29 113 121
    58 114 126  53 114  96  25 109   7  31 141
    46  13  27  43 117 116  27   7  68  40  31
   115 124  42 128  52  71 118 117  38  27 106
    33 117 116 111  40 119  47 105  57 122 109
   124 115  43 120  43  27  27  18  28  48 125
   107 114  34 133  45 120  30 127  31 116 146

>».Int.sort;

my Int $stem_unit = 10; my %h = @data.classify: * div $stem_unit;

my $range = [minmax] %h.keys».Int; my $stem_format = "%{$range.from.chars max $range.to.chars}d";

for $range.list -> $stem {

   my $leafs = %h{$stem} // [];
   say $stem.fmt($stem_format), ' | ', ~$leafs.map: * % $stem_unit;

}</lang>

Output:

 0 | 7 7
 1 | 2 3 8 8
 2 | 3 5 7 7 7 7 7 7 8 8 9 9
 3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9
 4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8
 5 | 2 3 7 8 8
 6 | 1 3 8
 7 | 1
 8 | 
 9 | 6 9
10 | 4 5 5 5 5 6 7 9 9 9
11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9
12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8
13 | 1 2 3 9
14 | 1 6

PicoLisp

<lang PicoLisp>(de *Data

  12 127 28 42 39 113 42 18 44 118 44 37 113 124 37 48 127 36
  29 31 125 139 131 115 105 132 104 123 35 113 122 42 117 119
  58 109 23 105 63 27 44 105 99 41 128 121 116 125 32 61 37 127
  29 113 121 58 114 126 53 114 96 25 109 7 31 141 46 13 27 43
  117 116 27 7 68 40 31 115 124 42 128 52 71 118 117 38 27 106
  33 117 116 111 40 119 47 105 57 122 109 124 115 43 120 43 27
  27 18 28 48 125 107 114 34 133 45 120 30 127 31 116 146 )

(let L

  (group
     (mapcar
        '((N)
           (cons
              (or (format (head -1 (setq N (chop N)))) 0)
              (last N) ) )
        (sort *Data) ) )
  (for I (range (caar L) (car (last L)))
     (prinl (align 3 I) " | " (glue " " (cdr (assoc I L)))) ) )</lang>

Output:

  0 | 7 7
  1 | 2 3 8 8
  2 | 3 5 7 7 7 7 7 7 8 8 9 9
  3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9
  4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8
  5 | 2 3 7 8 8
  6 | 1 3 8
  7 | 1
  8 |
  9 | 6 9
 10 | 4 5 5 5 5 6 7 9 9 9
 11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9
 12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8
 13 | 1 2 3 9
 14 | 1 6

PureBasic

Works with: PureBasic version 4.41

PureBasic Code <lang PureBasic>If OpenConsole()

 Dim MyList(120)
 Define i, j, StemMax, StemMin
 Restore MyData          ; Get the address of MyData, e.g. the data to print as a Stem-and-leaf plot
 For a=0 To 120
   Read.i MyList(a)      ; Read the data into the used Array
   If MyList(a)>StemMax
     StemMax=MyList(a)   ; Find the largest Stem layer at the same time
   EndIf    
   If MyList(a)<StemMin
     StemMin=MyList(a)   ; Find the smallest Stem layer at the same time
   EndIf
 Next 
 StemMax/10: StemMin/10  ; Remove the leafs from the Stem limits
 SortArray(MyList(),#PB_Sort_Ascending)  ; Sort the data

 For i=StemMin To StemMax
   Print(RSet(Str(i),3)+" | ")           ; Print the Stem
   For j=0 To 120
     If MyList(j)<10*i                   ; Skip all smaller then current
       Continue
     ElseIf MyList(j)>=10*(i+1)          ; Break current print if a new Stem layer is reached
       Break
     Else
       Print(Str(MyList(j)%10)+" ")      ; Print all Leafs on this current Stem layer
     EndIf
   Next j
   PrintN("")
 Next i

 Print(#CRLF$+#CRLF$+"Press ENTER to exit")
 Input()
 CloseConsole()

EndIf

DataSection MyData:

 Data.i  12,127, 28, 42, 39,113, 42, 18, 44,118, 44, 37,113,124, 37, 48,127, 36, 29, 31,125,139,131,115
 Data.i 105,132,104,123, 35,113,122, 42,117,119, 58,109, 23,105, 63, 27, 44,105, 99, 41,128,121,116,125
 Data.i  32, 61, 37,127, 29,113,121, 58,114,126, 53,114, 96, 25,109,  7, 31,141, 46, 13, 27, 43,117,116
 Data.i  27,  7, 68, 40, 31,115,124, 42,128, 52, 71,118,117, 38, 27,106, 33,117,116,111, 40,119, 47,105
 Data.i  57,122,109,124,115, 43,120, 43, 27, 27, 18, 28, 48,125,107,114, 34,133, 45,120, 30,127, 31,116,146

EndDataSection</lang>

Output

 0 | 7 7
 1 | 2 3 8 8
 2 | 3 5 7 7 7 7 7 7 8 8 9 9
 3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9
 4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8
 5 | 2 3 7 8 8
 6 | 1 3 8
 7 | 1
 8 |
 9 | 6 9
10 | 4 5 5 5 5 6 7 9 9 9
11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9
12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8
13 | 1 2 3 9
14 | 1 6


Python

Adjusting Stem.leafdigits allows you to modify how many digits of a value are used in the leaf, with the stem intervals adjusted accordingly. <lang python>from collections import namedtuple from pprint import pprint as pp from math import floor

Stem = namedtuple('Stem', 'data, leafdigits')

data0 = Stem((12, 127, 28, 42, 39, 113, 42, 18, 44, 118, 44, 37, 113, 124, 37,

             48, 127, 36, 29, 31, 125, 139, 131, 115, 105, 132, 104, 123, 35,
             113, 122, 42, 117, 119, 58, 109, 23, 105, 63, 27, 44, 105, 99,
             41, 128, 121, 116, 125, 32, 61, 37, 127, 29, 113, 121, 58, 114,
             126, 53, 114, 96, 25, 109, 7, 31, 141, 46, 13, 27, 43, 117, 116,
             27, 7, 68, 40, 31, 115, 124, 42, 128, 52, 71, 118, 117, 38, 27,
             106, 33, 117, 116, 111, 40, 119, 47, 105, 57, 122, 109, 124, 115,
             43, 120, 43, 27, 27, 18, 28, 48, 125, 107, 114, 34, 133, 45, 120,
             30, 127, 31, 116, 146),
            1.0)

def stemplot(stem):

   d = []
   interval = int(10**int(stem.leafdigits))
   for data in sorted(stem.data):
       data = int(floor(data))
       stm, lf = divmod(data,interval)
       d.append( (int(stm), int(lf)) )
   stems, leafs = list(zip(*d))
   stemwidth = max(len(str(x)) for x in stems)
   leafwidth = max(len(str(x)) for x in leafs)
   laststem, out = min(stems) - 1, []
   for s,l in d:
       while laststem < s:
           laststem += 1
           out.append('\n%*i |' % ( stemwidth, laststem))
       out.append(' %0*i' % (leafwidth, l))
   out.append('\n\nKey:\n Stem multiplier: %i\n X | Y  =>  %i*X+Y\n'
              % (interval, interval))
   return .join(out)

if __name__ == '__main__':

   print( stemplot(data0) )</lang>

Sample Output

 0 | 7 7
 1 | 2 3 8 8
 2 | 3 5 7 7 7 7 7 7 8 8 9 9
 3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9
 4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8
 5 | 2 3 7 8 8
 6 | 1 3 8
 7 | 1
 8 |
 9 | 6 9
10 | 4 5 5 5 5 6 7 9 9 9
11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9
12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8
13 | 1 2 3 9
14 | 1 6

Key:
 Stem multiplier: 10
 X | Y  =>  10*X+Y

R

<lang R> x <- c(12, 127, 28, 42, 39, 113, 42, 18, 44, 118, 44, 37, 113, 124, 37, 48, 127, 36, 29, 31, 125, 139, 131, 115, 105, 132, 104, 123, 35, 113, 122, 42, 117, 119, 58, 109, 23, 105, 63, 27, 44, 105, 99, 41, 128, 121, 116, 125, 32, 61, 37, 127, 29, 113, 121, 58, 114, 126, 53, 114, 96, 25, 109, 7, 31, 141, 46, 13, 27, 43, 117, 116, 27, 7, 68, 40, 31, 115, 124, 42, 128, 52, 71, 118, 117, 38, 27, 106, 33, 117, 116, 111, 40, 119, 47, 105, 57, 122, 109, 124, 115, 43, 120, 43, 27, 27, 18, 28, 48, 125, 107, 114, 34, 133, 45, 120, 30, 127, 31, 116, 146)

stem(x) </lang>

Output :

   0 | 77
   1 | 2388
   2 | 357777778899
   3 | 011112345677789
   4 | 001222233344456788
   5 | 23788
   6 | 138
   7 | 1
   8 | 
   9 | 69
  10 | 4555567999
  11 | 13333444555666677778899
  12 | 00112234445556777788
  13 | 1239
  14 | 16

Ruby

This implementation will handle negative values. <lang ruby>class StemLeafPlot

 def initialize(data, options = {})
   opts = {:leaf_digits => 1}.merge(options)
   @leaf_digits = opts[:leaf_digits]
   @multiplier = 10 ** @leaf_digits
   @plot = generate_structure(data)
 end
 private
 def generate_structure(data)
   plot = Hash.new {|h,k| h[k] = []}
   data.sort.each do |value| 
     stem, leaf = parse(value)
     plot[stem] << leaf
   end
   plot
 end
 def parse(value)
   stem, leaf = value.abs.divmod(@multiplier)
   [Stem.get(stem, value), leaf.round]
 end
 public
 def print
   stem_width = Math.log10(@plot.keys.max_by {|s| s.value}.value).ceil + 1
   Stem.get_range(@plot.keys).each do |stem|
     leaves = @plot[stem].inject("") {|str,leaf| str << "%*d " % [@leaf_digits, leaf]}
     puts "%*s | %s" % [stem_width, stem, leaves]
   end
   puts "key: 5|4=#{5 * @multiplier + 4}"
   puts "leaf unit: 1"
   puts "stem unit: #@multiplier"
 end

end

class Stem

 @@cache = {}
 def self.get(stem_value, datum)
   sign = datum < 0 ? :- : :+
   cache(stem_value, sign)
 end
 
 private
 
 def self.cache(value, sign)
   if @@cachevalue, sign.nil?
     @@cachevalue, sign = self.new(value, sign)
   end
   @@cachevalue, sign 
 end
 def initialize(value, sign)
   @value = value
   @sign = sign
 end
 
 public 
 
 attr_accessor :value, :sign
 
 def negative?
   @sign == :-
 end
 def <=>(other)
   if self.negative?
     if other.negative?
       other.value <=> self.value
     else
       -1
     end
   else
     if other.negative?
       1
     else
       self.value <=> other.value
     end
   end
 end
 def to_s
   "%s%d" % [(self.negative? ? '-' : ' '), @value]
 end
 
 def self.get_range(array_of_stems)
   min, max = array_of_stems.minmax
   if min.negative?
     if max.negative?
       min.value.downto(max.value).collect {|n| cache(n, :-)}
     else
       min.value.downto(0).collect {|n| cache(n, :-)} + 0.upto(max.value).collect {|n| cache(n, :+)}
     end
   else
     min.value.upto(max.value).collect {|n| cache(n, :+)}
   end
 end

end

data = DATA.read.split.map {|s| Float(s)} StemLeafPlot.new(data).print

__END__ 12 127 28 42 39 113 42 18 44 118 44 37 113 124 37 48 127 36 29 31 125 139 131 115 105 132 104 123 35 113 122 42 117 119 58 109 23 105 63 27 44 105 99 41 128 121 116 125 32 61 37 127 29 113 121 58 114 126 53 114 96 25 109 7 31 141 46 13 27 43 117 116 27 7 68 40 31 115 124 42 128 52 71 118 117 38 27 106 33 117 116 111 40 119 47 105 57 122 109 124 115 43 120 43 27 27 18 28 48 125 107 114 34 133 45 120 30 127 31 116 146</lang>

outputs

  0 | 7 7
  1 | 2 3 8 8
  2 | 3 5 7 7 7 7 7 7 8 8 9 9
  3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9
  4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8
  5 | 2 3 7 8 8
  6 | 1 3 8
  7 | 1
  8 |
  9 | 6 9
 10 | 4 5 5 5 5 6 7 9 9 9
 11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9
 12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8
 13 | 1 2 3 9
 14 | 1 6
key: 5|4=54
leaf unit: 1
stem unit: 10

Scala

Works with: Scala version 2.8

<lang scala>def stemAndLeaf(numbers: List[Int]) = {

 val lineFormat = "%" + (numbers map (_.toString.length) max) + "d | %s"
 val map = numbers groupBy (_ / 10)
 for (stem <- numbers.min / 10 to numbers.max / 10) {
   println(lineFormat format (stem, map.getOrElse(stem, Nil) map (_ % 10) sortBy identity mkString " "))
 }

}</lang>

Example:

scala> val list = """12  127 28  42  39  113 42  18  44  118 44  37  113 124 37  48  127 36  29  31  125 139 131 115 105
 132 104 123 35  113 122 42  117 119 58  109 23  105 63  27  44  105 99  41  128 121 116 125 32  61  37  127 29  113 121
 58  114 126 53  114 96  25  109 7   31  141 46  13  27  43  117 116 27  7   68  40  31  115 124 42  128 52  71  118 117
 38  27  106 33  117 116 111 40  119 47  105 57  122 109 124 115 43  120 43  27  27  18  28  48  125 107 114 34  133 45
 120 30  127 31  116
     | 146""" split "\\s+" map (_.toInt) toList
list: List[Int] = List(12, 127, 28, 42, 39, 113, 42, 18, 44, 118, 44, 37, 113, 124, 37, 48, 127, 36, 29, 31, 125, 139, 1
31, 115, 105, 132, 104, 123, 35, 113, 122, 42, 117, 119, 58, 109, 23, 105, 63, 27, 44, 105, 99, 41, 128, 121, 116, 125,
32, 61, 37, 127, 29, 113, 121, 58, 114, 126, 53, 114, 96, 25, 109, 7, 31, 141, 46, 13, 27, 43, 117, 116, 27, 7, 68, 40,
31, 115, 124, 42, 128, 52, 71, 118, 117, 38, 27, 106, 33, 117, 116, 111, 40, 119, 47, 105, 57, 122, 109, 124, 115, 43, 1
20, 43, 27, 27, 18, 28, 48, 125, 107, 114, 34, 133, 45, 120, 30, 127, 31, 116, 146)

scala> stemAndLeaf(list)
  0 | 7 7
  1 | 2 3 8 8
  2 | 3 5 7 7 7 7 7 7 8 8 9 9
  3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9
  4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8
  5 | 2 3 7 8 8
  6 | 1 3 8
  7 | 1
  8 |
  9 | 6 9
 10 | 4 5 5 5 5 6 7 9 9 9
 11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9
 12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8
 13 | 1 2 3 9
 14 | 1 6

Tcl

Works with: Tcl version 8.5

<lang tcl>package require Tcl 8.5

  1. How to process a single value, adding it to the table mapping stems to
  2. leaves.

proc addSLValue {tblName value {splitFactor 10}} {

   upvar 1 $tblName tbl
   # Extract the stem and leaf
   if {$value < 0} {

set value [expr {round(-$value)}] set stem -[expr {$value / $splitFactor}]

   } else {

set value [expr {round($value)}] set stem [expr {$value / $splitFactor}]

   }
   if {![info exist tbl]} {

dict set tbl min $stem

   }
   dict set tbl max $stem
   set leaf [expr {$value % $splitFactor}]
   dict lappend tbl $stem $leaf

}

  1. How to do the actual output of the stem-and-leaf table, given that we have
  2. already done the splitting into stems and leaves.

proc printSLTable {tblName} {

   upvar 1 $tblName tbl
   # Get the range of stems
   set min [dict get $tbl min]
   set max [dict get $tbl max]
   # Work out how much width the stems take so everything lines up
   set l [expr {max([string length $min], [string length $max])}]
   # Print out the table
   for {set i $min} {$i <= $max} {incr i} {

if {![dict exist $tbl $i]} { puts [format " %*d |" $l $i] } else { puts [format " %*d | %s" $l $i [dict get $tbl $i]] }

   }

}

  1. Assemble the parts into a full stem-and-leaf table printer.

proc printStemLeaf {dataList {splitFactor 10}} {

   foreach value [lsort -real $dataList] {

addSLValue tbl $value $splitFactor

   }
   printSLTable tbl

}

  1. Demo code

set data {

   12  127 28  42  39  113 42  18  44  118 44  37  113 124 37  48  127 36
   29  31  125 139 131 115 105 132 104 123 35  113 122 42  117 119 58  109
   23  105 63  27  44  105 99  41  128 121 116 125 32  61  37  127 29  113
   121 58  114 126 53  114 96  25  109 7   31  141 46  13  27  43  117 116
   27  7   68  40  31  115 124 42  128 52  71  118 117 38  27  106 33  117
   116 111 40  119 47  105 57  122 109 124 115 43  120 43  27  27  18  28
   48  125 107 114 34  133 45  120 30  127 31  116 146

} printStemLeaf $data</lang> Output:

  0 | 7 7
  1 | 2 3 8 8
  2 | 3 5 7 7 7 7 7 7 8 8 9 9
  3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9
  4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8
  5 | 2 3 7 8 8
  6 | 1 3 8
  7 | 1
  8 |
  9 | 6 9
 10 | 4 5 5 5 5 6 7 9 9 9
 11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9
 12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8
 13 | 1 2 3 9
 14 | 1 6

Ursala

<lang Ursala>#import std

  1. import nat

data =

<

  12,127,28,42,39,113,42,18,44,118,44,37,113,124,37,48,127,36,29,31,125,139,131,
  115,105,132,104,123,35,113,122,42,117,119,58,109,23,105,63,27,44,105,99,41,128,
  121,116,125,32,61,37,127,29,113,121,58,114,126,53,114,96,25,109,7,31,141,46,13,
  27,43,117,116,27,7,68,40,31,115,124,42,128,52,71,118,117,38,27,106,33,117,116,
  111,40,119,47,105,57,122,109,124,115,43,120,43,27,27,18,28,48,125,107,114,34,
  133,45,120,30,127,31,116,146>

stemleaf_plot =

^|T(~&,' | '--)*+ -+

  ^p(pad` @hS; * ==` ~-rlT,mat` *tS)@hSS+ (%nP*)^|*H/~& ^lrNCT/iota ~&,
  ^(*+ ^C/~&+ -:0!,~&zl)+ ^|(~&,nleq-<)*+ nleq-<&l@lK2hlPrSXS+ * division\10+-
  1. show+

main = stemleaf_plot data</lang> Reading from right to left on the bottom line of the stemleaf_plot function, we obtain the quotient and remainder of every datum divided by ten, partition by quotients, sort the partitions and sort within each partition, make a note of the maximum quotient, and reify the table into a function.

From right to left on the line above, we apply the reified function to each number up to the maximum quotient (i.e., including 8 which wasn't in the original table), convert each number to a string, flatten each leaf list with spaces as separators, and right justify the stem list.

The remaining top line forms the concatenation of each stem with its flattened leaf list and a vertical bar between them.

output:

 0 | 7 7
 1 | 2 3 8 8
 2 | 3 5 7 7 7 7 7 7 8 8 9 9
 3 | 0 1 1 1 1 2 3 4 5 6 7 7 7 8 9
 4 | 0 0 1 2 2 2 2 3 3 3 4 4 4 5 6 7 8 8
 5 | 2 3 7 8 8
 6 | 1 3 8
 7 | 1
 8 | 
 9 | 6 9
10 | 4 5 5 5 5 6 7 9 9 9
11 | 1 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 9 9
12 | 0 0 1 1 2 2 3 4 4 4 5 5 5 6 7 7 7 7 8 8
13 | 1 2 3 9
14 | 1 6