Python 3 Sequence

Laxer

Co-Founder
Senior Administrator
Staff member
Joined
Feb 20, 2012
Posts
4,003
Location
Portland, OR
As part of one of my software engineering courses. (Almost 100% theoretical) we were prompted with the task to rewrite the *nix CoreUtils Seq Command(seq invocation - GNU Coreutils) using Python.

Seeing as I have never really worked with python all of my code is based off of whatever I could google up... :lolg:

As of right now I have completed CL1 outlined below:

Code:
Copyright © 2013 Bart Massey 
Revision 0: 1 October 2013

This specification describes the "universal sequence" command sequ. The sequ command is a backward-compatible set of extensions to the UNIX [seq](http://www.gnu.org/software/coreutils/manual/html_node/seq-invocation.html) command. There are many implementations of seq out there: this specification is built on the seq supplied with GNU Coreutils version 8.21.

The seq command emits a monotonically increasing sequence of numbers. It is most commonly used in shell scripting:

  TOTAL=0
  for i in `seq 1 10`
  do
    TOTAL=`expr $i + $TOTAL`
  done
  echo $TOTAL
prints 55 on standard output. The full sequ command does this basic counting operation, plus much more.

This specification of sequ is in several stages, known as compliance levels. Each compliance level adds required functionality to the sequ specification. Level 1 compliance is equivalent to the Coreutils seq command.

The usual specification language applies to this document: MAY, SHOULD, MUST (and their negations) are used in the standard fashion.

Wherever the specification indicates an error, a conforming sequ implementation MUST immediately issue appropriate error message specific to the problem. The implementation then MUST exit, with a status indicating failure to the invoking process or system. On UNIX systems, the error MUST be indicated by exiting with status code 1.

When a conforming sequ implementation successfully completes its output, it MUST immediately exit, with a status indicating success to the invoking process or systems. On UNIX systems, success MUST be indicated by exiting with status code 0.

Compliance Level 0

Compliance Level 0 of sequ requires absolute minimum functionality. A CL0 sequ MUST accept exactly two command-line arguments. Each argument SHOULD be a representation of an integer value. Any other supplied argument syntax is an error.

If the first integer argument is numerically greater than the second, the sequ command MUST emit no output. Otherwise, sequ MUST print on its output each of the integers between the first and second argument, inclusive. Each output integer MUST be on a line by itself, that is, a line terminated with an appropriate line terminator for the host environment.

Compliance Level 1

Compliance Level 1 of sequ adds the full functionality of GNU Coreutils seq. This includes the "--format", "--separator", "--equal-width", "--help" and "--version" arguments (as well as the one-character abbreviations of these), the increment argument, and support for floating-point numbers. The sequ initialization and increment arguments are now optional, as per the seq spec.

The sequ "--format" specifier MAY format floating-point numbers differently than seq, but it MUST follow some well-described and reasonable floating-point formatting standard.

Backslash-escapes in the "-s" argument string MUST be processed as in C printf(3).

Compliance Level 2

Compliance Level 2 of sequ adds additional convenience arguments for formatting.

The arguments that MUST be accepted are as follows:

-W, --words: Output the sequence as a single space-separated line. Equivalent to "-s ' '".

-p, --pad : Output the sequence with elements padded on the left to be all of equal width: the pad character is given by the single-char pad string . Backslash-escapes in MUST be processed as in C printf(3).

Note that the "-w" command of level 2 is equivalent to "-p '0'".

-P, --pad-spaces: Output the sequence with elements padded with spaces on the left to be all of equal width. Equivalent to "-p ' '".

Compliance Level 3

Compliance Level 3 of sequ adds the ability to have sequences of types other than floating-point numbers.

Specifically, CL3 sequ MUST accept as arguments and output as results: arbitrary-precision integers, single lowercase alphabetic (ASCII) letters, single uppercase alphabetic (ASCII) letters, and lowercase or uppercase unsigned Roman Numerals.

The sequ command MUST accept a new flag, "--format-word" or "-F", that takes a one-word argument indicating the type of the sequence. The sequ command MUST accept the format-word arguments "arabic" (for integers), "floating", "alpha" (for letters), "ALPHA", "roman" or "ROMAN"; the all-uppercase variants indicate uppercase sequences.

The sequ command MUST accept limit arguments (start, end, and increment) in the format consistent with the format-word. Arabic limit arguments MAY be "promoted" to Roman Numerals when Roman output is requested. The increment argument for alpha formats MUST be arabic. Otherwise, the limit arguments MUST be in the same format as the format-word. When no format-word is given, the format MUST be inferred from the format of the mandatory end argument.

Compliance Level 4

Compliance Level 4 of sequ adds the ability to number the lines of a textfile presented on the input.

CL4 sequ MUST accept the "--number-lines" / "-n" argument. This argument indicates that, rather than outputting the sequence on standard output, sequ will act as a filter, numbering lines of a file read from standard input to standard output. Each line "number" will be in the format specified by the "--format-word" argument, or inferred from the start or increment limit argument if the "--format-word" argument is not supplied. The end argument is irrelevant when "--number-lines" is supplied; it MUST NOT be accepted. The separator between the line number and the line may be given by the "--separator" argument, defaulting to space.

Compliance Level 5

Compliance Level 5 of sequ adds the ability to infer a sequence from a given prefix.

As an alternative to the limit arguments of previous Compliance Levels, CL5 sequ may accept a sequence specifier of the form:

value [value] [value] ... ".." value

When the ".." argument is present, the non-flag arguments MUST be parsed in inference mode.

In inference mode, sequ picks a best match for the pattern (partial sequence of values leading up to the ".."), and then continues the sequence until the end value (after the "..") is succeeded.

I am happy with my code for the most part except, the last part(printing the sequence) and that sequ.py -w does not accept a negative scientific notation number.

Here is my code so far:
Code:
#!/usr/bin/python3
# Copyright © 2013 Bart Massey & Geoff Maggi

# CL1 of the sequ command:
#	Is equivalent to the coreutils seq command
#
#	Spec change:
#		on two entry runs the precision is set to the input with the highest precision. (Coreutils sets it to the first number's precision)
#		If more than 3 terms are input ignore them and just use the first 3
#		Default separator is ' '
#		-f overrides -w and -s, as it specifies it's own format

import sys, argparse, codecs
from decimal import Decimal
	
def numDecimals(x):
	x = Decimal(x).as_tuple().exponent
	if(x<1):
		return(-x)
	else:
		return(0)

def isint(x):
	try:
		x=dec(x)
	except Exception:
		return False
	if(x%1 == 0):
		return True
	else:
		return False
		
def findWidth(curTerm, step, finalTerm, precision):
	width = 1 #set width to 1 by default
	curTerm = len(formatTerm(curTerm,precision,width))
	step = len(formatTerm(step,precision,width))
	finalTerm = len(formatTerm(finalTerm,precision,width))
	return max(curTerm,step,finalTerm) #return max width
	
def formatTerm(term, precision, width):
	return '{0:0{fill}.{prec}f}'.format(term, prec=precision, fill=width)

def dec(string):
	try:
		value = Decimal(string)
	except TypeError:
		dec(float(string))
		raise argparse.ArgumentTypeError("not valid input")
		sys.exit(1)
	except Exception:
		print("Not a valid input")
		print("\nCheck --help for more information on inputs")
		sys.exit(1)
	return value
	
parser = argparse.ArgumentParser(description='Sequ, prints a sequence')
parser.add_argument('-f','--format', help='Use printf style floating-point')
parser.add_argument('-s','--separator', help='Use STRING to separate numbers')
parser.add_argument('-v','--version', help='Displays version information', action='store_true')
parser.add_argument('-w','--equal-width', help='Equalize width by padding with leading zeroes', action='store_true', dest='width')
parser.add_argument('vals', help='Values to create a sequence from start step end', nargs='*') #0 or more as I catch 0 cases myself
args = parser.parse_args()

if(args.version):
	print("sequ.py 0.2")
	print("Copyright (c) 2013 Bart Massey & Geoff Maggi")
	sys.exit(0) #Exit with good status!

precision = 0 #Set the output precision to 0. Overwritten if needed later
width = 1 #Default set, overwritten if -w flag is set.
separator = ' ' #Default separator, not sure if it should be ' ' or '\n'

if(args.separator):
	separator = codecs.getdecoder("unicode_escape")(args.separator)[0] #fixes parsing error where strings were automatically encoding, messing up escape chars
if(args.format):
	format = codecs.getdecoder("unicode_escape")(args.format)[0] #same as above

if(len(args.vals) > 2): #All args set, custom increment
	curTerm = dec(args.vals[0])
	step = dec(args.vals[1])
	finalTerm = dec(args.vals[2])
	precision = numDecimals(args.vals[1]) #precision of step, used for output
	
if(len(args.vals) == 2):
	curTerm = dec(args.vals[0])
	step = 1
	finalTerm = dec(args.vals[1])
	p0 = numDecimals(args.vals[0]) #precision of first num
	p1 = numDecimals(args.vals[1]) #precision of second num
	precision = max(p0,p1)
	
if(len(args.vals) == 1):
	curTerm = 1
	step = 1
	finalTerm = dec(args.vals[0])
	precision = numDecimals(args.vals[0]) #precision of input, have output match
	
if(len(args.vals) == 0):
	sys.exit(1) #throw some basic error
	
if((curTerm>finalTerm and step>0) or (curTerm<finalTerm and step<0) or step==0):
	sys.exit(0) #Exit and print nothing if it is impossible to get from curTerm to FinalTerm with step.

if(args.width):
	width = findWidth(curTerm,step,finalTerm, precision) #find the width given all of the terms
	
#Printing everything, needs cleaning.... Mostly for format, lots of dup code	
if(curTerm<=finalTerm): #counting up
	while(curTerm<=(finalTerm-step)):
		if(args.format):
			try:
				print(format % curTerm, end='')
			except Exception:
				print("Not a valid format")
				print("\nCheck --help for more information on formats")
				sys.exit(1)
		else:
			print(formatTerm(curTerm,precision,width), end=separator)
		curTerm+=step	
		
elif(curTerm>finalTerm): #counting down
	while(curTerm>=(finalTerm-step)):
		if(args.format):
			try:
				print(format % curTerm, end='')
			except Exception:
				print("Not a valid format")
				print("\nCheck --help for more information on formats")
				sys.exit(1)
		else:
			print(formatTerm(curTerm,precision,width), end=separator)
		curTerm+=step
		
if(args.format):
	try:
		print(format % curTerm, end='') #print the last term without the separator!
	except Exception:
		print("Not a valid format")
		print("\nCheck --help for more information on formats")
		sys.exit(1)
else:
		print(formatTerm(curTerm,precision,width), end='') #print the last term without the separator!
		
sys.exit(0)

Thoughts? Also, as I am new to python if you see anything that screams bad please tell me :thumbsup2:
 
I'll take a look at this later, I'm way too tired currently. Worked a total of over 60hrs this week and I'm required to work tomorrow at 7am as well this weekend.
 
I see minor things here that don't make sense:
Code:
[plain]if(x<1):
		return(-x)
	else:
		return(0)[/plain]

If x was 0, why return a negated value of 0 when the result +/- 0 still means 0? Why not have that condition: x < 0?

As for the isint function definition, that's not a great way of checking. What you could do is just check the type. There's no need for a try block here to attempt to cast to a numeric type if you can verify that the type is not a string. Mod 1 doesn't validate that the value is an integral either, it just means that it can be evenly divisible by 1, aside from that it could be a float. If you needed an int, check for a numeric value type, then cast to an int.
 
The first part you are right. x<0 works just as well. :lolg:

As for the isint function.... it actually isn't even used anymore... It was used when I was trying to make sure floats that were like 1.00000 were casted to int....

I've covered this with double so no need... It can be deleted...

Code:
#!/usr/bin/python3
# Copyright © 2013 Bart Massey & Geoff Maggi

# CL1 of the sequ command:
#	Is equivalent to the coreutils seq command
#
#	Spec change:
#		on two entry runs the precision is set to the input with the highest precision. (Coreutils sets it to the first number's precision)
#		If more than 3 terms are put in ignore them and just use the first 3
#		Default separator is ' '
#		-f overrides -w and -s, as it specifies it's own format

import sys, argparse, codecs
from decimal import Decimal
	
def numDecimals(x):
	x = Decimal(x).as_tuple().exponent
	if(x<0):
		return(-x)
	else:
		return(0)
		
def findWidth(curTerm, step, finalTerm, precision):
	width = 1 #set width to 1 by default
	curTerm = len(formatTerm(curTerm,precision,width))
	step = len(formatTerm(step,precision,width))
	finalTerm = len(formatTerm(finalTerm,precision,width))
	return max(curTerm,step,finalTerm) #return max width
	
def formatTerm(term, precision, width):
	return '{0:0{fill}.{prec}f}'.format(term, prec=precision, fill=width)

def dec(string):
	try:
		value = Decimal(string)
	except TypeError:
		dec(float(string))
		raise argparse.ArgumentTypeError("not valid input")
		sys.exit(1)
	except Exception:
		print("Not a valid input")
		print("\nCheck --help for more information on inputs")
		sys.exit(1)
	return value
	
parser = argparse.ArgumentParser(description='Sequ, prints a sequence')
parser.add_argument('-f','--format', help='Use printf style floating-point')
parser.add_argument('-s','--separator', help='Use STRING to separate numbers')
parser.add_argument('-v','--version', help='Displays version information', action='store_true')
parser.add_argument('-w','--equal-width', help='Equalize width by padding with leading zeroes', action='store_true', dest='width')
parser.add_argument('vals', help='Values to create a sequence from start step end', nargs='*') #0 or more as I catch 0 cases myself
args = parser.parse_args()

if(args.version):
	print("sequ.py 0.2")
	print("Copyright (c) 2013 Bart Massey & Geoff Maggi")
	sys.exit(0) #Exit with good status!

precision = 0 #Set the output precision to 0. Overwritten if needed later
width = 1 #Default set, overwritten if -w flag is set.
separator = ' ' #Default separator, not sure if it should be ' ' or '\n'

if(args.separator):
	separator = codecs.getdecoder("unicode_escape")(args.separator)[0] #fixes parsing error where strings were automatically encoding, messing up escape chars
if(args.format):
	format = codecs.getdecoder("unicode_escape")(args.format)[0] #same as above

if(len(args.vals) > 2): #All args set, custom increment
	curTerm = dec(args.vals[0])
	step = dec(args.vals[1])
	finalTerm = dec(args.vals[2])
	precision = numDecimals(args.vals[1]) #precision of step, used for output
	
if(len(args.vals) == 2):
	curTerm = dec(args.vals[0])
	step = 1
	finalTerm = dec(args.vals[1])
	p0 = numDecimals(args.vals[0]) #precision of first num
	p1 = numDecimals(args.vals[1]) #precision of second num
	precision = max(p0,p1)
	
if(len(args.vals) == 1):
	curTerm = 1
	step = 1
	finalTerm = dec(args.vals[0])
	precision = numDecimals(args.vals[0]) #precision of input, have output match
	
if(len(args.vals) == 0):
	sys.exit(1) #throw some basic error
	
if((curTerm>finalTerm and step>0) or (curTerm<finalTerm and step<0) or step==0):
	sys.exit(0) #Exit and print nothing if it is impossible to get from curTerm to FinalTerm with step.

if(args.width):
	width = findWidth(curTerm,step,finalTerm, precision) #find the width given all of the terms
	
#Printing everything, needs cleaning.... Mostly for format, lots of dup code	
if(curTerm<=finalTerm): #counting up
	while(curTerm<=(finalTerm-step)):
		if(args.format):
			try:
				print(format % curTerm, end='')
			except Exception:
				print("Not a valid format")
				print("\nCheck --help for more information on formats")
				sys.exit(1)
		else:
			print(formatTerm(curTerm,precision,width), end=separator)
		curTerm+=step	
		
elif(curTerm>finalTerm): #counting down
	while(curTerm>=(finalTerm-step)):
		if(args.format):
			try:
				print(format % curTerm, end='')
			except Exception:
				print("Not a valid format")
				print("\nCheck --help for more information on formats")
				sys.exit(1)
		else:
			print(formatTerm(curTerm,precision,width), end=separator)
		curTerm+=step
		
if(args.format):
	try:
		print(format % curTerm, end='') #print the last term without the separator!
	except Exception:
		print("Not a valid format")
		print("\nCheck --help for more information on formats")
		sys.exit(1)
else:
		print(formatTerm(curTerm,precision,width), end='') #print the last term without the separator!
		
sys.exit(0)

Thanks Ace.

Told you I had remnants of code all over the place :shame2:
 
I knew it wasn't being used. I just wanted to point my observations out. Why? -- I was trying to look for the usage to see why you even needed such a method and found no references anyways.
 
See the original seq.c file for reference:

Code:
/* seq - print sequence of numbers to standard output.
   Copyright (C) 1994-2013 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

/* Written by Ulrich Drepper.  */

#include <config.h>
#include <getopt.h>
#include <stdio.h>
#include <sys/types.h>

#include "system.h"
#include "c-strtod.h"
#include "error.h"
#include "quote.h"
#include "xstrtod.h"

/* Roll our own isfinite rather than using <math.h>, so that we don't
   have to worry about linking -lm just for isfinite.  */
#ifndef isfinite
# define isfinite(x) ((x) * 0 == 0)
#endif

/* The official name of this program (e.g., no 'g' prefix).  */
#define PROGRAM_NAME "seq"

#define AUTHORS proper_name ("Ulrich Drepper")

/* If true print all number with equal width.  */
static bool equal_width;

/* The string used to separate two numbers.  */
static char const *separator;

/* The string output after all numbers have been output.
   Usually "\n" or "\0".  */
static char const terminator[] = "\n";

static struct option const long_options[] =
{
  { "equal-width", no_argument, NULL, 'w'},
  { "format", required_argument, NULL, 'f'},
  { "separator", required_argument, NULL, 's'},
  {GETOPT_HELP_OPTION_DECL},
  {GETOPT_VERSION_OPTION_DECL},
  { NULL, 0, NULL, 0}
};

void
usage (int status)
{
  if (status != EXIT_SUCCESS)
    emit_try_help ();
  else
    {
      printf (_("\
Usage: %s [OPTION]... LAST\n\
  or:  %s [OPTION]... FIRST LAST\n\
  or:  %s [OPTION]... FIRST INCREMENT LAST\n\
"), program_name, program_name, program_name);
      fputs (_("\
Print numbers from FIRST to LAST, in steps of INCREMENT.\n\
"), stdout);

      emit_mandatory_arg_note ();

      fputs (_("\
  -f, --format=FORMAT      use printf style floating-point FORMAT\n\
  -s, --separator=STRING   use STRING to separate numbers (default: \\n)\n\
  -w, --equal-width        equalize width by padding with leading zeroes\n\
"), stdout);
      fputs (HELP_OPTION_DESCRIPTION, stdout);
      fputs (VERSION_OPTION_DESCRIPTION, stdout);
      fputs (_("\
\n\
If FIRST or INCREMENT is omitted, it defaults to 1.  That is, an\n\
omitted INCREMENT defaults to 1 even when LAST is smaller than FIRST.\n\
FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\
INCREMENT is usually positive if FIRST is smaller than LAST, and\n\
INCREMENT is usually negative if FIRST is greater than LAST.\n\
"), stdout);
      fputs (_("\
FORMAT must be suitable for printing one argument of type 'double';\n\
it defaults to %.PRECf if FIRST, INCREMENT, and LAST are all fixed point\n\
decimal numbers with maximum precision PREC, and to %g otherwise.\n\
"), stdout);
      emit_ancillary_info ();
    }
  exit (status);
}

/* A command-line operand.  */
struct operand
{
  /* Its value, converted to 'long double'.  */
  long double value;

  /* Its print width, if it were printed out in a form similar to its
     input form.  An input like "-.1" is treated like "-0.1", and an
     input like "1." is treated like "1", but otherwise widths are
     left alone.  */
  size_t width;

  /* Number of digits after the decimal point, or INT_MAX if the
     number can't easily be expressed as a fixed-point number.  */
  int precision;
};
typedef struct operand operand;

/* Description of what a number-generating format will generate.  */
struct layout
{
  /* Number of bytes before and after the number.  */
  size_t prefix_len;
  size_t suffix_len;
};

/* Read a long double value from the command line.
   Return if the string is correct else signal error.  */

static operand
scan_arg (const char *arg)
{
  operand ret;

  if (! xstrtold (arg, NULL, &ret.value, c_strtold))
    {
      error (0, 0, _("invalid floating point argument: %s"), arg);
      usage (EXIT_FAILURE);
    }

  /* We don't output spaces or '+' so don't include in width */
  while (isspace (to_uchar (*arg)) || *arg == '+')
    arg++;

  ret.width = strlen (arg);
  ret.precision = INT_MAX;

  if (! arg[strcspn (arg, "xX")] && isfinite (ret.value))
    {
      char const *decimal_point = strchr (arg, '.');
      if (! decimal_point)
        ret.precision = 0;
      else
        {
          size_t fraction_len = strcspn (decimal_point + 1, "eE");
          if (fraction_len <= INT_MAX)
            ret.precision = fraction_len;
          ret.width += (fraction_len == 0                      /* #.  -> #   */
                        ? -1
                        : (decimal_point == arg                /* .#  -> 0.# */
                           || ! ISDIGIT (decimal_point[-1]))); /* -.# -> 0.# */
        }
      char const *e = strchr (arg, 'e');
      if (! e)
        e = strchr (arg, 'E');
      if (e)
        {
          long exponent = strtol (e + 1, NULL, 10);
          ret.precision += exponent < 0 ? -exponent : 0;
          /* Don't account for e.... in the width since this is not output.  */
          ret.width -= strlen (arg) - (e - arg);
          /* Adjust the width as per the exponent.  */
          if (exponent < 0)
            {
              if (decimal_point)
                {
                  if (e == decimal_point + 1) /* undo #. -> # above  */
                    ret.width++;
                }
              else
                ret.width++;
              exponent = -exponent;
            }
          ret.width += exponent;
        }
    }

  return ret;
}

/* If FORMAT is a valid printf format for a double argument, return
   its long double equivalent, allocated from dynamic storage, and
   store into *LAYOUT a description of the output layout; otherwise,
   report an error and exit.  */

static char const *
long_double_format (char const *fmt, struct layout *layout)
{
  size_t i;
  size_t prefix_len = 0;
  size_t suffix_len = 0;
  size_t length_modifier_offset;
  bool has_L;

  for (i = 0; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i += (fmt[i] == '%') + 1)
    {
      if (!fmt[i])
        error (EXIT_FAILURE, 0,
               _("format %s has no %% directive"), quote (fmt));
      prefix_len++;
    }

  i++;
  i += strspn (fmt + i, "-+#0 '");
  i += strspn (fmt + i, "0123456789");
  if (fmt[i] == '.')
    {
      i++;
      i += strspn (fmt + i, "0123456789");
    }

  length_modifier_offset = i;
  has_L = (fmt[i] == 'L');
  i += has_L;
  if (fmt[i] == '\0')
    error (EXIT_FAILURE, 0, _("format %s ends in %%"), quote (fmt));
  if (! strchr ("efgaEFGA", fmt[i]))
    error (EXIT_FAILURE, 0,
           _("format %s has unknown %%%c directive"), quote (fmt), fmt[i]);

  for (i++; ; i += (fmt[i] == '%') + 1)
    if (fmt[i] == '%' && fmt[i + 1] != '%')
      error (EXIT_FAILURE, 0, _("format %s has too many %% directives"),
             quote (fmt));
    else if (fmt[i])
      suffix_len++;
    else
      {
        size_t format_size = i + 1;
        char *ldfmt = xmalloc (format_size + 1);
        memcpy (ldfmt, fmt, length_modifier_offset);
        ldfmt[length_modifier_offset] = 'L';
        strcpy (ldfmt + length_modifier_offset + 1,
                fmt + length_modifier_offset + has_L);
        layout->prefix_len = prefix_len;
        layout->suffix_len = suffix_len;
        return ldfmt;
      }
}

/* Actually print the sequence of numbers in the specified range, with the
   given or default stepping and format.  */

static void
print_numbers (char const *fmt, struct layout layout,
               long double first, long double step, long double last)
{
  bool out_of_range = (step < 0 ? first < last : last < first);

  if (! out_of_range)
    {
      long double x = first;
      long double i;

      for (i = 1; ; i++)
        {
          long double x0 = x;
          printf (fmt, x);
          if (out_of_range)
            break;
          x = first + i * step;
          out_of_range = (step < 0 ? x < last : last < x);

          if (out_of_range)
            {
              /* If the number just past LAST prints as a value equal
                 to LAST, and prints differently from the previous
                 number, then print the number.  This avoids problems
                 with rounding.  For example, with the x86 it causes
                 "seq 0 0.000001 0.000003" to print 0.000003 instead
                 of stopping at 0.000002.  */

              bool print_extra_number = false;
              long double x_val;
              char *x_str;
              int x_strlen;
              setlocale (LC_NUMERIC, "C");
              x_strlen = asprintf (&x_str, fmt, x);
              setlocale (LC_NUMERIC, "");
              if (x_strlen < 0)
                xalloc_die ();
              x_str[x_strlen - layout.suffix_len] = '\0';

              if (xstrtold (x_str + layout.prefix_len, NULL, &x_val, c_strtold)
                  && x_val == last)
                {
                  char *x0_str = NULL;
                  if (asprintf (&x0_str, fmt, x0) < 0)
                    xalloc_die ();
                  print_extra_number = !STREQ (x0_str, x_str);
                  free (x0_str);
                }

              free (x_str);
              if (! print_extra_number)
                break;
            }

          fputs (separator, stdout);
        }

      fputs (terminator, stdout);
    }
}

/* Return the default format given FIRST, STEP, and LAST.  */
static char const *
get_default_format (operand first, operand step, operand last)
{
  static char format_buf[sizeof "%0.Lf" + 2 * INT_STRLEN_BOUND (int)];

  int prec = MAX (first.precision, step.precision);

  if (prec != INT_MAX && last.precision != INT_MAX)
    {
      if (equal_width)
        {
          /* increase first_width by any increased precision in step */
          size_t first_width = first.width + (prec - first.precision);
          /* adjust last_width to use precision from first/step */
          size_t last_width = last.width + (prec - last.precision);
          if (last.precision && prec == 0)
            last_width--;  /* don't include space for '.' */
          if (last.precision == 0 && prec)
            last_width++;  /* include space for '.' */
          if (first.precision == 0 && prec)
            first_width++;  /* include space for '.' */
          size_t width = MAX (first_width, last_width);
          if (width <= INT_MAX)
            {
              int w = width;
              sprintf (format_buf, "%%0%d.%dLf", w, prec);
              return format_buf;
            }
        }
      else
        {
          sprintf (format_buf, "%%.%dLf", prec);
          return format_buf;
        }
    }

  return "%Lg";
}

/* The NUL-terminated string S0 of length S_LEN represents a valid
   non-negative decimal integer.  Adjust the string and length so
   that the pair describe the next-larger value.  */
static void
incr (char **s0, size_t *s_len)
{
  char *s = *s0;
  char *endp = s + *s_len - 1;

  do
    {
      if ((*endp)++ < '9')
        return;
      *endp-- = '0';
    }
  while (endp >= s);
  *--(*s0) = '1';
  ++*s_len;
}

/* Compare A and B (each a NUL-terminated digit string), with lengths
   given by A_LEN and B_LEN.  Return +1 if A < B, -1 if B < A, else 0.  */
static int
cmp (char const *a, size_t a_len, char const *b, size_t b_len)
{
  if (a_len < b_len)
    return -1;
  if (b_len < a_len)
    return 1;
  return (strcmp (a, b));
}

/* Trim leading 0's from S, but if S is all 0's, leave one.
   Return a pointer to the trimmed string.  */
static char const * _GL_ATTRIBUTE_PURE
trim_leading_zeros (char const *s)
{
  char const *p = s;
  while (*s == '0')
    ++s;

  /* If there were only 0's, back up, to leave one.  */
  if (!*s && s != p)
    --s;
  return s;
}

/* Print all whole numbers from A to B, inclusive -- to stdout, each
   followed by a newline.  If B < A, return false and print nothing.
   Otherwise, return true.  */
static bool
seq_fast (char const *a, char const *b)
{
  /* Skip past any leading 0's.  Without this, our naive cmp
     function would declare 000 to be larger than 99.  */
  a = trim_leading_zeros (a);
  b = trim_leading_zeros (b);

  size_t p_len = strlen (a);
  size_t q_len = strlen (b);
  size_t n = MAX (p_len, q_len);
  char *p0 = xmalloc (n + 1);
  char *p = memcpy (p0 + n - p_len, a, p_len + 1);
  char *q0 = xmalloc (n + 1);
  char *q = memcpy (q0 + n - q_len, b, q_len + 1);

  bool ok = cmp (p, p_len, q, q_len) <= 0;
  if (ok)
    {
      /* Buffer at least this many numbers per fwrite call.
         This gives a speed-up of more than 2x over the unbuffered code
         when printing the first 10^9 integers.  */
      enum {N = 40};
      char *buf = xmalloc (N * (n + 1));
      char const *buf_end = buf + N * (n + 1);

      char *z = buf;

      /* Write first number to buffer.  */
      z = mempcpy (z, p, p_len);

      /* Append separator then number.  */
      while (cmp (p, p_len, q, q_len) < 0)
        {
          *z++ = *separator;
          incr (&p, &p_len);
          z = mempcpy (z, p, p_len);
          /* If no place for another separator + number then
             output buffer so far, and reset to start of buffer.  */
          if (buf_end - (n + 1) < z)
            {
              fwrite (buf, z - buf, 1, stdout);
              z = buf;
            }
        }

      /* Write any remaining buffered output, and the terminator.  */
      *z++ = *terminator;
      fwrite (buf, z - buf, 1, stdout);

      IF_LINT (free (buf));
    }

  free (p0);
  free (q0);
  return ok;
}

/* Return true if S consists of at least one digit and no non-digits.  */
static bool _GL_ATTRIBUTE_PURE
all_digits_p (char const *s)
{
  size_t n = strlen (s);
  return ISDIGIT (s[0]) && n == strspn (s, "0123456789");
}

int
main (int argc, char **argv)
{
  int optc;
  operand first = { 1, 1, 0 };
  operand step = { 1, 1, 0 };
  operand last;
  struct layout layout = { 0, 0 };

  /* The printf(3) format used for output.  */
  char const *format_str = NULL;

  initialize_main (&argc, &argv);
  set_program_name (argv[0]);
  setlocale (LC_ALL, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);

  atexit (close_stdout);

  equal_width = false;
  separator = "\n";

  /* We have to handle negative numbers in the command line but this
     conflicts with the command line arguments.  So explicitly check first
     whether the next argument looks like a negative number.  */
  while (optind < argc)
    {
      if (argv[optind][0] == '-'
          && ((optc = argv[optind][1]) == '.' || ISDIGIT (optc)))
        {
          /* means negative number */
          break;
        }

      optc = getopt_long (argc, argv, "+f:s:w", long_options, NULL);
      if (optc == -1)
        break;

      switch (optc)
        {
        case 'f':
          format_str = optarg;
          break;

        case 's':
          separator = optarg;
          break;

        case 'w':
          equal_width = true;
          break;

        case_GETOPT_HELP_CHAR;

        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);

        default:
          usage (EXIT_FAILURE);
        }
    }

  unsigned int n_args = argc - optind;
  if (n_args < 1)
    {
      error (0, 0, _("missing operand"));
      usage (EXIT_FAILURE);
    }

  if (3 < n_args)
    {
      error (0, 0, _("extra operand %s"), quote (argv[optind + 3]));
      usage (EXIT_FAILURE);
    }

  if (format_str)
    format_str = long_double_format (format_str, &layout);

  if (format_str != NULL && equal_width)
    {
      error (0, 0, _("format string may not be specified"
                     " when printing equal width strings"));
      usage (EXIT_FAILURE);
    }

  /* If the following hold:
     - no format string, [FIXME: relax this, eventually]
     - integer start (or no start)
     - integer end
     - increment == 1 or not specified [FIXME: relax this, eventually]
     then use the much more efficient integer-only code.  */
  if (all_digits_p (argv[optind])
      && (n_args == 1 || all_digits_p (argv[optind + 1]))
      && (n_args < 3 || (STREQ ("1", argv[optind + 1])
                         && all_digits_p (argv[optind + 2])))
      && !equal_width && !format_str && strlen (separator) == 1)
    {
      char const *s1 = n_args == 1 ? "1" : argv[optind];
      char const *s2 = argv[optind + (n_args - 1)];
      if (seq_fast (s1, s2))
        exit (EXIT_SUCCESS);

      /* Upon any failure, let the more general code deal with it.  */
    }

  last = scan_arg (argv[optind++]);

  if (optind < argc)
    {
      first = last;
      last = scan_arg (argv[optind++]);

      if (optind < argc)
        {
          step = last;
          last = scan_arg (argv[optind++]);
        }
    }

  if (first.precision == 0 && step.precision == 0 && last.precision == 0
      && 0 <= first.value && step.value == 1 && 0 <= last.value
      && !equal_width && !format_str && strlen (separator) == 1)
    {
      char *s1;
      char *s2;
      if (asprintf (&s1, "%0.Lf", first.value) < 0)
        xalloc_die ();
      if (asprintf (&s2, "%0.Lf", last.value) < 0)
        xalloc_die ();

      if (seq_fast (s1, s2))
        {
          IF_LINT (free (s1));
          IF_LINT (free (s2));
          exit (EXIT_SUCCESS);
        }

      free (s1);
      free (s2);
      /* Upon any failure, let the more general code deal with it.  */
    }

  if (format_str == NULL)
    format_str = get_default_format (first, step, last);

  print_numbers (format_str, layout, first.value, step.value, last.value);

  exit (EXIT_SUCCESS);
}

