Random Number List Generator master
authorTJ <hacker@iam.tj>
Mon, 11 Jan 2016 22:00:18 +0000 (22:00 +0000)
committerTJ <hacker@iam.tj>
Mon, 11 Jan 2016 22:00:18 +0000 (22:00 +0000)
Signed-off-by: TJ <hacker@iam.tj>
Makefile [new file with mode: 0644]
randlist.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..021e390
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+PROG = randlist
+SRC = $(PROG).c
+CC = gcc
+CFLAGS = -O2
+
+all:
+       $(CC) $(CFLAGS) -o $(PROG) $(SRC)
+
+clean:
+       rm -f $(PROG) *.o
+
+PHONY: all clean
diff --git a/randlist.c b/randlist.c
new file mode 100644 (file)
index 0000000..cbaa7b5
--- /dev/null
@@ -0,0 +1,443 @@
+static const char const  *copyright = 
+"Random Number List Generator\n"
+"Copyright 2016 TJ <hacker@iam.tj>\n"
+"Licensed on the terms of the GNU General Public License version 3\n";
+
+static const char const *description =
+"Seed the C standard library's random number generator from /dev/random and generate one of more Groups of "
+"Count pseudo-random 'long int' numbers. If a Range is specified reduce each number modulo the range. "
+"If an Offset is specified add it to the random number. If Unique is specified ensure the number has not "
+"already been generated. If Split is specified change to the Split Range after Split numbers have been "
+"generated. If Sort is specified each Group (or Split sub-Groups) is sorted low to high. Pre-defined settings "
+"matching popular lottery games are included. Result is written as text to standard output; Optionally it is "
+"presented as seven segment LED digits. Verbose (debug) messages are written to standard error.";
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+
+// useful default values
+#define COUNT 1
+#define RANGE 100
+#define OFFSET 0
+#define UNIQUE 1
+#define GROUPS 1
+
+// command line option
+struct option {
+  const char *arg;
+  const char *usage;
+};
+
+// order items by longest 'arg' strings first to avoid sub-string matches
+static struct option app_options[] = {
+ { "-ukloto", "Generate values for UK Lotto (c=6,r=59,o=1,u,s)" },
+ { "-euromillions", "Generate values for EuroMillions (c=7,r=50,o=1,u,s,split=5,sr=10)" },
+ { "-split", "Change range after generating value [number]" },
+ { "-sr", "new Range [number] after split" },
+ { "-c", "generate [number] Count of values" },
+ { "-g", "generate [number] Groups of Count values" },
+ { "-r", "numbers in Range from 0 to [number]-1" },
+ { "-o", "Offset each value by +[number]" },
+ { "-u", "only Unique values in each group" },
+ { "-s", "Sort each Group (or Split group) low to high" },
+ { "-t", "Show Title [string] before numbers" },
+ { "-7", "7-segment LED display" },
+ { "-h", "display this help" },
+ { "-d", "debug messages (to stderr)" },
+ { NULL, NULL }
+};
+
+// declare in the same order as 'struct options'
+enum OPTS { O_UKLOTO, O_EUROMILLIONS, O_SPLIT, O_SPLITRANGE, O_COUNT, O_GROUP, O_RANGE, O_OFFSET, O_UNIQUE, O_SORT, O_TITLE, O_7SEG, O_HELP, O_DEBUG };
+
+// 7 segment LED number patterns
+enum SEG_PATTERN {S_TOP = 0x01, S_MID = 0x02, S_BOT = 0x04, S_TL = 0x08, S_TR = 0x10, S_BL = 0x20, S_BR = 0x40, S_DOT = 0x80};
+
+enum SEGS {S_0, S_1, S_2, S_3, S_4, S_5, S_6, S_7, S_8, S_9, S_dot };
+
+static unsigned char segments[] = {
+ /* 0 */ S_TOP | S_BOT | S_TL | S_TR | S_BL | S_BR,
+ /* 1 */ S_TR | S_BR,
+ /* 2 */ S_TOP | S_TR | S_MID | S_BL | S_BOT,
+ /* 3 */ S_TOP | S_TR | S_MID | S_BR | S_BOT,
+ /* 4 */ S_TL | S_TR | S_MID | S_BR,
+ /* 5 */ S_TOP | S_TL | S_MID | S_BR | S_BOT,
+ /* 6 */ S_TL | S_MID | S_BL | S_BR | S_BOT,
+ /* 7 */ S_TOP | S_TR | S_BR,
+ /* 8 */ S_TOP | S_TL | S_TR | S_MID | S_BL | S_BR | S_BOT,
+ /* 9 */ S_TOP | S_TL | S_TR | S_MID | S_BR,
+ /* A */ S_TOP | S_TL | S_TR | S_MID | S_BL | S_BR,
+ /* b */ S_TL | S_MID | S_TL | S_TR | S_BOT,
+ /* C */ S_TOP | S_TL | S_BL | S_BOT,
+ /* d */ S_TR | S_MID | S_BL | S_BR | S_BOT,
+ /* E */ S_TOP | S_TL | S_MID | S_BL | S_BOT,
+ /* F */ S_TOP | S_TL | S_MID | S_BL,
+ /* . */ S_DOT 
+};
+
+// display help
+void
+usage(void)
+{
+ printf("%s\n", copyright);
+ for (int o = 0; app_options[o].arg; ++o)
+  printf(" %s\t\t%s\n", app_options[o].arg, app_options[o].usage);
+ printf("\n%s\n", description);
+}
+
+// integer powers of 10
+long int 
+powi(unsigned int base, unsigned int exponent)
+{
+ for (int n = 0; n < exponent; ++n)
+  base *= 10;
+ return base;
+}
+
+// parse base 10 string into a number
+long int
+parse_long_int(const char *num)
+{
+ const char *ch;
+ unsigned bytes = sizeof(long int);
+ int count = 0;
+ int valid = 0;
+ int negative = 0; // defaults to assuming a positive number
+ long int result = 0;
+ long int parsed = 0;
+
+ /* This initial parse extracts valid digits until the end of valid characters, or string, whichever happens first
+    Because at this point the number of valid digits is not known each digit's value is multiplied by 10 times its
+    position in the valid number. E.g. "12345" becomes 54,321:
+    (1*1) + (2*10) + (3*100) + (4*1000) + (5*10000)
+    Obviously this is inverted compared to the real value.
+  */
+ for (ch = num; ch && *ch; ++ch) {
+  if (*ch == ' ')
+   continue;
+  else if (*ch == '-' && count == 0)
+   negative = 1;
+  else if (*ch >= '0' && *ch <= '9') {
+   // add value of digit multiplied by 10 x 'column'
+   parsed += (*ch - '0') * powi(1, count);
+   ++count;
+   valid = 1;
+  }
+  else
+   break;
+ }
+ /* If the parsed value is valid the value-position of each digit is inverted so e.g. 54,321 becomes 12,345:
+    (1*10000) + (2*1000) + (3*100) + (4*10) + (5*1).
+  */
+ if (valid) {
+  int digit = 0;
+  for (count=count-1; count >= 0; --count) {
+   int div = powi(1, count);
+   int mul = powi(1, digit);
+   int val = parsed/div;
+   parsed -= val * div;
+   result += val * mul;
+   ++digit;
+  }
+  if (negative)
+   result = -result;
+ }
+ return result;
+}
+
+// count the base-10 digits in a number
+int
+digits_count(long int value)
+{
+ int count = 1;
+ while (value > 9) {
+  value /= 10;
+  ++count;
+ }
+ return count;
+}
+
+// comparator for qsort()
+static int
+compar(const void *left, const void *right)
+{
+ const long int *l = (long int *)left;
+ const long int *r = (long int *)right;
+ return *l == *r ? 0 : (*l < *r ? -1 : 1);
+}
+
+// entry point
+int
+main(int argc, char **argv, char **env)
+{
+  long int r;
+  long int *unique;
+  ssize_t bytes;
+  unsigned int seed;
+  int not_unique;
+  int fd;
+  int result = 1;
+  int do_count  = COUNT;
+  int do_range  = RANGE;
+  int do_offset = OFFSET;
+  int do_unique = UNIQUE;
+  int do_groups = GROUPS;
+  int do_sort   = 0;
+  int do_debug  = 0;
+  int do_7seg   = 0;
+  int do_split  = 0; // index of 'count' where a new range applies
+  int do_split_range = 0; // the new range (useful for e.g. EuroMillions 5+2 (1-50, 1-10))
+  const char *dev_random = "/dev/random";
+  const char *title = NULL;
+
+  if (argc == 1) {
+   usage();
+   return 0;
+  }
+  
+  printf("%s\n", copyright);
+
+  for (int a = 1; a < argc; ++a) { // parse each command-line argument
+   if (do_debug) fprintf(stderr, "Testing arg %d %s\n", a, argv[a]);
+   for (int option = 0; app_options[option].arg; ++option) { // for each option...
+    if (do_debug) fprintf(stderr, "Testing option %d %s\n", option, app_options[option].arg);
+    if (strncmp(argv[a], app_options[option].arg, strlen(app_options[option].arg)) == 0) {
+     switch (option) {
+      case O_HELP:
+       usage();
+       return 0;
+      case O_DEBUG:
+       do_debug = 1;
+       break;
+      case O_COUNT:
+       do_count = parse_long_int(argv[++a]);
+       break;
+      case O_GROUP:
+       do_groups = parse_long_int(argv[++a]);
+       break;
+      case O_UNIQUE:
+       do_unique = 1;
+       break;
+      case O_RANGE:
+       do_range = parse_long_int(argv[++a]);
+       if (!do_split_range)
+        do_split_range = do_range;
+       break;
+      case O_OFFSET:
+       do_offset = parse_long_int(argv[++a]);
+       break;
+      case O_SORT:
+       do_sort = 1;
+       break;
+      case O_TITLE:
+       title = argv[++a];
+       break;
+      case O_7SEG:
+       do_7seg = 1;
+       break;
+      case O_SPLIT:
+       do_split = parse_long_int(argv[++a]);
+       break;
+      case O_SPLITRANGE:
+       do_split_range = parse_long_int(argv[++a]);
+       break;
+      case O_UKLOTO:
+       do_count = 6; 
+       do_range = 59;
+       do_offset = 1;
+       do_unique = 1;
+       do_sort = 1;
+       title = app_options[option].arg + 1;
+       break;
+      case O_EUROMILLIONS:
+       do_count = 7;
+       do_range = 50;
+       do_offset = 1;
+       do_unique = 1;
+       do_sort = 1;
+       do_split = 5;
+       do_split_range = 10;
+       title = app_options[option].arg + 1;
+       break;
+     } // switch
+     break;
+    } // arg == option comparison
+    else {
+     fprintf(stderr, "Error: unknown option %s\n", argv[a]);
+     return 1;
+    }
+   } // options
+  } // args
+
+  if (!do_split)
+   do_split = do_count; // no Split sub-group
+
+  if (do_debug)
+   setvbuf(stderr, NULL, _IONBF, 0); // don't buffer debug messages
+
+  unique = calloc(do_count, sizeof(*unique)); // reserve storage for a Group's values
+  if (unique) {
+   if ((fd = open(dev_random, O_RDONLY)) >= 0) {
+    bytes = read(fd, (void *)&seed, sizeof(seed)); // get a truly random value
+    if (do_debug) fprintf(stderr, "Seed %u of %ld bytes from %s\n", seed, bytes, dev_random);
+    close(fd);
+    if (bytes == sizeof(seed)) { // ensure bytes read from /dev/random fill the seed
+     srandom(seed); // seed te pseudo-random number generator
+
+     if (title)
+      printf("%s\n", title);
+
+     for (int group = 0; group < do_groups; ++group) { // generate each Group
+      if (do_debug) fprintf(stderr, "Generating group %d\n", group);
+      int digit_count = 0; // keep track of the number of digits in the Group
+
+      for (int n = 0; n < do_count; ++n) { // generate each value
+       // determine if this is now a Split sub-group
+       unsigned int in_split = (do_split < do_count && n >= do_split) ? 1 : 0;
+
+       if (do_debug) fprintf(stderr, "Generating value %d in range %d,", n, in_split ? do_split_range : do_range);
+
+       r = random(); // the (long int) random number
+       // adjust to fit Range
+       r = do_offset + (r % (in_split ? do_split_range : do_range));
+       unique[n] = r;
+       if (do_debug) fprintf(stderr, " value: %ld\n", unique[n]);
+
+       if (do_unique) {
+        if (do_debug) fprintf(stderr, "%s\n", "Unique check");
+        not_unique = 0;
+
+        for (int m = (in_split ? do_split : 0); m < n && !not_unique; ++m) { // detect repeated values in sub-group
+         if (do_debug) fprintf(stderr, " comparing value %d (%ld) with %d (%ld)\n", n, unique[n], m, unique[m]);
+         if (unique[m] == unique[n]) 
+          not_unique = 1;
+         if (do_debug) fprintf(stderr, "%sUnique\n", not_unique ? "NOT " : "");
+        }
+        if (not_unique) {
+         --n; // do this Count value again
+         continue;
+        }
+        digit_count += digits_count(unique[n]);
+       }
+      } // count
+      if (do_sort) {
+       if (do_debug) fprintf(stderr, "%s\n", "Sorting");
+       // if there is no split sub-group this will sort all values (since do_split == do_count)
+       // if there is a split sub-group this will only sort the values before the split
+       qsort(unique, do_split, sizeof(*unique), compar); 
+       if (do_split < do_count) // sort the values after the split
+        qsort(unique + do_split, do_count - do_split, sizeof(*unique), compar);
+      }
+
+      if (do_7seg) { // generate and draw the pseudo 7-segment 'LED' display
+       // size and spacing of digits
+       unsigned int d_rows = 9;
+       unsigned int d_cols = 5;
+       unsigned int d_space = 3;
+       // (x,y) coordinate maximums
+       int grid_y_max = d_rows;
+       int grid_x_max = (digit_count * d_cols) + (do_count * d_space);
+       if (do_split < do_count)
+        grid_x_max += d_space * 2; // space the Split sub-group clearly
+
+       // track current (x,y) coordinate
+       int grid_y = 0;
+       int grid_x = 0;
+
+       if (do_debug) fprintf(stderr, "Grid: digits: %d rows: %d cols: %d\n", digit_count, grid_y_max, grid_x_max);
+
+       unsigned char *grid = malloc(grid_x_max * grid_y_max);
+       if (grid) {
+        memset(grid, ' ', grid_y_max * grid_x_max); // default all 'LEDs' to unlit
+        int digit_i = 0; // keep track of which digit of the entire current group is being rendered
+
+        for (int y = 1; y <= grid_y_max; ++y) { // insert string terminators at the end of each row
+         char *p = grid + y * grid_x_max - 1;
+         *p = 0;
+        }
+        for (int c = 0; c < do_count; ++c) { // process each value in the group
+         long int val = unique[c];
+
+         for (int d = digits_count(unique[c]) - 1; d >= 0; --d) { // split value into individual digits
+          long int v = val / powi(1, d); // isolate a base-10 digit
+          val -= v * powi(1, d); // update the remaining value
+          unsigned char s = segments[v]; // get the 'LED' On flags for this digit
+          if (do_debug) fprintf(stderr, " digit: %d %d: %ld = %02x",digit_i,  d, v, s);
+
+          for (int x = grid_x; x < grid_x + d_cols; ++x) { // scan the columns...
+           for (int y = grid_y; y < grid_y_max; ++y) { // ... by row
+            int show = 0; // set to 1 if this 'LED' position should be 'lit'
+
+            // draw the horizontal bars
+            if (x >= grid_x && x < grid_x + d_cols - 1) {
+             if ((s & S_TOP && y == 0) ||
+                 (s & S_MID && y == (grid_y_max-1)/2) ||
+                 (s & S_BOT && y == grid_y_max-1))
+              show = 1;
+            }
+            // draw the vertical bars
+            if (s & S_TL && (x == grid_x && (y >= 0 && y <= (grid_y_max-1)/2)))
+             show = 1;
+            if (s & S_TR && (x == (grid_x + d_cols - 2) && (y >= 0 && y <= (grid_y_max-1)/2)))
+             show = 1;
+            if (s & S_BL && (x == grid_x && (y >= (grid_y_max-1)/2)))
+             show = 1;
+            if (s & S_BR && (x == (grid_x + d_cols - 2) && (y >= (grid_y_max-1)/2)))
+             show = 1;
+
+            if (show)
+             *(grid + (y * grid_x_max) + x ) = '0' + v; // use the current digit as the 'light'
+           }
+          }
+          grid_x += d_cols; // advance to next digit position
+          ++digit_i; // advance to next digit in group
+         } // digits
+         if (do_debug) fprintf(stderr, "%s", "\n");
+         // insert comma after digits of each value
+         *(grid + ((grid_y_max-1) * grid_x_max) + grid_x + 1) = ',';
+         grid_x += d_space; // advance with additional spacing between digits
+
+         if (do_split < do_count && c == do_split - 1)
+          grid_x += d_space * 2; // advance with addtional spacing between Split sub-groups
+         
+        } // count
+
+        // the completed 7-segment 'LED' display
+        printf("%s", "\n");
+        for (int y = 0; y < grid_y_max; ++y) {
+         char *line = grid + y * grid_x_max;
+         printf("%s\n", line);
+        }
+        printf("%s", "\n");
+
+        free(grid);
+        grid = NULL;
+       } // grid
+      } // 7-segment
+      else { // normal text output of values
+       for (int c = 0; c < do_count; ++c) {
+        printf(" %ld", unique[c]);
+       }
+      }
+      printf("%s", "\n");
+     } // group
+     result = 0;
+    } // seed is valid
+    else {
+     fprintf(stderr, "Error: insuffient random bytes returned from %s\n", dev_random);
+    }
+    free(unique);
+   } // /dev/random
+  } // memory allocated for values
+  else {
+   fprintf(stderr, "Error: failed to allocate %ld bytes of memory for values\n", do_count * sizeof(*unique));
+   result = 3;
+  }
+  return result;
+}
+