diff --git a/awk/README.md b/awk/README.md index 1a27ab81..243ea273 100644 --- a/awk/README.md +++ b/awk/README.md @@ -34,3 +34,4 @@ - [space-age](./space-age/README.md) - [all-your-base](./all-your-base/README.md) - [high-scores](./high-scores/README.md) +- [difference-of-squares](./difference-of-squares/README.md) diff --git a/awk/difference-of-squares/.exercism/config.json b/awk/difference-of-squares/.exercism/config.json new file mode 100644 index 00000000..6e5f0c4d --- /dev/null +++ b/awk/difference-of-squares/.exercism/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "difference-of-squares.awk" + ], + "test": [ + "test-difference-of-squares.bats" + ], + "example": [ + ".meta/example.awk" + ] + }, + "blurb": "Find the difference between the square of the sum and the sum of the squares of the first N natural numbers.", + "source": "Problem 6 at Project Euler", + "source_url": "https://projecteuler.net/problem=6" +} diff --git a/awk/difference-of-squares/.exercism/metadata.json b/awk/difference-of-squares/.exercism/metadata.json new file mode 100644 index 00000000..413e43a6 --- /dev/null +++ b/awk/difference-of-squares/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"awk","exercise":"difference-of-squares","id":"488e8fafe396433c9d3455749663cac9","url":"https://exercism.org/tracks/awk/exercises/difference-of-squares","handle":"vpayno","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/awk/difference-of-squares/.lint_default_vars b/awk/difference-of-squares/.lint_default_vars new file mode 100644 index 00000000..e69de29b diff --git a/awk/difference-of-squares/HELP.md b/awk/difference-of-squares/HELP.md new file mode 100644 index 00000000..f7806eb2 --- /dev/null +++ b/awk/difference-of-squares/HELP.md @@ -0,0 +1,100 @@ +# Help + +## Running the tests + +Each exercise contains a test file. +Run the tests using the `bats` program. +```bash +bats test-hello-world.bats +``` + +`bats` will need to be installed. +See the [Testing on the Bash track][bash] page for instructions to install `bats` for your system. + +### bats is implemented in bash + +The bats file is a bash script, with some special functions recognized by the `bats` command. +You'll see some tests that look like +```sh +gawk -f some-exercise.awk <<< "some,input,here" +``` +That `<<<` syntax is a bash [Here String][here-string]. +It sends the string on the right-hand side into the standard input of the program on the left-hand side. +It is ([approximately][so]) the same as +```sh +echo "some,input,here" | gawk -f some-exercise.awk +``` + +## Help for assert functions + +The tests use functions from the [bats-assert][bats-assert] library. +Help for the various `assert*` functions can be found there. + +## Skipped tests + +Solving an exercise means making all its tests pass. +By default, only one test (the first one) is executed when you run the tests. +This is intentional, as it allows you to focus on just making that one test pass. +Once it passes, you can enable the next test by commenting out or removing the + + [[ $BATS_RUN_SKIPPED == true ]] || skip + +annotations prepending other tests. + +## Overriding skips + +To run all tests, including the ones with `skip` annotations, you can run: +```bash +BATS_RUN_SKIPPED=true bats test-some-exercise.bats +``` + +It can be convenient to use a wrapper function to save on typing: in `bash` you can do: +```bash +bats() { + BATS_RUN_SKIPPED=true command bats *.bats +} +``` +Then run tests with just: +```bash +bats +``` + +[bash]: https://exercism.org/docs/tracks/bash/tests +[bats-assert]: https://github.com/bats-core/bats-assert +[here-string]: https://www.gnu.org/software/bash/manual/bash.html#Here-Strings +[so]: https://unix.stackexchange.com/a/80372/4667 + +## Submitting your solution + +You can submit your solution using the `exercism submit difference-of-squares.awk` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [AWK track's documentation](https://exercism.org/docs/tracks/awk) +- The [AWK track's programming category on the forum](https://forum.exercism.org/c/programming/awk) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Places to look for help for AWK questions: + +* [Stack Overflow `awk` tag][so]. +* check the Resources section of the [Stack Overflow `awk` tag into page][so-info]. +* raise an issue at the [exercism/awk][github] Github repository. +* IRC: `irc://irc.liberachat.net/#awk`, `irc://irc.liberachat.net/#exercism` + * see [Libera.chat][libera] if you're unfamiliar with IRC. + + +[so]: https://stackoverflow.com/tags/awk +[so-info]: https://stackoverflow.com/tags/awk/info +[github]: https://github.com/exercism/awk +[libera]: https://libera.chat \ No newline at end of file diff --git a/awk/difference-of-squares/README.md b/awk/difference-of-squares/README.md new file mode 100644 index 00000000..62acb228 --- /dev/null +++ b/awk/difference-of-squares/README.md @@ -0,0 +1,36 @@ +# Difference of Squares + +Welcome to Difference of Squares on Exercism's AWK Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +Find the difference between the square of the sum and the sum of the squares of the first N natural numbers. + +The square of the sum of the first ten natural numbers is +(1 + 2 + ... + 10)² = 55² = 3025. + +The sum of the squares of the first ten natural numbers is +1² + 2² + ... + 10² = 385. + +Hence the difference between the square of the sum of the first ten natural numbers and the sum of the squares of the first ten natural numbers is 3025 - 385 = 2640. + +You are not expected to discover an efficient solution to this yourself from first principles; research is allowed, indeed, encouraged. +Finding the best algorithm for the problem is a key skill in software engineering. + +## Source + +### Created by + +- @glennj + +### Based on + +Problem 6 at Project Euler - https://projecteuler.net/problem=6 + +### My Solution + +- [my solution](./difference-of-squares.awk) +- [awkunit tests](./difference-of-squares_test.awk) +- [test cases](./test-cases.awk) +- [run-tests](./run-tests-awk.txt) diff --git a/awk/difference-of-squares/awkunit.awk b/awk/difference-of-squares/awkunit.awk new file mode 120000 index 00000000..38a08adf --- /dev/null +++ b/awk/difference-of-squares/awkunit.awk @@ -0,0 +1 @@ +../.lib/awkunit.awk \ No newline at end of file diff --git a/awk/difference-of-squares/bats-extra.bash b/awk/difference-of-squares/bats-extra.bash new file mode 100644 index 00000000..54d48070 --- /dev/null +++ b/awk/difference-of-squares/bats-extra.bash @@ -0,0 +1,637 @@ +# This is the source code for bats-support and bats-assert, concatenated +# * https://github.com/bats-core/bats-support +# * https://github.com/bats-core/bats-assert +# +# Comments have been removed to save space. See the git repos for full source code. + +############################################################ +# +# bats-support - Supporting library for Bats test helpers +# +# Written in 2016 by Zoltan Tombol +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any +# warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# . +# + +fail() { + (( $# == 0 )) && batslib_err || batslib_err "$@" + return 1 +} + +batslib_is_caller() { + local -i is_mode_direct=1 + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -i|--indirect) is_mode_direct=0; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + # Arguments. + local -r func="$1" + + # Check call stack. + if (( is_mode_direct )); then + [[ $func == "${FUNCNAME[2]}" ]] && return 0 + else + local -i depth + for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do + [[ $func == "${FUNCNAME[$depth]}" ]] && return 0 + done + fi + + return 1 +} + +batslib_err() { + { if (( $# > 0 )); then + echo "$@" + else + cat - + fi + } >&2 +} + +batslib_count_lines() { + local -i n_lines=0 + local line + while IFS='' read -r line || [[ -n $line ]]; do + (( ++n_lines )) + done < <(printf '%s' "$1") + echo "$n_lines" +} + +batslib_is_single_line() { + for string in "$@"; do + (( $(batslib_count_lines "$string") > 1 )) && return 1 + done + return 0 +} + +batslib_get_max_single_line_key_width() { + local -i max_len=-1 + while (( $# != 0 )); do + local -i key_len="${#1}" + batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len" + shift 2 + done + echo "$max_len" +} + +batslib_print_kv_single() { + local -ir col_width="$1"; shift + while (( $# != 0 )); do + printf '%-*s : %s\n' "$col_width" "$1" "$2" + shift 2 + done +} + +batslib_print_kv_multi() { + while (( $# != 0 )); do + printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )" + printf '%s\n' "$2" + shift 2 + done +} + +batslib_print_kv_single_or_multi() { + local -ir width="$1"; shift + local -a pairs=( "$@" ) + + local -a values=() + local -i i + for (( i=1; i < ${#pairs[@]}; i+=2 )); do + values+=( "${pairs[$i]}" ) + done + + if batslib_is_single_line "${values[@]}"; then + batslib_print_kv_single "$width" "${pairs[@]}" + else + local -i i + for (( i=1; i < ${#pairs[@]}; i+=2 )); do + pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )" + done + batslib_print_kv_multi "${pairs[@]}" + fi +} + +batslib_prefix() { + local -r prefix="${1:- }" + local line + while IFS='' read -r line || [[ -n $line ]]; do + printf '%s%s\n' "$prefix" "$line" + done +} + +batslib_mark() { + local -r symbol="$1"; shift + # Sort line numbers. + set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" ) + + local line + local -i idx=0 + while IFS='' read -r line || [[ -n $line ]]; do + if (( ${1:--1} == idx )); then + printf '%s\n' "${symbol}${line:${#symbol}}" + shift + else + printf '%s\n' "$line" + fi + (( ++idx )) + done +} + +batslib_decorate() { + echo + echo "-- $1 --" + cat - + echo '--' + echo +} + +############################################################ + +assert() { + if ! "$@"; then + batslib_print_kv_single 10 'expression' "$*" \ + | batslib_decorate 'assertion failed' \ + | fail + fi +} + +assert_equal() { + if [[ $1 != "$2" ]]; then + batslib_print_kv_single_or_multi 8 \ + 'expected' "$2" \ + 'actual' "$1" \ + | batslib_decorate 'values do not equal' \ + | fail + fi +} + +assert_failure() { + : "${output?}" + : "${status?}" + + (( $# > 0 )) && local -r expected="$1" + if (( status == 0 )); then + batslib_print_kv_single_or_multi 6 'output' "$output" \ + | batslib_decorate 'command succeeded, but it was expected to fail' \ + | fail + elif (( $# > 0 )) && (( status != expected )); then + { local -ir width=8 + batslib_print_kv_single "$width" \ + 'expected' "$expected" \ + 'actual' "$status" + batslib_print_kv_single_or_multi "$width" \ + 'output' "$output" + } \ + | batslib_decorate 'command failed as expected, but status differs' \ + | fail + fi +} + +assert_line() { + local -i is_match_line=0 + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + : "${lines?}" + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -n|--index) + if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then + echo "\`--index' requires an integer argument: \`$2'" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + is_match_line=1 + local -ri idx="$2" + shift 2 + ;; + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + + # Arguments. + local -r expected="$1" + + if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$expected'" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + + # Matching. + if (( is_match_line )); then + # Specific line. + if (( is_mode_regexp )); then + if ! [[ ${lines[$idx]} =~ $expected ]]; then + batslib_print_kv_single 6 \ + 'index' "$idx" \ + 'regexp' "$expected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'regular expression does not match line' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ ${lines[$idx]} != *"$expected"* ]]; then + batslib_print_kv_single 9 \ + 'index' "$idx" \ + 'substring' "$expected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line does not contain substring' \ + | fail + fi + else + if [[ ${lines[$idx]} != "$expected" ]]; then + batslib_print_kv_single 8 \ + 'index' "$idx" \ + 'expected' "$expected" \ + 'actual' "${lines[$idx]}" \ + | batslib_decorate 'line differs' \ + | fail + fi + fi + else + # Contained in output. + if (( is_mode_regexp )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} =~ $expected ]] && return 0 + done + { local -ar single=( 'regexp' "$expected" ) + local -ar may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } \ + | batslib_decorate 'no output line matches regular expression' \ + | fail + elif (( is_mode_partial )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} == *"$expected"* ]] && return 0 + done + { local -ar single=( 'substring' "$expected" ) + local -ar may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } \ + | batslib_decorate 'no output line contains substring' \ + | fail + else + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} == "$expected" ]] && return 0 + done + { local -ar single=( 'line' "$expected" ) + local -ar may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } \ + | batslib_decorate 'output does not contain line' \ + | fail + fi + fi +} + +assert_output() { + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + local -i is_mode_nonempty=0 + local -i use_stdin=0 + : "${output?}" + + # Handle options. + if (( $# == 0 )); then + is_mode_nonempty=1 + fi + + while (( $# > 0 )); do + case "$1" in + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + -|--stdin) use_stdin=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: assert_output' \ + | fail + return $? + fi + + # Arguments. + local expected + if (( use_stdin )); then + expected="$(cat -)" + else + expected="${1-}" + fi + + # Matching. + if (( is_mode_nonempty )); then + if [ -z "$output" ]; then + echo 'expected non-empty output, but output was empty' \ + | batslib_decorate 'no output' \ + | fail + fi + elif (( is_mode_regexp )); then + if [[ '' =~ $expected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$expected'" \ + | batslib_decorate 'ERROR: assert_output' \ + | fail + elif ! [[ $output =~ $expected ]]; then + batslib_print_kv_single_or_multi 6 \ + 'regexp' "$expected" \ + 'output' "$output" \ + | batslib_decorate 'regular expression does not match output' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ $output != *"$expected"* ]]; then + batslib_print_kv_single_or_multi 9 \ + 'substring' "$expected" \ + 'output' "$output" \ + | batslib_decorate 'output does not contain substring' \ + | fail + fi + else + if [[ $output != "$expected" ]]; then + batslib_print_kv_single_or_multi 8 \ + 'expected' "$expected" \ + 'actual' "$output" \ + | batslib_decorate 'output differs' \ + | fail + fi + fi +} + +assert_success() { + : "${output?}" + : "${status?}" + + if (( status != 0 )); then + { local -ir width=6 + batslib_print_kv_single "$width" 'status' "$status" + batslib_print_kv_single_or_multi "$width" 'output' "$output" + } \ + | batslib_decorate 'command failed' \ + | fail + fi +} + +refute() { + if "$@"; then + batslib_print_kv_single 10 'expression' "$*" \ + | batslib_decorate 'assertion succeeded, but it was expected to fail' \ + | fail + fi +} + +refute_line() { + local -i is_match_line=0 + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + : "${lines?}" + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -n|--index) + if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then + echo "\`--index' requires an integer argument: \`$2'" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + is_match_line=1 + local -ri idx="$2" + shift 2 + ;; + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + + # Arguments. + local -r unexpected="$1" + + if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$unexpected'" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + + # Matching. + if (( is_match_line )); then + # Specific line. + if (( is_mode_regexp )); then + if [[ ${lines[$idx]} =~ $unexpected ]]; then + batslib_print_kv_single 6 \ + 'index' "$idx" \ + 'regexp' "$unexpected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'regular expression should not match line' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ ${lines[$idx]} == *"$unexpected"* ]]; then + batslib_print_kv_single 9 \ + 'index' "$idx" \ + 'substring' "$unexpected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line should not contain substring' \ + | fail + fi + else + if [[ ${lines[$idx]} == "$unexpected" ]]; then + batslib_print_kv_single 5 \ + 'index' "$idx" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line should differ' \ + | fail + fi + fi + else + # Line contained in output. + if (( is_mode_regexp )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} =~ $unexpected ]]; then + { local -ar single=( 'regexp' "$unexpected" 'index' "$idx" ) + local -a may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } \ + | batslib_decorate 'no line should match the regular expression' \ + | fail + return $? + fi + done + elif (( is_mode_partial )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} == *"$unexpected"* ]]; then + { local -ar single=( 'substring' "$unexpected" 'index' "$idx" ) + local -a may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } \ + | batslib_decorate 'no line should contain substring' \ + | fail + return $? + fi + done + else + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} == "$unexpected" ]]; then + { local -ar single=( 'line' "$unexpected" 'index' "$idx" ) + local -a may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } \ + | batslib_decorate 'line should not be in output' \ + | fail + return $? + fi + done + fi + fi +} + +refute_output() { + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + local -i is_mode_empty=0 + local -i use_stdin=0 + : "${output?}" + + # Handle options. + if (( $# == 0 )); then + is_mode_empty=1 + fi + + while (( $# > 0 )); do + case "$1" in + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + -|--stdin) use_stdin=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: refute_output' \ + | fail + return $? + fi + + # Arguments. + local unexpected + if (( use_stdin )); then + unexpected="$(cat -)" + else + unexpected="${1-}" + fi + + if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$unexpected'" \ + | batslib_decorate 'ERROR: refute_output' \ + | fail + return $? + fi + + # Matching. + if (( is_mode_empty )); then + if [ -n "$output" ]; then + batslib_print_kv_single_or_multi 6 \ + 'output' "$output" \ + | batslib_decorate 'output non-empty, but expected no output' \ + | fail + fi + elif (( is_mode_regexp )); then + if [[ $output =~ $unexpected ]]; then + batslib_print_kv_single_or_multi 6 \ + 'regexp' "$unexpected" \ + 'output' "$output" \ + | batslib_decorate 'regular expression should not match output' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ $output == *"$unexpected"* ]]; then + batslib_print_kv_single_or_multi 9 \ + 'substring' "$unexpected" \ + 'output' "$output" \ + | batslib_decorate 'output should not contain substring' \ + | fail + fi + else + if [[ $output == "$unexpected" ]]; then + batslib_print_kv_single_or_multi 6 \ + 'output' "$output" \ + | batslib_decorate 'output equals, but it was expected to differ' \ + | fail + fi + fi +} diff --git a/awk/difference-of-squares/difference-of-squares.awk b/awk/difference-of-squares/difference-of-squares.awk new file mode 100644 index 00000000..4f02d646 --- /dev/null +++ b/awk/difference-of-squares/difference-of-squares.awk @@ -0,0 +1,47 @@ +#!/usr/bin/gawk -M --lint --file + +function squareOfSum(number) { + return (number * (number + 1) / 2) ^ 2 +} + +function sumOfSquares(number) { + return (number * (number + 1) * (2 * number + 1)) / 6 +} + +function differenceOfSquares(number) { + return squareOfSum(number) - sumOfSquares(number) +} + +BEGIN { +} + +{ + if (match($0, /^difference,/)) { + _ = split($0, part, ",") + + print differenceOfSquares(part[2]) + + exit 0 + } + + if (match($0, /^square_of_sum,/)) { + _ = split($0, part, ",") + + print squareOfSum(part[2]) + + exit 0 + } + + if (match($0, /^sum_of_squares,/)) { + _ = split($0, part, ",") + + print sumOfSquares(part[2]) + + exit 0 + } + + print "error" +} + +END { +} diff --git a/awk/difference-of-squares/difference-of-squares_test.awk b/awk/difference-of-squares/difference-of-squares_test.awk new file mode 100644 index 00000000..1ad87fde --- /dev/null +++ b/awk/difference-of-squares/difference-of-squares_test.awk @@ -0,0 +1,74 @@ +#!/usr/bin/gawk --bignum --lint --file + +@include "awkunit" +@include "test-cases" +@include "difference-of-squares" + +passed = 0 +testCount = 0 + +function _debugTestPre() { + printf "Test %s: %s\n", (passed + 1), test_name + printf " input -> [%s]\n", input +} + +function _debugTestPost() { + passed = passed + 1 + printf " output -> [%s]\n", got + printf " result -> passed\n\n" +} + +function casesDifferenceOfSquares() { + printf "Running %d test cases\n\n", length(cases) + caseNum = 0 + + # orders array by index in for loop + PROCINFO["sorted_in"] = "@ind_str_asc" + + # Associative arrays don't preserve insert order. + for (key in cases) { + input = key + want = cases[key] + + test_name = "[" input "]" + + _ = split(input, part, ",") + + _debugTestPre() + + got = 0 + + if (part[1] == "difference") { + got = differenceOfSquares(part[2]) + } else if (part[1] == "square_of_sum") { + got = squareOfSum(part[2]) + } else if (part[1] == "sum_of_squares") { + got = sumOfSquares(part[2]) + } + + assertEquals(got, want) + + _debugTestPost() + } +} + +BEGIN { + exit 0 +} + +END { + cmd = "grep --no-filename --count ^function\\ test *_test.awk" + cmd | getline testCount + + printf "\nRunning %d tests...\n\n", testCount + + testCount = testCount + length(cases) + + # running tests with reduced duplication + casesDifferenceOfSquares() + + print "\n" passed " out of " testCount " tests passed!" + + # add exit here to keep it from looping + exit 0 +} diff --git a/awk/difference-of-squares/run-tests-awk.txt b/awk/difference-of-squares/run-tests-awk.txt new file mode 100644 index 00000000..7ae93d4b --- /dev/null +++ b/awk/difference-of-squares/run-tests-awk.txt @@ -0,0 +1,158 @@ +Running automated test file(s): + + +=============================================================================== + +AWKLIBPATH=/usr/lib/x86_64-linux-gnu/gawk:../.lib + +/usr/lib/x86_64-linux-gnu/gawk +filefuncs.so +fnmatch.so +fork.so +inplace.so +intdiv.so +ordchr.so +readdir.so +readfile.so +revoutput.so +revtwoway.so +rwarray.so +time.so + +../.lib +awkunit.awk +awkunit.so + +gawk --lint --file=./awkunit.awk < /dev/null > /dev/null +gawk: ./awkunit.awk:3: warning: `load' is a gawk extension +gawk: warning: function `assertEquals' defined but never called directly +gawk: warning: function `assert' defined but never called directly +gawk: ./awkunit.awk:26: warning: reference to uninitialized variable `_assert_exit' + +real 0m0.003s +user 0m0.001s +sys 0m0.002s + +gawk --lint --file=./difference-of-squares.awk < /dev/null > /dev/null + +real 0m0.004s +user 0m0.002s +sys 0m0.002s + +gawk --lint --file=./test-cases.awk < /dev/null > /dev/null + +real 0m0.003s +user 0m0.001s +sys 0m0.002s + +exit 0 + +=============================================================================== + +Running: bats ./test-difference-of-squares.bats +1..9 +ok 1 square of sum 1 +ok 2 square of sum 5 +ok 3 square of sum 100 +ok 4 sum of squares 1 +ok 5 sum of squares 5 +ok 6 sum of squares 100 +ok 7 difference of squares 1 +ok 8 difference of squares 5 +ok 9 difference of squares 100 + +real 0m0.351s +user 0m0.210s +sys 0m0.162s + +exit 0 + +=============================================================================== + +AWKLIBPATH=/usr/lib/x86_64-linux-gnu/gawk:../.lib + +/usr/lib/x86_64-linux-gnu/gawk +filefuncs.so +fnmatch.so +fork.so +inplace.so +intdiv.so +ordchr.so +readdir.so +readfile.so +revoutput.so +revtwoway.so +rwarray.so +time.so + +../.lib +awkunit.awk +awkunit.so + +Running: gawk --file ./difference-of-squares_test.awk && printf \n%s\n Tests Passed! || printf \n%s\n Tests Failed! + +Running 0 tests... + +Running 8 test cases + +Test 1: [difference,1] + input -> [difference,1] + output -> [0] + result -> passed + +Test 2: [difference,100] + input -> [difference,100] + output -> [25164150] + result -> passed + +Test 3: [difference,5] + input -> [difference,5] + output -> [170] + result -> passed + +Test 4: [square_of_sum,1] + input -> [square_of_sum,1] + output -> [1] + result -> passed + +Test 5: [square_of_sum,100] + input -> [square_of_sum,100] + output -> [25502500] + result -> passed + +Test 6: [sum_of_squares,1] + input -> [sum_of_squares,1] + output -> [1] + result -> passed + +Test 7: [sum_of_squares,100] + input -> [sum_of_squares,100] + output -> [338350] + result -> passed + +Test 8: [sum_of_squares,5] + input -> [sum_of_squares,5] + output -> [55] + result -> passed + + +8 out of 8 tests passed! + +real 0m0.009s +user 0m0.005s +sys 0m0.004s + +Tests Passed! + +exit 0 + +=============================================================================== + +Running: misspell . + +real 0m0.038s +user 0m0.044s +sys 0m0.014s + +=============================================================================== + diff --git a/awk/difference-of-squares/test-cases.awk b/awk/difference-of-squares/test-cases.awk new file mode 100644 index 00000000..0560a799 --- /dev/null +++ b/awk/difference-of-squares/test-cases.awk @@ -0,0 +1,20 @@ +#!/usr/bin/gawk --lint --file +# test-cases.awk + +# key: input +# value: output + +BEGIN { + cases["square_of_sum,1"]="1" + cases["square_of_sum,100"]="225" + cases["square_of_sum,100"]="25502500" + cases["sum_of_squares,1"]="1" + cases["sum_of_squares,5"]="55" + cases["sum_of_squares,100"]="338350" + cases["difference,1"]="0" + cases["difference,5"]="170" + cases["difference,100"]="25164150" + + # add exit here to keep it from waiting for input + exit 0 +} diff --git a/awk/difference-of-squares/test-difference-of-squares.bats b/awk/difference-of-squares/test-difference-of-squares.bats new file mode 100644 index 00000000..72d76f3f --- /dev/null +++ b/awk/difference-of-squares/test-difference-of-squares.bats @@ -0,0 +1,66 @@ +#!/usr/bin/env bats +load bats-extra + +# Square the sum of the numbers up to the given number +@test "square of sum 1" { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f difference-of-squares.awk <<< "square_of_sum,1" + assert_success + assert_output "1" +} + +@test "square of sum 5" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f difference-of-squares.awk <<< "square_of_sum,5" + assert_success + assert_output "225" +} + +@test "square of sum 100" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f difference-of-squares.awk <<< "square_of_sum,100" + assert_success + assert_output "25502500" +} +# Sum the squares of the numbers up to the given number +@test "sum of squares 1" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f difference-of-squares.awk <<< "sum_of_squares,1" + assert_success + assert_output "1" +} + +@test "sum of squares 5" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f difference-of-squares.awk <<< "sum_of_squares,5" + assert_success + assert_output "55" +} + +@test "sum of squares 100" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f difference-of-squares.awk <<< "sum_of_squares,100" + assert_success + assert_output "338350" +} +# Subtract sum of squares from square of sums +@test "difference of squares 1" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f difference-of-squares.awk <<< "difference,1" + assert_success + assert_output "0" +} + +@test "difference of squares 5" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f difference-of-squares.awk <<< "difference,5" + assert_success + assert_output "170" +} + +@test "difference of squares 100" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f difference-of-squares.awk <<< "difference,100" + assert_success + assert_output "25164150" +}