He has a separate function for dealing with just integers. I wanted to do something similar and wanted to make sure it was best optimized so that it would convert floats to ints if it can.

I also had to be able to distinguish a string from a number rather it be in scientific notation, engineer notation or decimal.

it was replaced with the dec() function and I guess never removed? :lolg:
 
He has a separate function for dealing with just integers

That was written in C, yours is written in Python. Various languages have different anomalies. Converting a float to an int can be done like this:
Code:
[plain]int(f)[/plain]

Although I'm not seeing a function that deals with integers in that C code compared to the way you were dealing with them. :S I see him checking c-style strings for numeric digits, but that's it. Which function were you referring to? I assumed it was this:
Code:
[plain]static bool _GL_ATTRIBUTE_PURE
all_digits_p (char const *s)
{
  size_t n = strlen (s);
  return ISDIGIT (s[0]) && n == strspn (s, "0123456789");
}[/plain]
 
No the seq_fast function is a sped up version because it uses only ints.

and yes I know that is written in C, that whole suite is a mix of C, C++, pearl, etc

My teacher just decided to make us do it in python for uhhh... the fun?
 
Did some slight restructuring and fixed a few bugs:

Code:
#!/usr/bin/python3
# Copyright © 2013 Bart Massey & Geoff Maggi

# CL1 of the sequ command:
#	Is equivalent to the coreutils seq command
#
#	Spec change:
#		on two entry runs the precision is set to the input with the highest precision. (Coreutils sets it to the first number's precision)
#		If more than 3 terms are put in ignore them and just use the first 3
#		-f overrides -w and -s, as it specifies it's own format

