Perl Weekly Challenge 135: Middle 3-digits

by Abigail

Challenge

You are given an integer.

Write a script find out the middle 3-digits of the given integer, if possible otherwise throw sensible error.

Example 1

Input: $n = 1234567
Output: 345

Example 2

Input: $n = -123
Output: 123

Example 3

Input: $n = 1
Output: too short

Example 4

Input: $n = 10
Output: even number of digits

Solution

We need to do a couple of things, all fairly trivial:

  1. Read a line from standard input.
  2. Remove a leading sign, as we can basically ignore it.
  3. Check we're left with only digits; error if not.
  4. Check we have an odd number of digits; error if not.
  5. Check we have at least three digits; error if not.
  6. Extract the middle three digits, and print them.

The third step technically isn't required, as it's a given we're getting an integer. But we check anyway.

Note that a two digit integer fails validation twice: it's too short, and it has an even number of digits. The fourth example shows we should give an error for the second failure (and not the first).

As usual, we will be accepting multiple integers, assuming one integer per line of input. For each integer, we'll print the middle three digits, or an error if necessary.

AWK

AWK is very suitable to repeatedly do something to a line of input, and to repeat this for each line of input.

We start off with stripping a leading sign, if any:

/^[-+]/         {$0 = substr ($0, 2)}

$0 holds the current line of input, and it's an lvalue, so we can assign to it.

We can now do the validation. If it fails a validation, we print an error message, and continue with the next line of input:

/[^0-9]/        {print "not an integer";        next}
/^(..)*$/       {print "even number of digits"; next}
length ($0) < 3 {print "too short";             next}

We can now print the middle three digits:

{print substr ($0, 1 + (length ($0) - 3) / 2, 3)}

substr in AWK takes three arguments: the string we want to extract a substring from; the position from where to start (first character is on position 1), and the length of the substring.

Find the full program on GitHub.

Bash

We first start off reading a line of input using read:

while read line; do

First, we strip off a leading sign, if any, using parameter expansion:

line=${line/#[-+]/}

This takes what is in the variable line, and performs a substitution: replacing #[-+] with nothing (because there is nothing following the second slash. Where most languages use ^ to indicate the beginning of a string, here, this role is played by #.

We now check whether we are left with just digits:

if [[ $line =~ [^0-9] ]]
then echo "not an integer"

[[ introduces Bash Conditional Expressions. The expression results in a true/false result. Here, we are matching line against a regular expression. Note that we don't need a delimiter here.

Checking for the right amount of digits:

elif ((${#line} % 2 == 0))
then echo "even number of digits"
elif ((${#line} < 3))
then echo "too short"

(( introduces Shell Arithmetic.

If we have passed all validations, we can print the right substring:

echo ${line:$(((${#line} - 3) / 2)):3}

We do this with several other parameter expansions.

${#line} is the first parameter expansion. The result is the length of the string in line.

$(((${#line} - 3) / 2)) takes what is inside $(( )) and treats it as an arithmetic expression. The result is the start index of substring we want.

We use another type of parameter expansion to get the substring: ${line:position:length} gets length characters from line, starting at position position.

Find the full program on GitHub.

C

We start off with reading a line from standard input. getline will do the job:

char *  line    = NULL;
size_t  len     = 0;
size_t  str_len;
while ((str_len = getline (&line, &len, stdin)) != -1) {

The string (with terminating newline) will be put into the character array line, while the return value of getline is the length of the line. (getline will do the mallocing for us, and populate len with the number of bytes allocated; we will not use len in our program).

We start with removing a leading sign, if any (if there is any space following the sign, we will remove this as well):

char * line_ptr = line;
if (* line_ptr == '-' || * line_ptr == '+') {
    str_len --;
    line_ptr ++;
    while (isspace (* line_ptr)) {
        line_ptr ++;
        str_len --;
    }
}

The result is that afterwards, line_ptr will point to the first character following the sign.

Next step is to make sure all the other characters are digits. Note that our string still contains a newline:

bool done = false;
char * line_ptr2 = line_ptr;
while (* line_ptr2 && * line_ptr2 != '\n') {
    if (!isdigit (* line_ptr2)) {
        printf ("not an integer\n");
        done = true;
        break;
    }
    line_ptr2 ++;
}
if (done) {
    continue;
}

C cannot break out of nested loops, so we determine the input contains a non-digit, we set a boolean, and do the continue after we're done with the inner loop.

We can now check we have the right amount of digits (while remembering we have a trailing newline):

if (str_len % 2 == 1) {
    printf ("even number of digits\n");
    continue;
}

if (str_len < 4) {
    printf ("too short\n");
    continue;
}

We can print the middle three digits, which we print one by one, followed by a newline:

size_t mid = (str_len - 3) / 2;
for (size_t i = 0; i < 3; i ++) {
    printf ("%c", line_ptr [mid + i]);
}
printf ("\n");

It's C, so, we shouldn't forget the free allocated memory:

free (line);

Find the full program on GitHub.

Go

First, we have to read a line from standard input:

    var reader = bufio . NewReader (os. Stdin)
main_loop:
    for {
        var text, err = reader . ReadString ('\n')
        if (err != nil) {
            break
        }

Stripping off the newline we do with TrimRight in the string module:

text = strings . TrimRight (text, "\n")

Go has immutable strings, so we have a leading sign, we take a slice from the string, and assign that back to the string:

if (text [0:1] == "-" || text [0:1] == "+") {
    text = text [1:]
}

We now check whether the string contains just digits. For that, we iterate over the runes of the string:

for _, rune := range text {
    if rune < '0' || rune > '9' {
        fmt . Print ("not an integer\n")
        continue main_loop
    }
}

We can now check for the correct number of digits. len returns the length of a string:

if len (text) % 2 == 0 {
    fmt . Print ("even number of digits\n")
    continue main_loop
}

if len (text) < 3 {
    fmt . Print ("too short\n")
    continue main_loop
}

Now we can print the required digits, using a slice:

fmt . Printf ("%s\n", text [(len (text) - 3) / 2 :
                            (len (text) + 3) / 2])

Find the full program on GitHub.

Java

Java requires even more work to be done to just read a line of input:

Scanner scanner = new Scanner (System . in);
try {
    while (true) {
        String line = scanner . nextLine ();

This time, we first check whether we have an integer, by using a regular expression. The matches function in the Pattern class tries to match the given pattern against the given string:

if (!Pattern . matches ("^[-+]?[0-9]+$", line)) {
    System . out . println ("not an integer");
    continue;
}

If the string starts with a sign, we get rid of it. Java strings are immutable, so we take a substring and assign it back to the string:

if (Pattern . matches ("^[-+].*", line)) {
    line = line . substring (1);
}

We can now check for the right amount of digits:

int ll = line . length ();
if (ll % 2 == 0) {
    System . out . println ("even number of digits");
    continue;
}
if (ll < 3) {
    System . out . println ("too short");
    continue;
}

Finally, we print the wanted digits:

System . out . println (line . substring ((ll - 3) / 2,
                                         (ll + 3) / 2));

Find the full program on GitHub.

Lua

First, we strip off the sign. line contains a line from standard input:

line = line : gsub ("^[-+]%s*", "")

Our set of validations:

if line : find ("%D") then
    print ("not an integer")
else
    if line : len () % 2 == 0 then
        print ("even number of digits")
    else
        if line : len () < 3 then
            print ("too short")

And the printing of the middle digits:

local start = 1 + (line : len () - 3) / 2
print (line : sub (start, start + 2))

Find the full program on GitHub.

Node.js

First, we strip off the sign and the trailing newline. line contains a line from standard input:

line . replace (/^[-+]/, '') . trim ()

replace takes two arguments, a pattern, and a replacement, while trim removes whitespace from either end of the string. Neither method changes a string — they return the modified string.

We now do the validations as a sequence of if/else statements:

if (line . match (/[^0-9]/)) {
    console . log ("not an integer")
}
else {
    if (line . length % 2 == 0) {
        console . log ("even number of digits")
    }
    else {
        if (line . length < 3) {
            console . log ("too short")
        }
    }
}

match takes a pattern as argument, and returns true if the pattern matches the string it's applied to.

Finally, the printing:

console . log (line . substr ((line . length - 3) / 2, 3))

Find the full program on GitHub.

Perl

Perl has powerful regexes: it has the ability to run code, and dynamically create sub-patterns.

We use this to validate the number, and select the middle digits at the same time; if it fails, we do additional checks, all using regular expressions to find out why it fails:

say /^[-+]?([0-9]*)([0-9]{3})([0-9]*)$
    (??{length ($1) == length ($3) ? "" : "(*FAIL)"})/x
                              ? $2
  : /^[-+]?[0-9]*[^0-9].*\n/  ? "not an integer"
  : /^[-+]?(?:[0-9][0-9])*\n/ ? "even number of digits"
  :                             "too short"

$_ contains the line of input (with a trailing newline). The first regular expression does the validation, and selecting of the middle digits. We can break down the expression as follows:

/^               # Match from the beginning
 [-+]?           # An optional sign
 ([0-9]*)        # Zero or more digits; capture group $1
 ([0-9]{3})      # Exactly three digits; capture group $2
 ([0-9]*)        # Zero or more digits; capture group $3
 $               # End of string
 (??{            # Run the following code, and treat its value
                 # as a sub-pattern to be matched at this point.
     length ($1) == length ($3)  # If capture groups $1 and $3 are same length:
             ? ""                # Match an empty string (always succeeds)
             : "(*FAIL)"         # Else fail (and backtrack)
 })/x

The latter part, forcing that the first and third capture group have the same number of digits, means that if the entire match succeeds, $2 contains the middle three digits: it must be the middle three, as the entire number (without sign) is $1$2$3, and $1 and $3 contain the same amount of digits.

If the match fails, we need to find out why it fails:

/^[-+]?[0-9]*[^0-9].*\n/

This pattern matches if the input line contains, except a leading sign and trailing newline, a character which is not a digit.

/^[-+]?(?:[0-9][0-9])*\n/ 

This pattern matches if, and only if, the input line contains an even number of digits (note that if we execute this pattern, we already know all the characters between the beginning (and possible sign) and the trailing newline are all digits.

If this last pattern fails as well, the only possibility which is left, is that the input number contains less than three digits.

Find the full program on GitHub.

Python

The Python solution follows the same strategy as most implementations.

First, we remove any leading sign, and the trailing newline. line contains our line of input:

line = re . sub (r'^[-+]', '', line . strip ())

String are immutable in Python, so all the methods return the modified string, without modifying the original.

Validating and printing the middle three digits is easy:

ll = len (line)
if re . search (r'[^0-9]', line):
    print ("not an integer")
elif ll % 2 == 0:
    print ("even number of digits")
elif ll < 3:
    print ("too short")
else:
    print (line [(ll - 3) // 2 : (ll + 3) // 2])

Find the full program on GitHub.

Ruby

The Ruby solution looks very similar to the Python solution. The current line of input is in the variable line:

line = line . strip() . gsub(/^[-+]/, "")
ll   = line . length
if line . match (/[^0-9]/) then
    puts ("not an integer")
elsif ll % 2 == 0 then
    puts ("even number of digits")
elsif ll < 3 then
    puts ("too short")
else
    puts (line [((ll - 3) / 2) . to_i .. ((ll + 2) / 2) . to_i])
end

Find the full program on GitHub.

Scheme

Our Scheme solution isn't very different from other solutions — it just looks different:

(define line (read-line))

This creates a variable line, and we initialize it with a line from standard input.

We first check whether the input contains an integer, and we capture the numbers:

(set! is-number (string-match "^[-+]?([0-9]+)$" line))

string-match takes two arguments, and matches the first argument (the pattern) against the second argument. If the match succeeds, a match object is returned; else, #f is returned.

We first check whether the match succeed; if it doesn't, we print the appropriate message:

(if (not is-number)
    (display "not an integer")
    ...

If it does match, not is-number is fails, so the if statement executes its third argument (represented by ... above). This third argument is:

(begin
    (set! number (match:substring is-number 1))
    (set! ll (string-length number))
    (if (= (modulo ll 2) 0)
        (display "even number of digits")
        (if (< ll 3)
            (display "too short")
            (display (substring number (/ (- ll 3) 2)
                                       (/ (+ ll 3) 2)))
        )
    )
)

Remember the capture in the first pattern? The match object can be queried for that using match:substring. This function takes the arguments, a match object, and the number of the capture we want. In our case, this is the number without sign.

string-length returns the length of its argument.

The rest of the code is fairly straightforward. Remember that in scheme, all operators are postfix operators (and as such, there isn't any difference between operators and functions).

Find the full program on GitHub.

Tcl

Our Tcl solution starts with:

if {[regexp {^[-+]?([0-9]+)$} $line -> digits]} {
    ...
} else {
    puts "not an integer"
}

where $line contains the current line of input. regexp takes at least two parameters: a pattern, a value to match against. Subsequent parameters, if any, are use to write captures to, where the first capture is the entire match; each subsequent capture is a next group of parens. Here -> isn't a funny piece of syntax, it is the third argument to regexp, indicating we don't need the entire match. But we do want what was matched by the first set of parens: this is written to the variable digits.

If the match fails, we're printing not an integer. Else, we run what's in the placeholder ...:

set ll [string length $digits]
if {[expr $ll % 2] == 0} {
    puts "even number of digits"
} elseif {[expr $ll < 3]} {
    puts "too short"
} else {
    puts [string range $digits [expr ($ll - 3) / 2]\
                               [expr ($ll + 2) / 2]]

Find the full program on GitHub.


Please leave any comments as a GitHub issue.