import sys, argparse, codecs
from decimal import Decimal
	
def dec(string):
	try:
		value = Decimal(string)
	except TypeError:
		dec(float(string))
		raise argparse.ArgumentTypeError("not valid input")
		sys.exit(1)
	except Exception:
		print("Not a valid input")
		print("\nCheck --help for more information on inputs")
		sys.exit(1)
	return value
	
def numDecimals(x): #finds the number of terms after the decimal
	x = Decimal(x).as_tuple().exponent
	if(x<0):
		return(-x)
	else:
		return(0)
		
def findWidth(curTerm, step, finalTerm, precision):
	width = 1 #set width to 1 by default
	curTerm = len(formatTerm(curTerm,precision,width))
	step = len(formatTerm(step,precision,width))
	finalTerm = len(formatTerm(finalTerm,precision,width))
	return max(curTerm,step,finalTerm) #return max width
		
def formatTerm(term, precision, width):
	return '{0:0{fill}.{prec}f}'.format(term, prec=precision, fill=width)
	
def printNormal(term, precision, width, separator):
	print(formatTerm(curTerm,precision,width), end=separator)
	
def printCustom(format, curTerm):
	try:
		print(format % curTerm, end='')
	except Exception:
		print("Not a valid format")
		print("\nCheck --help for more information on formats")
		sys.exit(1)
	
parser = argparse.ArgumentParser(description='Sequ, prints a sequence')
parser.add_argument('-f','--format', help='Use printf style floating-point') #if not specified/empty assume default format
parser.add_argument('-s','--separator', help='Use STRING to separate numbers', default=None) #if not specified assume default format
parser.add_argument('-v','--version', help='Displays version information', action='store_true')
parser.add_argument('-w','--equal-width', help='Equalize width by padding with leading zeroes', action='store_true', dest='width')
parser.add_argument('vals', help='Values to create a sequence from start step end', nargs='*') #0 or more as I catch 0 cases myself
args = parser.parse_args()

if(args.version):
	print("sequ.py 0.3")
	print("Copyright (c) 2013 Bart Massey & Geoff Maggi")
	sys.exit(0) #Exit with good status!

#Default values, overwritten later if needed.
precision = 0
width = 1
separator = '\n'

if(args.separator != None): #(!= None fixes issue with empty separator)
	separator = codecs.getdecoder("unicode_escape")(args.separator)[0] #fixes parsing error where strings were automatically encoding, messing up escape chars
if(args.format):
	format = codecs.getdecoder("unicode_escape")(args.format)[0] #same as above

if(len(args.vals) > 2): #All args set, custom increment
	curTerm = dec(args.vals[0])
	step = dec(args.vals[1])
	finalTerm = dec(args.vals[2])
	precision = numDecimals(args.vals[1]) #precision of step, used for output
	
if(len(args.vals) == 2):
	curTerm = dec(args.vals[0])
	step = 1
	finalTerm = dec(args.vals[1])
	p0 = numDecimals(args.vals[0]) #precision of first num
	p1 = numDecimals(args.vals[1]) #precision of second num
	precision = max(p0,p1)
	
if(len(args.vals) == 1):
	curTerm = 1
	step = 1
	finalTerm = dec(args.vals[0])
	precision = numDecimals(args.vals[0]) #precision of input, have output match
	
if(len(args.vals) == 0):
	sys.exit(1) #throw some basic error
	
if((curTerm>finalTerm and step>0) or (curTerm<finalTerm and step<0) or step==0):
	sys.exit(0) #Exit and print nothing if it is impossible to get from curTerm to FinalTerm with step.

if(args.width):
	width = findWidth(curTerm,step,finalTerm, precision) #find the width given all of the terms
	
#Printing everything, needs cleaning.... Mostly for format, lots of dup code	
if(curTerm!=finalTerm): #fixes bug for 1 1 1 or 1 -1 1 being an inf loop
	if(curTerm<finalTerm): #counting up
		while(curTerm<=(finalTerm-step)):
			if(args.format):
				printCustom(args.format, curTerm)
			else:
				printNormal(curTerm,precision,width,separator)
			curTerm+=step	
			
	else: #counting down
		while(curTerm>=(finalTerm-step)):
			if(args.format):
				printCustom(args.format, curTerm)
			else:
				printNormal(curTerm,precision,width,separator)
			curTerm+=step
		
if(args.format):
	printCustom(args.format, curTerm)
else:
	printNormal(curTerm,precision,width,'') #print the last term without the separator!
		
sys.exit(0)
 
I don't want to sound like a dick, but you aren't supposed to use parentheses in if statements. They make it look kind of weird if you are accustomed to python :P
 
I don't want to sound like a dick, but you aren't supposed to use parentheses in if statements. They make it look kind of weird if you are accustomed to python :P

No, just like VB, they are not required in Python but optional if you want them.
 
Last edited:
I don't want to sound like a dick, but you aren't supposed to use parentheses in if statements. They make it look kind of weird if you are accustomed to python :P

Shows that I am a primarily C/C++/PHP programmer :lolg:

Also: you're not being a dick, thank you for the information! :wave:
 
Latest revision:
Code:
#!/usr/bin/python3
# Copyright © 2013 Bart Massey & Geoff Maggi

# CL2 of the sequ command
#
#	Spec change:
#		on two entry runs the precision is set to the input with the highest precision. (Coreutils sets it to the first number's precision)
#		If more than 3 terms are put in ignore them and just use the first 3
#		-f overrides -w and -s, as it specifies it's own format

import sys, argparse, codecs
from decimal import Decimal
	
def dec(string):
	try:
		value = Decimal(string)
	except TypeError:
		dec(float(string))
		raise argparse.ArgumentTypeError("not valid input")
		sys.exit(1)
	except Exception:
		print("Not a valid input")
		print("\nCheck --help for more information on inputs")
		sys.exit(1)
	return value
	
def numDecimals(x): #finds the number of terms after the decimal
	x = Decimal(x).as_tuple().exponent
	if x<0:
		return(-x)
	else:
		return(0)
		
def findWidth(curTerm, step, finalTerm, precision):
	curTerm = len(formatTerm(curTerm,precision))
	step = len(formatTerm(step,precision))
	finalTerm = len(formatTerm(finalTerm,precision))
	return max(curTerm,step,finalTerm) #return max width
		
def formatTerm(term, precision, width=1, pad=' '):
	return '{0:.{prec}f}'.format(term, prec=precision).rjust(width, pad)
	
def printNormal(term, precision, width, separator,pad):
	print(formatTerm(curTerm,precision,width,pad), end=separator)
	
def printCustom(format, curTerm):
	try:
		print(format % curTerm, end='')
	except Exception:
		print("Not a valid format")
		print("\nCheck --help for more information on formats")
		sys.exit(1)
	
parser = argparse.ArgumentParser(description='Sequ, prints a sequence')
parser.add_argument('-f','--format', help='Use printf style floating-point') #if not specified/empty assume default format
parser.add_argument('-s','--separator', help='Use STRING to separate numbers', default=None) #if not specified assume default format
parser.add_argument('-v','--version', help='Displays version information', action='store_true')
parser.add_argument('-w','--equal-width', help='Equalize width by padding with leading zeroes', action='store_true', dest='width')
parser.add_argument('-W','--words', help='Prints sequence with a space separator', action='store_true')
parser.add_argument('-p','--pad', help='Equalize width by padding with leading custom char', default=None)
parser.add_argument('-P','--pad-spaces', help='Equalize width by padding with leading spaces', action='store_true', dest='pad_spaces')
parser.add_argument('vals', help='Values to create a sequence from start step end', nargs='*') #0 or more as I catch 0 cases myself
args = parser.parse_args()

if args.version:
	print("sequ.py 0.4")
	print("Copyright (c) 2013 Bart Massey & Geoff Maggi")
	sys.exit(0) #Exit with good status!

#Default values, overwritten later if needed.
precision = 0
width = 1
separator = '\n'
pad = '0'

if args.separator != None: #(!= None fixes issue with empty separator)
	separator = args.separator.encode().decode("unicode_escape", "ignore") #fixes parsing error where strings were automatically encoding, messing up escape chars
if args.format:
	format = args.format.encode().decode("unicode_escape", "ignore") #same as above
if args.words:
	separator = ' '
if args.pad != None:
	pad = args.pad.encode().decode("unicode_escape", "ignore") #same as above
	if len(pad)!=1:
		print('Pad character must be exactly length 1')
		sys.exit(1)
	args.width=True
if args.pad_spaces:
	pad = ' '
	args.width=True
	
if len(args.vals) > 2: #All args set, custom increment
	curTerm = dec(args.vals[0])
	step = dec(args.vals[1])
	finalTerm = dec(args.vals[2])
	precision = numDecimals(args.vals[1]) #precision of step, used for output
	
if len(args.vals) == 2:
	curTerm = dec(args.vals[0])
	step = 1
	finalTerm = dec(args.vals[1])
	p0 = numDecimals(args.vals[0]) #precision of first num
	p1 = numDecimals(args.vals[1]) #precision of second num
	precision = max(p0,p1)
	
if len(args.vals) == 1:
	curTerm = 1
	step = 1
	finalTerm = dec(args.vals[0])
	precision = numDecimals(args.vals[0]) #precision of input, have output match
	
if len(args.vals) == 0:
	sys.exit(1) #throw some basic error
	
if (curTerm>finalTerm and step>0) or (curTerm<finalTerm and step<0) or step==0:
	sys.exit(0) #Exit and print nothing if it is impossible to get from curTerm to FinalTerm with step.

if args.width:
	width = findWidth(curTerm,step,finalTerm, precision) #find the width given all of the terms
	
#Printing everything, needs cleaning.... Mostly for format, lots of dup code	
if curTerm!=finalTerm: #fixes bug for 1 1 1 or 1 -1 1 being an inf loop
	if curTerm<finalTerm: #counting up
		while curTerm<=(finalTerm-step):
			if args.format:
				printCustom(args.format, curTerm)
			else:
				printNormal(curTerm,precision,width,separator,pad)
			curTerm+=step	
			
	else: #counting down
		while curTerm>=(finalTerm-step):
			if args.format:
				printCustom(args.format, curTerm)
			else:
				printNormal(curTerm,precision,width,separator,pad)
			curTerm+=step
		
if args.format:
	printCustom(args.format, curTerm)
else:
	printNormal(curTerm,precision,width,'',pad) #print the last term without the separator!
		
sys.exit(0)

Any other oddities you guys see? :grin1:
 
I'm using a different version (Python 2), so I've got different syntax that would dictate you have an invalid python script. Mainly line #44:
PwtpwZZ.png


Still things can be done, I haven't looked further, but for instance:
Code:
[NO-PARSE]def dec(s):
	dec_found = False
	valid = "0123456789"
	for x in range(0,len(s)):
		if not s[x] in valid:
			if s[x] == "." and not dec_found: dec_found = True
			else: return None
	return Decimal(s)


result = dec("32.3")
if result != None:
	print("Proper value", result)
else:
	print("Invalid input...")[/NO-PARSE]

Maybe you want to check for a type of string however, so use something like this:
Code:
[plain]if type(s) == str[/plain]

Keep in mind, if you convert a floating point datatype to a Decimal, you'll get some strange values. Keep in the back of your mind, my Floating Point Chaos thread I posted a while back on this forum...

32.3 may result as 32.2999999999999971578290569595992565155029296875

You'll have to check for invalid types of course, such as tuples and lists, etc... But now if you had commented this function with assumptions, that would be the ignorance of the programmer for using the function improperly.

You should comment your def's:
Code:
[NO-PARSE]def dec(s):
	'''
	summary: converts variable s to a Decimal
	assumes: s is a convertable value, and not a list, tuple, etc...
	'''[/NO-PARSE]

What you leave in those comments is based on programmer discretion really. I know some Python programmers who explain the expected types of each param, and what each param does and is used for, with a short and concise summary of the function itself. Just good programming habits.

I'm still a novice in Python though.
 
Last edited:
Yes it is python 3, we had the option to use 2 or 3. He suggested 3 so I went with that. (2 and 3 are considered different programming languages)

This might help you understand why I picked decimal over floats: 9.4. decimal ? Decimal fixed point and floating point arithmetic ? Python v2.7.6 documentation

mainly as you stated as a concern above:

Decimal numbers can be represented exactly.

The exactness carries over into arithmetic. In decimal floating point, 0.1 + 0.1 + 0.1 - 0.3 is exactly equal to zero. In binary floating point, the result is 5.5511151231257827e-017.

I struggled with floats at first using this program then found decimal and decided it would make my program 1000000x times easier.

Additionally, you're right I do need block comments for my functions and confusing parts of my code.

I decided not to include them at first because things were so volatile. I have used this same code for some time now so I should probably include them.

Also, thank you for your utf-8 comment. I agree that I should add it.
 
Yeah, I know why you chose Decimal (or at least I know why it is a better idea, but wasn't sure if you knew about floating point datatypes). I wrote this thread here explaining a few things: https://www.sysnative.com/forums/programming/6077-floating-point-chaos.html. However, this still doesn't avoid the issues of conversion to other datatypes from floating point datatypes, so that is something you should be careful of.
 
Yeah, I know why you chose Decimal (or at least I know why it is a better idea, but wasn't sure if you knew about floating point datatypes). I wrote this thread here explaining a few things: https://www.sysnative.com/forums/programming/6077-floating-point-chaos.html. However, this still doesn't avoid the issues of conversion to other datatypes from floating point datatypes, so that is something you should be careful of.

Thanks for the heads up, I already know the pains of floating points. I should be able to completely avoid them with any luck with my program.
 
Term ends Monday, here is my first go and "Production Ready" code:

Code:
#!/usr/bin/python3
# Copyright (c) 2013 Bart Massey & Geoff Maggi
# Documentation and Repository: https://projects.cecs.pdx.edu/projects/masseycs300-gmaggi

#Current Version: 1.0 (CL4)

import sys, argparse, re
from decimal import Decimal
	
#Converts given string to decimal, if it cannot, throws error and exits.
def dec(string):
	try:
		value = Decimal(string)
	except Exception:
		print(string, "is not a valid input")
		print("\nCheck --help for more information on input values")
		sys.exit(1)
	return value

#finds the number of terms after the decimal	
def numDecimals(x):
	x = Decimal(x).as_tuple().exponent
	if x<0:
		return(-x)
	else:
		return(0)

#Format term based off given precision,width and pad
def formatTerm(term, precision, width=1, pad=' '):
	return '{0:.{prec}f}'.format(term, prec=precision).rjust(width, pad)
	
#Find Width of longest term and return it.
def findWidth(cur_term, step, final_term, precision):
	cur_term = len(formatTerm(cur_term,precision))
	step = len(formatTerm(step,precision))
	final_term = len(formatTerm(final_term,precision))
	return max(cur_term,step,final_term) #return max width

def printNormal(term, precision, width, separator,pad):
	print(formatTerm(term,precision,width,pad), end=separator)

#Custom print specified by the -f command
def printCustom(format, cur_term):
	try:
		print(format % cur_term, end='')
	except Exception:
		print("Not a valid format")
		print("\nCheck --help for more information on formats")
		sys.exit(1)
		
#Prints a term given all the information in the correct format
def printTerm(term, precision, width, separator, pad, format, format_word):
	if format:
		printCustom(format, cur_term)
	elif format_word in ['alpha','ALPHA']:
		print(chr(cur_term), end=separator)
	elif format_word == "roman":
		print(to_roman(cur_term).lower(), end=separator)
	elif format_word == "ROMAN":
		print(to_roman(cur_term), end=separator)
	else:
		printNormal(cur_term,precision,width,separator,pad)

#Get output format for the -F command
def getWordFormat(format_word, final_term):
	goodvals = ['arabic', 'floating', 'alpha', 'ALPHA', 'roman', 'ROMAN']
	if not (len(format_word)==0 or format_word in goodvals): #If word is not empty or not in the last of good values
		print("Format word(-F) is not Valid")
		sys.exit(1)
	if len(format_word) == 0:
		if len(final_term)==1 and final_term.isalpha(): #From last value we have determined that it is alpha/ALPHA, all other we assume float
			final_term = ord(final_term) #convert it to an int
			if final_term >= ord('A') and final_term <= ord('Z'):
				return "ALPHA"
			elif final_term >= ord('a') and final_term <= ord('z'):
				return "alpha"
			else:
				sys.exit(1) #How did we get here?
		else:
			return "floating"
	return format_word
	
##Based off Mark Pilgrim's 'Dive Into Python 3': http://www.diveintopython3.net/unit-testing.html
roman_numeral_map = (('M',1000),('CM',900),('D',500),('CD',400),('C',100),('XC',90),('L',50),('XL',40),('X',10),('IX',9),('V',5),('IV',4),('I',1))
roman_numeral_pattern = re.compile('''^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$''', re.VERBOSE) #Regex of all valid ROMANs
#Converts ints to romans
def to_roman(n):
	if not (0 < n < 4000):
		print("Roman numeral out of range. Valid Range: (1..3999)")
		sys.exit(1)
	if not isinstance(n, int):                                          
		print("Could not cast",n,"to Roman numeral")
		sys.exit(1)

	result = ''
	for numeral, integer in roman_numeral_map:
		while n >= integer:
			result += numeral
			n -= integer
	return result

#Converts strings to int or romans
def from_roman(s):
	try:
		result = int(s)
		if not (0 < result < 4000):
			print("Roman numeral out of range. Valid Range: (1..3999)")
			sys.exit(1)
	except Exception:
		if not roman_numeral_pattern.search(s):
			print(s,"is not a valid Roman numeral!")
			sys.exit(1)
		result = 0
		index = 0
		for numeral, integer in roman_numeral_map:
			while s[index : index + len(numeral)] == numeral:
				result += integer
				index += len(numeral)
	return result
	
parser = argparse.ArgumentParser(description='Sequ, prints a sequence, Copyright (c) 2013 Bart Massey & Geoff Maggi, https://projects.cecs.pdx.edu/projects/masseycs300-gmaggi')
parser.add_argument('-f','--format', help='Use printf style floating-point') #if not specified/empty assume default format
parser.add_argument('-F','--format-word', help='Output the string as arabic, floating, alpha, ALPHA, roman or ROMAN', dest='format_word', default=None)
parser.add_argument('-n','--number_lines', help='Appends line numbers to a given file.', dest='file_name', default=None)
parser.add_argument('-p','--pad', help='Equalize width by padding with leading custom char', default=None)
parser.add_argument('-P','--pad-spaces', help='Equalize width by padding with leading spaces', action='store_true', dest='pad_spaces')
parser.add_argument('-s','--separator', help='Use STRING to separate numbers', default=None)
parser.add_argument('-v','--version', help='Displays version information', action='store_true')
parser.add_argument('-w','--equal-width', help='Equalize width by padding with leading zeroes', action='store_true', dest='width')
parser.add_argument('-W','--words', help='Prints sequence with a space separator', action='store_true')
parser.add_argument('vals', help='Values to create a sequence from start, step, end (one term required)', nargs='*') #0 or more as I catch 0 cases myself
args = parser.parse_args()

if(args.version):
	print("sequ.py 1.0")
	print("Copyright (c) 2013 Bart Massey & Geoff Maggi")
	sys.exit(0) #Exit with good status!

#Default values, overwritten later if needed.
precision = 0
width = 1
if args.file_name != None: #If -n default separator is " " as per spec
	separator = ' '
else:
	separator = '\n'
pad = '0'
format_word = None

if args.separator != None: #!= None fixes issue with empty separator
	separator = args.separator.encode().decode("unicode_escape", "ignore") #fixes parsing error where strings were automatically encoding, messing up escape chars
if args.format:
	format = args.format.encode().decode("unicode_escape", "ignore") #same as above
if args.words:
	separator = ' '
if args.pad != None:
	pad = args.pad.encode().decode("unicode_escape", "ignore") #same as above
	if len(pad)!=1:
		print('Pad character must be exactly length 1')
		sys.exit(1)
	args.width=True
if args.pad_spaces:
	pad = ' '
	args.width=True
	
if len(args.vals) > 2: #All args set, custom increment
	if args.file_name != None:
		print("Too many values, An end argument for -n is not allowed")
		sys.exit(1)
	if args.format_word != None:
		format_word = getWordFormat(args.format_word, args.vals[2])
	if format_word in ['alpha','ALPHA']:
		if args.vals[0].isalpha():
			cur_term = ord(args.vals[0])
		else:
			print("Start value is not a alpha")
			sys.exit(1)
		if args.vals[2].isalpha():
			final_term = ord(args.vals[2])
		else:
			print("End value is not a alpha")
			sys.exit(1)
		if format_word == "ALPHA" and not (cur_term <= ord('Z') and final_term <= ord('Z')): #checks to see if they are both ALPHA
			print("start and final term must both be upper case")
			sys.exit(1)
		if format_word == "alpha" and not (cur_term >= ord('a') and final_term >= ord('a')): #checks to see if they are both alpha
			print("start and final term must both be lower case")
			sys.exit(1)
		try:
			step = int(args.vals[1])
		except Exception:
			print("The increment value must be Arabic!")
			sys.exit(1)
	elif format_word in ['roman','ROMAN']:
		cur_term = from_roman(args.vals[0].upper())
		final_term = from_roman(args.vals[2].upper())
		try:
			step = int(args.vals[1])
		except Exception:
			print("The increment value must be Arabic!")
			sys.exit(1)
	else:
		cur_term = dec(args.vals[0])
		step = dec(args.vals[1])
		final_term = dec(args.vals[2])
		precision = numDecimals(args.vals[1]) #precision of step, used for output
	
if len(args.vals) == 2:
	step = 1
	if args.format_word != None:
		format_word = getWordFormat(args.format_word, args.vals[1])
	if format_word in ['alpha','ALPHA']:
		if args.vals[0].isalpha():
			cur_term = ord(args.vals[0])
		else:
			print("Start value is not a alpha")
			sys.exit(1)
		if args.vals[1].isalpha():
			final_term = ord(args.vals[1])
		else:
			print("End value is not a alpha")
			sys.exit(1)
		if format_word == "ALPHA" and not (cur_term <= ord('Z') and final_term <= ord('Z')): #checks to see if they are both ALPHA
			print("start and final term must both be upper case")
			sys.exit(1)
		if format_word == "alpha" and not (cur_term >= ord('a') and final_term >= ord('a')): #checks to see if they are both alpha
			print("start and final term must both be lower case")
			sys.exit(1)
	elif format_word in ['roman','ROMAN']:
		cur_term = from_roman(args.vals[0].upper())
		final_term = from_roman(args.vals[1].upper())
	else:
		cur_term = dec(args.vals[0])
		final_term = dec(args.vals[1])
		p0 = numDecimals(args.vals[0]) #precision of first num
		p1 = numDecimals(args.vals[1]) #precision of second num
		precision = max(p0,p1)
	
if len(args.vals) == 1:
	cur_term = 1
	step = 1
	if args.format_word != None:
		format_word = getWordFormat(args.format_word, args.vals[0])
	if format_word in ['alpha','ALPHA']:
		if args.vals[0].isalpha():
			final_term = ord(args.vals[0])
		else:
			print("End value is not a alpha")
			sys.exit(1)
		if final_term <= ord('Z'):
			cur_term = ord('A')
		else:
			cur_term = ord('a')
	elif format_word in ['roman','ROMAN']:
		final_term = from_roman(args.vals[0].upper())
	else:
		final_term = dec(args.vals[0])
		precision = numDecimals(args.vals[0]) #precision of input, have output match
	
if len(args.vals) == 0:
	if args.file_name != None: #No args OK for -n
		cur_term = 1
		step = 1
	else:
		print("Need help? Try the -h command")
		sys.exit(1) #throw some basic error
	
if (cur_term>final_term and step>0) or (cur_term<final_term and step<0) or step==0:
	sys.exit(0) #Exit and print nothing if it is impossible to get from cur_term to final_term with step.

if format_word == 'arabic': #simple and clean way to deal with ints :)
	precision = 0
	
if args.width:
	width = findWidth(cur_term,step,final_term, precision) #find the width given all of the terms
	
if args.file_name != None:
	if len(args.vals)==2: #Hack so now the 2nd term in 2 term arguments is step not end
		step = final_term
	if len(args.vals)==1: #Hack so now the 1st term in 1 term args is the first term not end
		cur_term = final_term
	try:
		file = open(args.file_name, "r+" )
	except IOError:
	   print('File',args.file_name,'Not Found')
	   sys.exit(1)
	lines = []
	for line in file:
		lines.append(line)
	file.seek(0) #move back to the beginning of the file
	for line in lines:
		if args.format:
			try:
				number = format % cur_term
			except Exception:
				print("Not a valid format")
				print("\nCheck --help for more information on formats")
				sys.exit(1)
		elif format_word in ['alpha','ALPHA']:
			number = chr(cur_term)+separator
			if cur_term == ord('Z'): #fixes looping (Out of range)
				cur_term = ord('A') - 1
			if cur_term == ord('z'): #fixes looping (Out of range)
				cur_term = ord('a') - 1
			if cur_term == ord('A'): #fixes looping (Out of range)
				cur_term = ord('Z') + 1
			if cur_term == ord('a'): #fixes looping (Out of range)
				cur_term = ord('z') + 1
		elif format_word == "roman":
			number = to_roman(cur_term).lower()+separator
			if cur_term == 3999: #fixes looping (Out of range)
				cur_term = 0
			if cur_term == 0: #fixes looping (Out of range)
				cur_term = 4000
		elif format_word == "ROMAN":
			number = to_roman(cur_term)+separator
			if cur_term == 3999: #fixes looping (Out of range)
				cur_term = 0
			if cur_term == 0: #fixes looping (Out of range)
				cur_term = 4000
		else:
			number = formatTerm(cur_term,precision,width,pad)+separator
		file.write(number+line)
		cur_term = cur_term + step
	file.close()
	
else:	
	if cur_term!=final_term:
		if cur_term<final_term: #counting up
			while cur_term<=(final_term-step):
				printTerm(cur_term,precision,width,separator,pad,args.format,format_word)
				cur_term+=step	
				
		else: #counting down
			while cur_term>=(final_term-step):
				printTerm(cur_term,precision,width,separator,pad,args.format,format_word)
				cur_term+=step
	printTerm(cur_term,precision,width,'',pad,args.format,format_word) #prints the last term without the separator
		
sys.exit(0)
 

Attachments

Back
Top