Compare commits

..

12 Commits

Author SHA1 Message Date
8296c79f68 Some solutions for the julia path 2025-08-04 19:44:03 +02:00
5c52e8e34d Solution to tournament in Bash 2023-10-07 15:09:46 +02:00
83538317ee Solution to tournament in Bash 2023-10-07 15:09:13 +02:00
b44d1e941f Solution to grep in bash 2023-09-06 17:31:32 +02:00
33b8f2e204 Updated version 2023-09-06 17:02:40 +02:00
888b84bef9 Solution to resistor-color-trio in bash 2023-08-13 16:15:14 +02:00
6db87ea8f4 Solution to sieve in bash 2023-08-13 16:11:39 +02:00
6d6148d66d Solution to proverb in Bash 2023-07-15 18:39:44 +02:00
9dea7a1fcc Solution to matching brackets in Bash 2023-07-15 17:36:40 +02:00
09bf8bc515 Solution to DnD character in Bash 2023-07-01 16:56:11 +02:00
f26ebd97f2 Solution to darts in Bash 2023-06-18 18:12:30 +02:00
0b66c3cdf7 Solution to darts in Bash 2023-06-18 18:09:09 +02:00
161 changed files with 12614 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
{
"authors": [
"glennj"
],
"contributors": [
"guygastineau",
"IsaacG"
],
"files": {
"solution": [
"darts.sh"
],
"test": [
"darts.bats"
],
"example": [
".meta/example.sh"
]
},
"blurb": "Write a function that returns the earned points in a single toss of a Darts game.",
"source": "Inspired by an exercise created by a professor Della Paolera in Argentina"
}

View File

@@ -0,0 +1 @@
{"track":"bash","exercise":"darts","id":"83aa4ddf89f342908c0014fb73a1a3f4","url":"https://exercism.org/tracks/bash/exercises/darts","handle":"Kimawari","is_requester":true,"auto_approve":false}

105
bash/darts/HELP.md Normal file
View File

@@ -0,0 +1,105 @@
# Help
## Running the tests
Each exercise contains a test file.
Run the tests using the `bats` program.
```bash
bats hello_world.bats
```
`bats` will need to be installed.
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
[tests]: https://exercism.org/docs/tracks/bash/tests
## 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.
[bats-assert]: https://github.com/bats-core/bats-assert
## 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 next annotation:
```bash
[[ $BATS_RUN_SKIPPED == true ]] || skip
```
## Overriding skips
To run all tests, including the ones with `skip` annotations, you can run:
```bash
BATS_RUN_SKIPPED=true bats exercise_name.bats
```
It can be convenient to use a wrapper function to save on typing:
```bash
bats() {
BATS_RUN_SKIPPED=true command bats *.bats
}
```
Then run tests with just:
```bash
bats
```
## Submitting your solution
You can submit your solution using the `exercism submit darts.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
- [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.
Check your code for syntax errors: paste your code into
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
Stack Overflow will be your first stop for bash questions.
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
* the "Books and Resources" section is particularly useful.
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
## External utilities
`bash` is a language to write "scripts" -- programs that can call
external tools, such as
[`sed`](https://www.gnu.org/software/sed/),
[`awk`](https://www.gnu.org/software/gawk/),
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
and even programs written in other programming languages,
like [`Python`](https://www.python.org/).
This track does not restrict the usage of these utilities, and as long
as your solution is portable between systems and does not require
installation of third party applications, feel free to use them to solve
the exercise.
For an extra challenge, if you would like to have a better understanding of
the language, try to re-implement the solution in pure bash, without using
any external tools. There are some types of problems that bash cannot solve,
such as floating point arithmetic and manipulating dates: for those, you
must call out to an external tool.

49
bash/darts/README.md Normal file
View File

@@ -0,0 +1,49 @@
# Darts
Welcome to Darts on Exercism's Bash Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Write a function that returns the earned points in a single toss of a Darts game.
[Darts][darts] is a game where players throw darts at a [target][darts-target].
In our particular instance of the game, the target rewards 4 different amounts of points, depending on where the dart lands:
- If the dart lands outside the target, player earns no points (0 points).
- If the dart lands in the outer circle of the target, player earns 1 point.
- If the dart lands in the middle circle of the target, player earns 5 points.
- If the dart lands in the inner circle of the target, player earns 10 points.
The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1.
Of course, they are all centered at the same point — that is, the circles are [concentric][] defined by the coordinates (0, 0).
Write a function that given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), returns the correct amount earned by a dart landing at that point.
[darts]: https://en.wikipedia.org/wiki/Darts
[darts-target]: https://en.wikipedia.org/wiki/Darts#/media/File:Darts_in_a_dartboard.jpg
[concentric]: https://mathworld.wolfram.com/ConcentricCircles.html
[cartesian-coordinates]: https://www.mathsisfun.com/data/cartesian-coordinates.html
[real-numbers]: https://www.mathsisfun.com/numbers/real-numbers.html
## Floating Point Arithmetic
This particular exercise, since it deals with floating point arithmetic, is
natural to rely on external tools (see below). As an extra challenging
challenge, find a way to implement this with plain bash.
## Source
### Created by
- @glennj
### Contributed to by
- @guygastineau
- @IsaacG
### Based on
Inspired by an exercise created by a professor Della Paolera in Argentina

637
bash/darts/bats-extra.bash Normal file
View File

@@ -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 <zoltan dot tombol at gmail dot com>
#
# 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
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
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
}

125
bash/darts/darts.bats Normal file
View File

@@ -0,0 +1,125 @@
#!/usr/bin/env bats
load bats-extra
# local version: 2.2.0.1
@test "Missed target" {
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh -9 9
assert_success
assert_output "0"
}
@test "On the outer circle" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh 0 10
assert_success
assert_output "1"
}
@test "On the middle circle" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh -5 0
assert_success
assert_output "5"
}
@test "On the inner circle" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh 0 -1
assert_success
assert_output "10"
}
@test "Exactly on centre" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh 0 0
assert_success
assert_output "10"
}
@test "Near the centre" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh -0.1 -0.1
assert_success
assert_output "10"
}
@test "Just within the inner circle" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh 0.7 0.7
assert_success
assert_output "10"
}
@test "Just outside the inner circle" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh 0.8 -0.8
assert_success
assert_output "5"
}
@test "Just within the middle circle" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh -3.5 3.5
assert_success
assert_output "5"
}
@test "Just outside the middle circle" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh -3.6 -3.6
assert_success
assert_output "1"
}
@test "Just within the outer circle" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh -7.0 7.0
assert_success
assert_output "1"
}
@test "Just outside the outer circle" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh 7.1 -7.1
assert_success
assert_output "0"
}
@test "Asymmetric position between the inner and middle circles" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh 0.5 -4
assert_success
assert_output "5"
}
# bash-specific test: Input validation
@test "invalid args: no args" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh
assert_failure
assert_output # there is _some_ output
}
@test "invalid args: only 1 arg" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh 10
assert_failure
assert_output # there is _some_ output
}
@test "invalid args: first arg non-numeric" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh foo 10
assert_failure
assert_output # there is _some_ output
}
@test "invalid args: second arg non-numeric" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash darts.sh 10 bar
assert_failure
assert_output # there is _some_ output
}

26
bash/darts/darts.sh Normal file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
x=$1
y=$2
distance=$(echo "sqrt($x^2 + $y^2)" | bc -l)
if [[ $# -ne 2 ]]; then
echo "# there is _some_ output"
exit 1
fi
if ! [[ $x =~ ^[+-]?[0-9]+(\.[0-9]+)?$ ]] || ! [[ $y =~ ^[+-]?[0-9]+(\.[0-9]+)?$ ]]; then
echo "# there is _some_ output"
exit 1
fi
if (( $(echo "$distance > 10" | bc -l) )); then
echo "0"
elif (( $(echo "$distance > 5" | bc -l) )); then
echo "1"
elif (( $(echo "$distance > 1" | bc -l) )); then
echo "5"
else
echo "10"
fi

View File

@@ -0,0 +1,23 @@
{
"authors": [
"glennj"
],
"contributors": [
"guygastineau",
"IsaacG"
],
"files": {
"solution": [
"dnd_character.sh"
],
"test": [
"dnd_character.bats"
],
"example": [
".meta/example.sh"
]
},
"blurb": "Randomly generate Dungeons & Dragons characters.",
"source": "Simon Shine, Erik Schierboom",
"source_url": "https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945"
}

View File

@@ -0,0 +1 @@
{"track":"bash","exercise":"dnd-character","id":"c00402df3e994390a5f6cfba07ad644f","url":"https://exercism.org/tracks/bash/exercises/dnd-character","handle":"Kimawari","is_requester":true,"auto_approve":false}

105
bash/dnd-character/HELP.md Normal file
View File

@@ -0,0 +1,105 @@
# Help
## Running the tests
Each exercise contains a test file.
Run the tests using the `bats` program.
```bash
bats hello_world.bats
```
`bats` will need to be installed.
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
[tests]: https://exercism.org/docs/tracks/bash/tests
## 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.
[bats-assert]: https://github.com/bats-core/bats-assert
## 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 next annotation:
```bash
[[ $BATS_RUN_SKIPPED == true ]] || skip
```
## Overriding skips
To run all tests, including the ones with `skip` annotations, you can run:
```bash
BATS_RUN_SKIPPED=true bats exercise_name.bats
```
It can be convenient to use a wrapper function to save on typing:
```bash
bats() {
BATS_RUN_SKIPPED=true command bats *.bats
}
```
Then run tests with just:
```bash
bats
```
## Submitting your solution
You can submit your solution using the `exercism submit dnd_character.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
- [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.
Check your code for syntax errors: paste your code into
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
Stack Overflow will be your first stop for bash questions.
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
* the "Books and Resources" section is particularly useful.
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
## External utilities
`bash` is a language to write "scripts" -- programs that can call
external tools, such as
[`sed`](https://www.gnu.org/software/sed/),
[`awk`](https://www.gnu.org/software/gawk/),
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
and even programs written in other programming languages,
like [`Python`](https://www.python.org/).
This track does not restrict the usage of these utilities, and as long
as your solution is portable between systems and does not require
installation of third party applications, feel free to use them to solve
the exercise.
For an extra challenge, if you would like to have a better understanding of
the language, try to re-implement the solution in pure bash, without using
any external tools. There are some types of problems that bash cannot solve,
such as floating point arithmetic and manipulating dates: for those, you
must call out to an external tool.

View File

@@ -0,0 +1,51 @@
# D&D Character
Welcome to D&D Character on Exercism's Bash Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
For a game of [Dungeons & Dragons][dnd], each player starts by generating a character they can play with.
This character has, among other things, six abilities; strength, dexterity, constitution, intelligence, wisdom and charisma.
These six abilities have scores that are determined randomly.
You do this by rolling four 6-sided dice and record the sum of the largest three dice.
You do this six times, once for each ability.
Your character's initial hitpoints are 10 + your character's constitution modifier.
You find your character's constitution modifier by subtracting 10 from your character's constitution, divide by 2 and round down.
Write a random character generator that follows the rules above.
For example, the six throws of four dice may look like:
- 5, 3, 1, 6: You discard the 1 and sum 5 + 3 + 6 = 14, which you assign to strength.
- 3, 2, 5, 3: You discard the 2 and sum 3 + 5 + 3 = 11, which you assign to dexterity.
- 1, 1, 1, 1: You discard the 1 and sum 1 + 1 + 1 = 3, which you assign to constitution.
- 2, 1, 6, 6: You discard the 1 and sum 2 + 6 + 6 = 14, which you assign to intelligence.
- 3, 5, 3, 4: You discard the 3 and sum 5 + 3 + 4 = 12, which you assign to wisdom.
- 6, 6, 6, 6: You discard the 6 and sum 6 + 6 + 6 = 18, which you assign to charisma.
Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6.
## Notes
Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice.
One such language is [Troll][troll].
[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons
[troll]: https://di.ku.dk/Ansatte/?pure=da%2Fpublications%2Ftroll-a-language-for-specifying-dicerolls(84a45ff0-068b-11df-825d-000ea68e967b)%2Fexport.html
## Source
### Created by
- @glennj
### Contributed to by
- @guygastineau
- @IsaacG
### Based on
Simon Shine, Erik Schierboom - https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945

View File

@@ -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 <zoltan dot tombol at gmail dot com>
#
# 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
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
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
}

View File

@@ -0,0 +1,169 @@
#!/usr/bin/env bats
load bats-extra
# local version: 1.1.0.0
# usage: dnd_character.sh modifier n
# -> output expected modifier
# usage: dnd_character.sh generate
# -> output each characteristic and ability value, one per line
# ability modifier
@test "ability modifier for score 3 is -4" {
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 3
assert_success
assert_output "-4"
}
@test "ability modifier for score 4 is -3" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 4
assert_success
assert_output "-3"
}
@test "ability modifier for score 5 is -3" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 5
assert_success
assert_output "-3"
}
@test "ability modifier for score 6 is -2" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 6
assert_success
assert_output "-2"
}
@test "ability modifier for score 7 is -2" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 7
assert_success
assert_output "-2"
}
@test "ability modifier for score 8 is -1" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 8
assert_success
assert_output "-1"
}
@test "ability modifier for score 9 is -1" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 9
assert_success
assert_output "-1"
}
@test "ability modifier for score 10 is 0" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 10
assert_success
assert_output "0"
}
@test "ability modifier for score 11 is 0" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 11
assert_success
assert_output "0"
}
@test "ability modifier for score 12 is +1" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 12
assert_success
assert_output "1"
}
@test "ability modifier for score 13 is +1" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 13
assert_success
assert_output "1"
}
@test "ability modifier for score 14 is +2" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 14
assert_success
assert_output "2"
}
@test "ability modifier for score 15 is +2" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 15
assert_success
assert_output "2"
}
@test "ability modifier for score 16 is +3" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 16
assert_success
assert_output "3"
}
@test "ability modifier for score 17 is +3" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 17
assert_success
assert_output "3"
}
@test "ability modifier for score 18 is +4" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh modifier 18
assert_success
assert_output "4"
}
# generate a character, validate expected output
@test "generate a character" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash dnd_character.sh generate
assert_success
# these don't have to appear in any particular order
assert_line --regexp '^strength [[:digit:]]{1,2}$'
assert_line --regexp '^dexterity [[:digit:]]{1,2}$'
assert_line --regexp '^constitution [[:digit:]]{1,2}$'
assert_line --regexp '^intelligence [[:digit:]]{1,2}$'
assert_line --regexp '^wisdom [[:digit:]]{1,2}$'
assert_line --regexp '^charisma [[:digit:]]{1,2}$'
assert_line --regexp '^hitpoints [[:digit:]]{1,2}$'
# no other output: `run` populates the `lines` array
assert_equal ${#lines[@]} 7
}
# Usage: between $val $low $high
# Value is between low (inclusive) and high (inclusive).
between() {
(( $2 <= $1 && $1 <= $3 ))
}
# random ability is within range
@test "validate ability range and hitpoint value" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
for i in {1..50}; do
while read c v; do
if [[ $c == "hitpoints" ]]; then
hits=$v
else
assert between "$v" 3 18
[[ $c == "constitution" ]] && const=$v
fi
done < <(bash dnd_character.sh generate)
const_mod=$(bash dnd_character.sh modifier "$const")
assert_equal $((10 + const_mod)) "$hits"
done
}

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
mod() {
echo $(bc <<< "($1/2)-5")
}
order=$1
if [[ $order == "modifier" ]]; then
mod "$2"
else
echo "strength $(($RANDOM%15+3))"
echo "dexterity $(($RANDOM%15+3))"
constitution=$(($RANDOM%15+3))
echo "constitution ${constitution}"
echo "intelligence $(($RANDOM%15+3))"
echo "wisdom $(($RANDOM%15+3))"
echo "charisma $(($RANDOM%15+3))"
echo "hitpoints $(($(mod "$constitution")+10))"
fi

View File

@@ -0,0 +1,25 @@
{
"authors": [
"glennj"
],
"contributors": [
"bkhl",
"guygastineau",
"IsaacG",
"kotp"
],
"files": {
"solution": [
"grep.sh"
],
"test": [
"grep.bats"
],
"example": [
".meta/example.sh"
]
},
"blurb": "Search a file for lines matching a regular expression pattern. Return the line number and contents of each matching line.",
"source": "Conversation with Nate Foster.",
"source_url": "https://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf"
}

View File

@@ -0,0 +1 @@
{"track":"bash","exercise":"grep","id":"f17326cb951d4357a2394a24d217a5f3","url":"https://exercism.org/tracks/bash/exercises/grep","handle":"Kimawari","is_requester":true,"auto_approve":false}

105
bash/grep/HELP.md Normal file
View File

@@ -0,0 +1,105 @@
# Help
## Running the tests
Each exercise contains a test file.
Run the tests using the `bats` program.
```bash
bats hello_world.bats
```
`bats` will need to be installed.
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
[tests]: https://exercism.org/docs/tracks/bash/tests
## 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.
[bats-assert]: https://github.com/bats-core/bats-assert
## 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 next annotation:
```bash
[[ $BATS_RUN_SKIPPED == true ]] || skip
```
## Overriding skips
To run all tests, including the ones with `skip` annotations, you can run:
```bash
BATS_RUN_SKIPPED=true bats exercise_name.bats
```
It can be convenient to use a wrapper function to save on typing:
```bash
bats() {
BATS_RUN_SKIPPED=true command bats *.bats
}
```
Then run tests with just:
```bash
bats
```
## Submitting your solution
You can submit your solution using the `exercism submit grep.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
- [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.
Check your code for syntax errors: paste your code into
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
Stack Overflow will be your first stop for bash questions.
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
* the "Books and Resources" section is particularly useful.
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
## External utilities
`bash` is a language to write "scripts" -- programs that can call
external tools, such as
[`sed`](https://www.gnu.org/software/sed/),
[`awk`](https://www.gnu.org/software/gawk/),
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
and even programs written in other programming languages,
like [`Python`](https://www.python.org/).
This track does not restrict the usage of these utilities, and as long
as your solution is portable between systems and does not require
installation of third party applications, feel free to use them to solve
the exercise.
For an extra challenge, if you would like to have a better understanding of
the language, try to re-implement the solution in pure bash, without using
any external tools. There are some types of problems that bash cannot solve,
such as floating point arithmetic and manipulating dates: for those, you
must call out to an external tool.

65
bash/grep/README.md Normal file
View File

@@ -0,0 +1,65 @@
# Grep
Welcome to Grep on Exercism's Bash Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Search files for lines matching a search string and return all matching lines.
The Unix [`grep`][grep] command searches files for lines that match a regular expression.
Your task is to implement a simplified `grep` command, which supports searching for fixed strings.
The `grep` command takes three arguments:
1. The string to search for.
2. Zero or more flags for customizing the command's behavior.
3. One or more files to search in.
It then reads the contents of the specified files (in the order specified), finds the lines that contain the search string, and finally returns those lines in the order in which they were found.
When searching in multiple files, each matching line is prepended by the file name and a colon (':').
## Flags
The `grep` command supports the following flags:
- `-n` Prepend the line number and a colon (':') to each line in the output, placing the number after the filename (if present).
- `-l` Output only the names of the files that contain at least one matching line.
- `-i` Match using a case-insensitive comparison.
- `-v` Invert the program -- collect all lines that fail to match.
- `-x` Search only for lines where the search string matches the entire line.
[grep]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html
## To `grep` or not to `grep`, that is the question
Although this exercise can be trivially solved by simply passing the
arguments to `grep`, implement this exercise using bash only. The aim
of this exercism track is to learn how to use bash builtin commands to solve
problems.
To solve this exercise, you'll need to:
* parse command line arguments: [`getopts`](https://stackoverflow.com/tags/getopts/info) is useful for this.
* iterate over the lines of a file: this is [bash FAQ #1](https://mywiki.wooledge.org/BashFAQ/001)
* use regular expression matching: bash can do this using the `=~` operator
within [`[[ ... ]]`](https://www.gnu.org/software/bash/manual/bash.html#index-_005b_005b)
---
## Source
### Created by
- @glennj
### Contributed to by
- @bkhl
- @guygastineau
- @IsaacG
- @kotp
### Based on
Conversation with Nate Foster. - https://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf

637
bash/grep/bats-extra.bash Normal file
View File

@@ -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 <zoltan dot tombol at gmail dot com>
#
# 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
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
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
}

377
bash/grep/grep.bats Normal file
View File

@@ -0,0 +1,377 @@
#!/usr/bin/env bats
load bats-extra
# local version: 1.2.0.0
setup() {
cat > iliad.txt << END_ILIAD
Achilles sing, O Goddess! Peleus' son;
His wrath pernicious, who ten thousand woes
Caused to Achaia's host, sent many a soul
Illustrious into Ades premature,
And Heroes gave (so stood the will of Jove)
To dogs and to all ravening fowls a prey,
When fierce dispute had separated once
The noble Chief Achilles from the son
Of Atreus, Agamemnon, King of men.
END_ILIAD
cat > midsummer-night.txt << END_MIDSUMMER
I do entreat your grace to pardon me.
I know not by what power I am made bold,
Nor how it may concern my modesty,
In such a presence here to plead my thoughts;
But I beseech your grace that I may know
The worst that may befall me in this case,
If I refuse to wed Demetrius.
END_MIDSUMMER
cat > paradise-lost.txt << END_PARADISE
Of Mans First Disobedience, and the Fruit
Of that Forbidden Tree, whose mortal tast
Brought Death into the World, and all our woe,
With loss of Eden, till one greater Man
Restore us, and regain the blissful Seat,
Sing Heav'nly Muse, that on the secret top
Of Oreb, or of Sinai, didst inspire
That Shepherd, who first taught the chosen Seed
END_PARADISE
}
teardown() {
rm iliad.txt midsummer-night.txt paradise-lost.txt
}
# Test grepping a single file
@test "One file, one match, no flags" {
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="Of Atreus, Agamemnon, King of men."
pattern="Agamemnon"
flags=()
files=(iliad.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, one match, print line numbers flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="2:Of that Forbidden Tree, whose mortal tast"
pattern="Forbidden"
flags=(-n)
files=(paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, one match, case-insensitive flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="Of that Forbidden Tree, whose mortal tast"
pattern="FORBIDDEN"
flags=(-i)
files=(paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, one match, print file names flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="paradise-lost.txt"
pattern="Forbidden"
flags=(-l)
files=(paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, one match, match entire lines flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="With loss of Eden, till one greater Man"
pattern="With loss of Eden, till one greater Man"
flags=(-x)
files=(paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, one match, multiple flags" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="9:Of Atreus, Agamemnon, King of men."
pattern="OF ATREUS, Agamemnon, KIng of MEN."
flags=(-n -i -x)
files=(iliad.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, several matches, no flags" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="Nor how it may concern my modesty,
But I beseech your grace that I may know
The worst that may befall me in this case,"
pattern="may"
flags=()
files=(midsummer-night.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, several matches, print line numbers flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="3:Nor how it may concern my modesty,
5:But I beseech your grace that I may know
6:The worst that may befall me in this case,"
pattern="may"
flags=(-n)
files=(midsummer-night.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, several matches, match entire lines flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=""
pattern="may"
flags=(-x)
files=(midsummer-night.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, several matches, case-insensitive flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="Achilles sing, O Goddess! Peleus' son;
The noble Chief Achilles from the son"
pattern="ACHILLES"
flags=(-i)
files=(iliad.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, several matches, inverted flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="Brought Death into the World, and all our woe,
With loss of Eden, till one greater Man
Restore us, and regain the blissful Seat,
Sing Heav'nly Muse, that on the secret top
That Shepherd, who first taught the chosen Seed"
pattern="Of"
flags=(-v)
files=(paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, no matches, various flags" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=""
pattern="Gandalf"
flags=(-n -l -x -i)
files=(iliad.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, one match, file flag takes precedence over line flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="iliad.txt"
pattern="ten"
flags=(-n -l)
files=(iliad.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "One file, several matches, inverted and match entire lines flags" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="Achilles sing, O Goddess! Peleus' son;
His wrath pernicious, who ten thousand woes
Caused to Achaia's host, sent many a soul
And Heroes gave (so stood the will of Jove)
To dogs and to all ravening fowls a prey,
When fierce dispute had separated once
The noble Chief Achilles from the son
Of Atreus, Agamemnon, King of men."
pattern="Illustrious into Ades premature,"
flags=(-x -v)
files=(iliad.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
# Multiple files
@test "Multiple files, one match, no flags" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="iliad.txt:Of Atreus, Agamemnon, King of men."
pattern="Agamemnon"
flags=()
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "Multiple files, several matches, no flags" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="midsummer-night.txt:Nor how it may concern my modesty,
midsummer-night.txt:But I beseech your grace that I may know
midsummer-night.txt:The worst that may befall me in this case,"
pattern="may"
flags=()
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "Multiple files, several matches, print line numbers flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="midsummer-night.txt:5:But I beseech your grace that I may know
midsummer-night.txt:6:The worst that may befall me in this case,
paradise-lost.txt:2:Of that Forbidden Tree, whose mortal tast
paradise-lost.txt:6:Sing Heav'nly Muse, that on the secret top"
pattern="that"
flags=(-n)
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "Multiple files, one match, print file names flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="iliad.txt
paradise-lost.txt"
pattern="who"
flags=(-l)
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "Multiple files, several matches, case-insensitive flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="iliad.txt:Caused to Achaia's host, sent many a soul
iliad.txt:Illustrious into Ades premature,
iliad.txt:And Heroes gave (so stood the will of Jove)
iliad.txt:To dogs and to all ravening fowls a prey,
midsummer-night.txt:I do entreat your grace to pardon me.
midsummer-night.txt:In such a presence here to plead my thoughts;
midsummer-night.txt:If I refuse to wed Demetrius.
paradise-lost.txt:Brought Death into the World, and all our woe,
paradise-lost.txt:Restore us, and regain the blissful Seat,
paradise-lost.txt:Sing Heav'nly Muse, that on the secret top"
pattern="TO"
flags=(-i)
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "Multiple files, several matches, inverted flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="iliad.txt:Achilles sing, O Goddess! Peleus' son;
iliad.txt:The noble Chief Achilles from the son
midsummer-night.txt:If I refuse to wed Demetrius."
pattern="a"
flags=(-v)
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "Multiple files, one match, match entire lines flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="midsummer-night.txt:But I beseech your grace that I may know"
pattern="But I beseech your grace that I may know"
flags=(-x)
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "Multiple files, one match, multiple flags" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="paradise-lost.txt:4:With loss of Eden, till one greater Man"
pattern="WITH LOSS OF EDEN, TILL ONE GREATER MAN"
flags=(-n -i -x)
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "Multiple files, no matches, various flags" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=""
pattern="Frodo"
flags=(-n -l -i -x)
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "Multiple files, several matches, file flag takes precedence over line number flag" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="iliad.txt
paradise-lost.txt"
pattern="who"
flags=(-n -l)
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}
@test "Multiple files, several matches, inverted and match entire lines flags" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="iliad.txt:Achilles sing, O Goddess! Peleus' son;
iliad.txt:His wrath pernicious, who ten thousand woes
iliad.txt:Caused to Achaia's host, sent many a soul
iliad.txt:And Heroes gave (so stood the will of Jove)
iliad.txt:To dogs and to all ravening fowls a prey,
iliad.txt:When fierce dispute had separated once
iliad.txt:The noble Chief Achilles from the son
iliad.txt:Of Atreus, Agamemnon, King of men.
midsummer-night.txt:I do entreat your grace to pardon me.
midsummer-night.txt:I know not by what power I am made bold,
midsummer-night.txt:Nor how it may concern my modesty,
midsummer-night.txt:In such a presence here to plead my thoughts;
midsummer-night.txt:But I beseech your grace that I may know
midsummer-night.txt:The worst that may befall me in this case,
midsummer-night.txt:If I refuse to wed Demetrius.
paradise-lost.txt:Of Mans First Disobedience, and the Fruit
paradise-lost.txt:Of that Forbidden Tree, whose mortal tast
paradise-lost.txt:Brought Death into the World, and all our woe,
paradise-lost.txt:With loss of Eden, till one greater Man
paradise-lost.txt:Restore us, and regain the blissful Seat,
paradise-lost.txt:Sing Heav'nly Muse, that on the secret top
paradise-lost.txt:Of Oreb, or of Sinai, didst inspire
paradise-lost.txt:That Shepherd, who first taught the chosen Seed"
pattern="Illustrious into Ades premature,"
flags=(-x -v)
files=(iliad.txt midsummer-night.txt paradise-lost.txt)
run bash grep.sh "${flags[@]}" "$pattern" "${files[@]}"
assert_success
assert_output "$expected"
}

58
bash/grep/grep.sh Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# parsing CLI options
while getopts :nlivx option; do
case "$option" in
# include line numbers in output
n) linenr=1 ;;
# only file names containing matching lines are printed
l) filename=1 ;;
# case insentivity
i) shopt -s nocasematch ;;
# matching lines are inverted (i.e., lines that do not match)
v) invert=1 ;;
# pattern should match whole line
x) entire=1 ;;
# other options are invalid and exit
\?) echo Invalid option.; exit 1;;
esac
done
# shifts the command-line arguments so that the remaining arguments
# are just the pattern and the files to search in
shift $((OPTIND-1))
pattern=$1
shift
files=( "$@" )
# if entire is set to 1, modifies the pattern to match the entire line
# by anchoring it with ^ (beginning of line) and $ (end of line)
(( entire )) && pattern="^$pattern$"
for file in "${files[@]}"; do
count=0
while read -r line
do
(( count++ ))
out=
if (( invert )); then
! [[ ${line} =~ ${pattern} ]] && out="$line"
else
[[ ${line} =~ ${pattern} ]] && out="$line"
fi
# if filename is set to 1, prints the file name and breaks out
# of the loop after the first matching line in each file
if (( filename )) && [[ -n $out ]]; then
echo $file;
break;
fi
if (( linenr )) && [[ -n $out ]]; then
out="$count":$out;
fi
if (( ${#files[@]} > 1 )) && [[ -n $out ]]; then
out="$file":$out;
fi
[[ -n $out ]] && echo $out;
done < "$file"
done
exit 0

View File

@@ -0,0 +1,24 @@
{
"authors": [
"glennj"
],
"contributors": [
"bkhl",
"guygastineau",
"IsaacG",
"kotp"
],
"files": {
"solution": [
"matching_brackets.sh"
],
"test": [
"matching_brackets.bats"
],
"example": [
".meta/example.sh"
]
},
"blurb": "Make sure the brackets and braces all match.",
"source": "Ginna Baker"
}

View File

@@ -0,0 +1 @@
{"track":"bash","exercise":"matching-brackets","id":"b50475a6d5ae42daa2f76faee41171ae","url":"https://exercism.org/tracks/bash/exercises/matching-brackets","handle":"Kimawari","is_requester":true,"auto_approve":false}

View File

@@ -0,0 +1,105 @@
# Help
## Running the tests
Each exercise contains a test file.
Run the tests using the `bats` program.
```bash
bats hello_world.bats
```
`bats` will need to be installed.
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
[tests]: https://exercism.org/docs/tracks/bash/tests
## 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.
[bats-assert]: https://github.com/bats-core/bats-assert
## 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 next annotation:
```bash
[[ $BATS_RUN_SKIPPED == true ]] || skip
```
## Overriding skips
To run all tests, including the ones with `skip` annotations, you can run:
```bash
BATS_RUN_SKIPPED=true bats exercise_name.bats
```
It can be convenient to use a wrapper function to save on typing:
```bash
bats() {
BATS_RUN_SKIPPED=true command bats *.bats
}
```
Then run tests with just:
```bash
bats
```
## Submitting your solution
You can submit your solution using the `exercism submit matching_brackets.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
- [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.
Check your code for syntax errors: paste your code into
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
Stack Overflow will be your first stop for bash questions.
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
* the "Books and Resources" section is particularly useful.
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
## External utilities
`bash` is a language to write "scripts" -- programs that can call
external tools, such as
[`sed`](https://www.gnu.org/software/sed/),
[`awk`](https://www.gnu.org/software/gawk/),
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
and even programs written in other programming languages,
like [`Python`](https://www.python.org/).
This track does not restrict the usage of these utilities, and as long
as your solution is portable between systems and does not require
installation of third party applications, feel free to use them to solve
the exercise.
For an extra challenge, if you would like to have a better understanding of
the language, try to re-implement the solution in pure bash, without using
any external tools. There are some types of problems that bash cannot solve,
such as floating point arithmetic and manipulating dates: for those, you
must call out to an external tool.

View File

@@ -0,0 +1,26 @@
# Matching Brackets
Welcome to Matching Brackets on Exercism's Bash Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Given a string containing brackets `[]`, braces `{}`, parentheses `()`, or any combination thereof, verify that any and all pairs are matched and nested correctly.
The string may also contain other characters, which for the purposes of this exercise should be ignored.
## Source
### Created by
- @glennj
### Contributed to by
- @bkhl
- @guygastineau
- @IsaacG
- @kotp
### Based on
Ginna Baker

View File

@@ -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 <zoltan dot tombol at gmail dot com>
#
# 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
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
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
}

View File

@@ -0,0 +1,145 @@
#!/usr/bin/env bats
load bats-extra
# local version: 2.0.0.0
@test "paired square brackets" {
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "[]"
assert_success
assert_output "true"
}
@test "empty string" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh ""
assert_success
assert_output "true"
}
@test "unpaired brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "[["
assert_success
assert_output "false"
}
@test "wrong ordered brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "}{"
assert_success
assert_output "false"
}
@test "wrong closing bracket" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "{]"
assert_success
assert_output "false"
}
@test "paired with whitespace" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "{ }"
assert_success
assert_output "true"
}
@test "partially paired brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "{[])"
assert_success
assert_output "false"
}
@test "simple nested brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "{[]}"
assert_success
assert_output "true"
}
@test "several paired brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "{}[]"
assert_success
assert_output "true"
}
@test "paired and nested brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "([{}({}[])])"
assert_success
assert_output "true"
}
@test "unopened closing brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "{[)][]}"
assert_success
assert_output "false"
}
@test "unpaired and nested brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "([{])"
assert_success
assert_output "false"
}
@test "paired and wrong nested brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "[({]})"
assert_success
assert_output "false"
}
@test "paired and wrong nested brackets but innermost are correct" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "[({}])"
assert_success
assert_output "false"
}
@test "paired and incomplete brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "{}["
assert_success
assert_output "false"
}
@test "too many closing brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "[]]"
assert_success
assert_output "false"
}
@test "early unexpected brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh ")()"
assert_success
assert_output "false"
}
@test "early mismatched brackets" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "{)()"
assert_success
assert_output "false"
}
@test "math expression" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "(((185 + 223.85) * 15) - 543)/2"
assert_success
assert_output "true"
}
@test "complex latex expression" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash matching_brackets.sh "\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)"
assert_success
assert_output "true"
}

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
input="$1"
# Remove all characters except brackets, braces, and parentheses
cleaned_input=$(echo "$input" | tr -cd '[]{}()')
# Keep replacing any valid pair of brackets, braces, or parentheses with an empty string
while [[ $cleaned_input =~ (\(\)|\{\}|\[\]) ]]; do
cleaned_input=${cleaned_input//\(\)/}
cleaned_input=${cleaned_input//\{\}/}
cleaned_input=${cleaned_input//\[\]/}
done
# If the cleaned string is empty, all pairs are matched and nested correctly
if [[ -z $cleaned_input ]]; then
echo "true"
else
echo "false"
fi

View File

@@ -0,0 +1,25 @@
{
"authors": [
"glennj"
],
"contributors": [
"bkhl",
"guygastineau",
"IsaacG",
"kotp"
],
"files": {
"solution": [
"proverb.sh"
],
"test": [
"proverb.bats"
],
"example": [
".meta/example.sh"
]
},
"blurb": "For want of a horseshoe nail, a kingdom was lost, or so the saying goes. Output the full text of this proverbial rhyme.",
"source": "Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/For_Want_of_a_Nail"
}

View File

@@ -0,0 +1 @@
{"track":"bash","exercise":"proverb","id":"b9604697f68f45c0a89bd7b3ebb75cd5","url":"https://exercism.org/tracks/bash/exercises/proverb","handle":"Kimawari","is_requester":true,"auto_approve":false}

105
bash/proverb/HELP.md Normal file
View File

@@ -0,0 +1,105 @@
# Help
## Running the tests
Each exercise contains a test file.
Run the tests using the `bats` program.
```bash
bats hello_world.bats
```
`bats` will need to be installed.
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
[tests]: https://exercism.org/docs/tracks/bash/tests
## 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.
[bats-assert]: https://github.com/bats-core/bats-assert
## 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 next annotation:
```bash
[[ $BATS_RUN_SKIPPED == true ]] || skip
```
## Overriding skips
To run all tests, including the ones with `skip` annotations, you can run:
```bash
BATS_RUN_SKIPPED=true bats exercise_name.bats
```
It can be convenient to use a wrapper function to save on typing:
```bash
bats() {
BATS_RUN_SKIPPED=true command bats *.bats
}
```
Then run tests with just:
```bash
bats
```
## Submitting your solution
You can submit your solution using the `exercism submit proverb.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
- [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.
Check your code for syntax errors: paste your code into
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
Stack Overflow will be your first stop for bash questions.
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
* the "Books and Resources" section is particularly useful.
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
## External utilities
`bash` is a language to write "scripts" -- programs that can call
external tools, such as
[`sed`](https://www.gnu.org/software/sed/),
[`awk`](https://www.gnu.org/software/gawk/),
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
and even programs written in other programming languages,
like [`Python`](https://www.python.org/).
This track does not restrict the usage of these utilities, and as long
as your solution is portable between systems and does not require
installation of third party applications, feel free to use them to solve
the exercise.
For an extra challenge, if you would like to have a better understanding of
the language, try to re-implement the solution in pure bash, without using
any external tools. There are some types of problems that bash cannot solve,
such as floating point arithmetic and manipulating dates: for those, you
must call out to an external tool.

41
bash/proverb/README.md Normal file
View File

@@ -0,0 +1,41 @@
# Proverb
Welcome to Proverb on Exercism's Bash Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
For want of a horseshoe nail, a kingdom was lost, or so the saying goes.
Given a list of inputs, generate the relevant proverb.
For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme:
```text
For want of a nail the shoe was lost.
For want of a shoe the horse was lost.
For want of a horse the rider was lost.
For want of a rider the message was lost.
For want of a message the battle was lost.
For want of a battle the kingdom was lost.
And all for the want of a nail.
```
Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length and content.
No line of the output text should be a static, unchanging string; all should vary according to the input given.
## Source
### Created by
- @glennj
### Contributed to by
- @bkhl
- @guygastineau
- @IsaacG
- @kotp
### Based on
Wikipedia - https://en.wikipedia.org/wiki/For_Want_of_a_Nail

View File

@@ -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 <zoltan dot tombol at gmail dot com>
#
# 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
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
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
}

108
bash/proverb/proverb.bats Normal file
View File

@@ -0,0 +1,108 @@
#!/usr/bin/env bats
load bats-extra
# local version: 1.1.0.1
@test "zero pieces" {
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=""
run bash proverb.sh
assert_success
assert_output "$expected"
}
@test "one piece" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=$(cat <<END
And all for the want of a nail.
END
)
run bash proverb.sh nail
assert_success
assert_output "$expected"
}
@test "two pieces" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=$(cat <<END
For want of a nail the shoe was lost.
And all for the want of a nail.
END
)
run bash proverb.sh nail shoe
assert_success
assert_output "$expected"
}
@test "three pieces" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=$(cat <<END
For want of a nail the shoe was lost.
For want of a shoe the horse was lost.
And all for the want of a nail.
END
)
run bash proverb.sh nail shoe horse
assert_success
assert_output "$expected"
}
@test "full proverb" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=$(cat <<END
For want of a nail the shoe was lost.
For want of a shoe the horse was lost.
For want of a horse the rider was lost.
For want of a rider the message was lost.
For want of a message the battle was lost.
For want of a battle the kingdom was lost.
And all for the want of a nail.
END
)
run bash proverb.sh nail shoe horse rider message battle kingdom
assert_success
assert_output "$expected"
}
@test "four pieces modernized" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=$(cat <<END
For want of a pin the gun was lost.
For want of a gun the soldier was lost.
For want of a soldier the battle was lost.
And all for the want of a pin.
END
)
run bash proverb.sh pin gun soldier battle
assert_success
assert_output "$expected"
}
# bash-specific tests: Focus the student's attention on the effects of
# word splitting and filename expansion:
# https://www.gnu.org/software/bash/manual/bash.html#Shell-Expansions
@test "items with whitespace" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=$(cat <<END
For want of a rusty nail the horse shoe was lost.
And all for the want of a rusty nail.
END
)
run bash proverb.sh "rusty nail" "horse shoe"
assert_success
assert_output "$expected"
}
@test "shell globbing character" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=$(cat <<END
For want of a quotes the * was lost.
And all for the want of a quotes.
END
)
run bash proverb.sh quotes "*"
assert_success
assert_output "$expected"
}

25
bash/proverb/proverb.sh Normal file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
inputs=("$@")
length=${#inputs[@]}
last_index=$((length - 1))
# Loop through the 'inputs' array, excluding the last element
for ((i=0; i<last_index; i++)); do
current=${inputs[i]}
next=${inputs[i+1]}
# Check if the current element is empty
if [[ -z $current ]]; then
echo "And all for the want of a $next."
else
echo "For want of a $current the $next was lost."
fi
done
# Check if the first element is empty
if [[ -z ${inputs[0]} ]]; then
echo ""
else
echo "And all for the want of a ${inputs[0]}."
fi

View File

@@ -0,0 +1,24 @@
{
"authors": [
"glennj"
],
"contributors": [
"alirezaghey",
"guygastineau",
"IsaacG"
],
"files": {
"solution": [
"resistor_color_trio.sh"
],
"test": [
"resistor_color_trio.bats"
],
"example": [
".meta/example.sh"
]
},
"blurb": "Convert color codes, as used on resistors, to a human-readable label.",
"source": "Maud de Vries, Erik Schierboom",
"source_url": "https://github.com/exercism/problem-specifications/issues/1549"
}

View File

@@ -0,0 +1 @@
{"track":"bash","exercise":"resistor-color-trio","id":"a9b9f196cdd6470bb7513963ab18923c","url":"https://exercism.org/tracks/bash/exercises/resistor-color-trio","handle":"Kimawari","is_requester":true,"auto_approve":false}

View File

@@ -0,0 +1,105 @@
# Help
## Running the tests
Each exercise contains a test file.
Run the tests using the `bats` program.
```bash
bats hello_world.bats
```
`bats` will need to be installed.
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
[tests]: https://exercism.org/docs/tracks/bash/tests
## 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.
[bats-assert]: https://github.com/bats-core/bats-assert
## 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 next annotation:
```bash
[[ $BATS_RUN_SKIPPED == true ]] || skip
```
## Overriding skips
To run all tests, including the ones with `skip` annotations, you can run:
```bash
BATS_RUN_SKIPPED=true bats exercise_name.bats
```
It can be convenient to use a wrapper function to save on typing:
```bash
bats() {
BATS_RUN_SKIPPED=true command bats *.bats
}
```
Then run tests with just:
```bash
bats
```
## Submitting your solution
You can submit your solution using the `exercism submit resistor_color_trio.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
- [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.
Check your code for syntax errors: paste your code into
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
Stack Overflow will be your first stop for bash questions.
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
* the "Books and Resources" section is particularly useful.
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
## External utilities
`bash` is a language to write "scripts" -- programs that can call
external tools, such as
[`sed`](https://www.gnu.org/software/sed/),
[`awk`](https://www.gnu.org/software/gawk/),
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
and even programs written in other programming languages,
like [`Python`](https://www.python.org/).
This track does not restrict the usage of these utilities, and as long
as your solution is portable between systems and does not require
installation of third party applications, feel free to use them to solve
the exercise.
For an extra challenge, if you would like to have a better understanding of
the language, try to re-implement the solution in pure bash, without using
any external tools. There are some types of problems that bash cannot solve,
such as floating point arithmetic and manipulating dates: for those, you
must call out to an external tool.

View File

@@ -0,0 +1,77 @@
# Resistor Color Trio
Welcome to Resistor Color Trio on Exercism's Bash Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
If you want to build something using a Raspberry Pi, you'll probably use _resistors_.
For this exercise, you need to know only three things about them:
- Each resistor has a resistance value.
- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read.
To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values.
- Each band acts as a digit of a number.
For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15.
In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands.
The program will take 3 colors as input, and outputs the correct value, in ohms.
The color bands are encoded as follows:
- Black: 0
- Brown: 1
- Red: 2
- Orange: 3
- Yellow: 4
- Green: 5
- Blue: 6
- Violet: 7
- Grey: 8
- White: 9
In `resistor-color duo` you decoded the first two colors.
For instance: orange-orange got the main value `33`.
The third color stands for how many zeros need to be added to the main value.
The main value plus the zeros gives us a value in ohms.
For the exercise it doesn't matter what ohms really are.
For example:
- orange-orange-black would be 33 and no zeros, which becomes 33 ohms.
- orange-orange-red would be 33 and 2 zeros, which becomes 3300 ohms.
- orange-orange-orange would be 33 and 3 zeros, which becomes 33000 ohms.
(If Math is your thing, you may want to think of the zeros as exponents of 10.
If Math is not your thing, go with the zeros.
It really is the same thing, just in plain English instead of Math lingo.)
This exercise is about translating the colors into a label:
> "... ohms"
So an input of `"orange", "orange", "black"` should return:
> "33 ohms"
When we get to larger resistors, a [metric prefix][metric-prefix] is used to indicate a larger magnitude of ohms, such as "kiloohms".
That is similar to saying "2 kilometers" instead of "2000 meters", or "2 kilograms" for "2000 grams".
For example, an input of `"orange", "orange", "orange"` should return:
> "33 kiloohms"
[metric-prefix]: https://en.wikipedia.org/wiki/Metric_prefix
## Source
### Created by
- @glennj
### Contributed to by
- @alirezaghey
- @guygastineau
- @IsaacG
### Based on
Maud de Vries, Erik Schierboom - https://github.com/exercism/problem-specifications/issues/1549

View File

@@ -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 <zoltan dot tombol at gmail dot com>
#
# 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
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
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
}

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env bats
load bats-extra
# local version: 1.0.0.2
# additional tests for: invalid color, invalid octal number, too many colors
@test "Orange and orange and black" {
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "orange" "orange" "black"
assert_success
assert_output "33 ohms"
}
@test "Blue and grey and brown" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "blue" "grey" "brown"
assert_success
assert_output "680 ohms"
}
@test "Brown and red and red" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "brown" "red" "red"
assert_success
assert_output "1200 ohms"
}
@test "Red and black and red" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "red" "black" "red"
assert_success
assert_output "2 kiloohms"
}
@test "Green and brown and orange" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "green" "brown" "orange"
assert_success
assert_output "51 kiloohms"
}
@test "Yellow and violet and yellow" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "yellow" "violet" "yellow"
assert_success
assert_output "470 kiloohms"
}
@test "Blue and violet and grey" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "blue" "violet" "blue"
assert_success
assert_output "67 megaohms"
}
@test "Minimum possible value" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "black" "black" "black"
assert_success
assert_output "0 ohms"
}
@test "Maximum possible value" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "white" "white" "white"
assert_success
assert_output "99 gigaohms"
}
@test "Invalid first color" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "foo" "white" "white"
assert_failure
assert_output # there is _some_ output
}
@test "Invalid second color" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "white" "bar" "white"
assert_failure
assert_output # there is _some_ output
}
@test "Invalid third color" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "white" "white" "baz"
assert_failure
assert_output # there is _some_ output
}
@test "First two colors make an invalid octal number" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "black" "grey" "black"
assert_success
assert_output "8 ohms"
}
@test "Ignore extra colors" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash resistor_color_trio.sh "blue" "green" "yellow" "orange"
assert_success
assert_output "650 kiloohms"
}

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
color1=$1
color2=$2
color3=$3
# Associative array to store color values
declare -A colors=(
["black"]=0
["brown"]=1
["red"]=2
["orange"]=3
["yellow"]=4
["green"]=5
["blue"]=6
["violet"]=7
["grey"]=8
["white"]=9
)
value="${colors[$color1]}${colors[$color2]}"
zeros="${colors[$color3]}"
# Calculate the value with zeros
value_with_zeros="$value"
for (( i = 0; i < zeros; i++ )); do
value_with_zeros+="0"
done
# Check if the first digit is zero and omit it if needed
if [[ ${value_with_zeros:0:1} == "0" ]]; then
value_with_zeros=${value_with_zeros:1}
fi
# Determine the metric prefix
giga=1000000000
mega=1000000
kilo=1000
if (( value_with_zeros >= $giga )) && (( value_with_zeros % $giga == 0)); then
prefix="gigaohms"
value_with_zeros=$(( value_with_zeros / $giga ))
elif (( value_with_zeros >= $mega )) && (( value_with_zeros % $mega == 0)); then
prefix="megaohms"
value_with_zeros=$(( value_with_zeros / $mega ))
elif (( value_with_zeros >= $kilo )) && (( value_with_zeros % $kilo == 0)); then
prefix="kiloohms"
value_with_zeros=$(( value_with_zeros / $kilo ))
else
prefix="ohms"
fi
if [[ -z "${colors[$color1]}" || -z "${colors[$color2]}" || -z "${colors[$color3]}" ]]; then
return
else
echo "$value_with_zeros $prefix"
fi

View File

@@ -0,0 +1,25 @@
{
"authors": [
"glennj"
],
"contributors": [
"bkhl",
"guygastineau",
"IsaacG",
"kotp"
],
"files": {
"solution": [
"sieve.sh"
],
"test": [
"sieve.bats"
],
"example": [
".meta/example.sh"
]
},
"blurb": "Use the Sieve of Eratosthenes to find all the primes from 2 up to a given number.",
"source": "Sieve of Eratosthenes at Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes"
}

View File

@@ -0,0 +1 @@
{"track":"bash","exercise":"sieve","id":"01268935f0604c5eb192a7502640bad6","url":"https://exercism.org/tracks/bash/exercises/sieve","handle":"Kimawari","is_requester":true,"auto_approve":false}

105
bash/sieve/HELP.md Normal file
View File

@@ -0,0 +1,105 @@
# Help
## Running the tests
Each exercise contains a test file.
Run the tests using the `bats` program.
```bash
bats hello_world.bats
```
`bats` will need to be installed.
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
[tests]: https://exercism.org/docs/tracks/bash/tests
## 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.
[bats-assert]: https://github.com/bats-core/bats-assert
## 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 next annotation:
```bash
[[ $BATS_RUN_SKIPPED == true ]] || skip
```
## Overriding skips
To run all tests, including the ones with `skip` annotations, you can run:
```bash
BATS_RUN_SKIPPED=true bats exercise_name.bats
```
It can be convenient to use a wrapper function to save on typing:
```bash
bats() {
BATS_RUN_SKIPPED=true command bats *.bats
}
```
Then run tests with just:
```bash
bats
```
## Submitting your solution
You can submit your solution using the `exercism submit sieve.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
- [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.
Check your code for syntax errors: paste your code into
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
Stack Overflow will be your first stop for bash questions.
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
* the "Books and Resources" section is particularly useful.
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
## External utilities
`bash` is a language to write "scripts" -- programs that can call
external tools, such as
[`sed`](https://www.gnu.org/software/sed/),
[`awk`](https://www.gnu.org/software/gawk/),
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
and even programs written in other programming languages,
like [`Python`](https://www.python.org/).
This track does not restrict the usage of these utilities, and as long
as your solution is portable between systems and does not require
installation of third party applications, feel free to use them to solve
the exercise.
For an extra challenge, if you would like to have a better understanding of
the language, try to re-implement the solution in pure bash, without using
any external tools. There are some types of problems that bash cannot solve,
such as floating point arithmetic and manipulating dates: for those, you
must call out to an external tool.

58
bash/sieve/README.md Normal file
View File

@@ -0,0 +1,58 @@
# Sieve
Welcome to Sieve on Exercism's Bash Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Introduction
You bought a big box of random computer parts at a garage sale.
You've started putting the parts together to build custom computers.
You want to test the performance of different combinations of parts, and decide to create your own benchmarking program to see how your computers compare.
You choose the famous "Sieve of Eratosthenes" algorithm, an ancient algorithm, but one that should push your computers to the limits.
## Instructions
Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find prime numbers.
A prime number is a number that is only divisible by 1 and itself.
For example, 2, 3, 5, 7, 11, and 13 are prime numbers.
The Sieve of Eratosthenes is an ancient algorithm that works by taking a list of numbers and crossing out all the numbers that aren't prime.
A number that is **not** prime is called a "composite number".
To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number.
Then you repeat the following steps:
1. Find the next unmarked number in your list. This is a prime number.
2. Mark all the multiples of that prime number as composite (not prime).
You keep repeating these steps until you've gone through every number in your list.
At the end, all the unmarked numbers are prime.
~~~~exercism/note
[Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm.
The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes.
A good first test is to check that you do not use division or remainder operations.
[eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
~~~~
## Source
### Created by
- @glennj
### Contributed to by
- @bkhl
- @guygastineau
- @IsaacG
- @kotp
### Based on
Sieve of Eratosthenes at Wikipedia - https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes

637
bash/sieve/bats-extra.bash Normal file
View File

@@ -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 <zoltan dot tombol at gmail dot com>
#
# 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
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
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
}

44
bash/sieve/sieve.bats Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bats
load bats-extra
# local version: 1.1.0.0
@test "no primes under two" {
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected=""
run bash sieve.sh 1
assert_success
assert_output "$expected"
}
@test "find first prime" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="2"
run bash sieve.sh 2
assert_success
assert_output "$expected"
}
@test "find primes up to 10" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="2 3 5 7"
run bash sieve.sh 10
assert_success
assert_output "$expected"
}
@test "limit is prime" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="2 3 5 7 11 13"
run bash sieve.sh 13
assert_success
assert_output "$expected"
}
@test "find primes up to 1000" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
expected="2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997"
run bash sieve.sh 1000
assert_success
assert_output "$expected"
}

38
bash/sieve/sieve.sh Normal file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
if [ $# -ne 1 ]; then
echo "Usage: $0 <upper_limit>"
exit 1
fi
upper_limit=$1
# Create an array to store whether each number is prime or not
declare -a is_prime
# Initialize the array to assume all numbers are prime initially
for ((i = 2; i <= upper_limit; i++)); do
is_prime[$i]=true
done
# Apply the Sieve of Eratosthenes algorithm
for ((i = 2; i * i <= upper_limit; i++)); do
if [ "${is_prime[$i]}" = true ]; then
for ((j = i * i; j <= upper_limit; j += i)); do
is_prime[$j]=false
done
fi
done
# Print the prime numbers in a single line with space separation
for ((i = 2; i <= upper_limit; i++)); do
if [ "${is_prime[$i]}" = true ]; then
# if number is 2, print it without space before
if [ $i -eq 2 ]; then
echo -n "$i"
# print number after a space
else
echo -n " $i"
fi
fi
done

View File

@@ -0,0 +1,21 @@
{
"authors": [
"glennj"
],
"contributors": [
"guygastineau",
"IsaacG"
],
"files": {
"solution": [
"tournament.sh"
],
"test": [
"tournament.bats"
],
"example": [
".meta/example.sh"
]
},
"blurb": "Tally the results of a small football competition."
}

View File

@@ -0,0 +1 @@
{"track":"bash","exercise":"tournament","id":"d43f98a967f14693bc86e6eb2479c653","url":"https://exercism.org/tracks/bash/exercises/tournament","handle":"Kimawari","is_requester":true,"auto_approve":false}

106
bash/tournament/HELP.md Normal file
View File

@@ -0,0 +1,106 @@
# Help
## Running the tests
Each exercise contains a test file.
Run the tests using the `bats` program.
```bash
bats hello_world.bats
```
`bats` will need to be installed.
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
[tests]: https://exercism.org/docs/tracks/bash/tests
## 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.
[bats-assert]: https://github.com/bats-core/bats-assert
## 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 next annotation:
```bash
[[ $BATS_RUN_SKIPPED == true ]] || skip
```
## Overriding skips
To run all tests, including the ones with `skip` annotations, you can run:
```bash
BATS_RUN_SKIPPED=true bats exercise_name.bats
```
It can be convenient to use a wrapper function to save on typing:
```bash
bats() {
BATS_RUN_SKIPPED=true command bats *.bats
}
```
Then run tests with just:
```bash
bats
```
## Submitting your solution
You can submit your solution using the `exercism submit tournament.sh` 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 [Bash track's documentation](https://exercism.org/docs/tracks/bash)
- The [Bash track's programming category on the forum](https://forum.exercism.org/c/programming/bash)
- [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.
Check your code for syntax errors: paste your code into
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
Stack Overflow will be your first stop for bash questions.
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
* the "Books and Resources" section is particularly useful.
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
## External utilities
`bash` is a language to write "scripts" -- programs that can call
external tools, such as
[`sed`](https://www.gnu.org/software/sed/),
[`awk`](https://www.gnu.org/software/gawk/),
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
and even programs written in other programming languages,
like [`Python`](https://www.python.org/).
This track does not restrict the usage of these utilities, and as long
as your solution is portable between systems and does not require
installation of third party applications, feel free to use them to solve
the exercise.
For an extra challenge, if you would like to have a better understanding of
the language, try to re-implement the solution in pure bash, without using
any external tools. There are some types of problems that bash cannot solve,
such as floating point arithmetic and manipulating dates: for those, you
must call out to an external tool.

82
bash/tournament/README.md Normal file
View File

@@ -0,0 +1,82 @@
# Tournament
Welcome to Tournament on Exercism's Bash Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Tally the results of a small football competition.
Based on an input file containing which team played against which and what the outcome was, create a file with a table like this:
```text
Team | MP | W | D | L | P
Devastating Donkeys | 3 | 2 | 1 | 0 | 7
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
Blithering Badgers | 3 | 1 | 0 | 2 | 3
Courageous Californians | 3 | 0 | 1 | 2 | 1
```
What do those abbreviations mean?
- MP: Matches Played
- W: Matches Won
- D: Matches Drawn (Tied)
- L: Matches Lost
- P: Points
A win earns a team 3 points.
A draw earns 1.
A loss earns 0.
The outcome is ordered by points, descending.
In case of a tie, teams are ordered alphabetically.
## Input
Your tallying program will receive input that looks like:
```text
Allegoric Alaskans;Blithering Badgers;win
Devastating Donkeys;Courageous Californians;draw
Devastating Donkeys;Allegoric Alaskans;win
Courageous Californians;Blithering Badgers;loss
Blithering Badgers;Devastating Donkeys;loss
Allegoric Alaskans;Courageous Californians;win
```
The result of the match refers to the first team listed.
So this line:
```text
Allegoric Alaskans;Blithering Badgers;win
```
means that the Allegoric Alaskans beat the Blithering Badgers.
This line:
```text
Courageous Californians;Blithering Badgers;loss
```
means that the Blithering Badgers beat the Courageous Californians.
And this line:
```text
Devastating Donkeys;Courageous Californians;draw
```
means that the Devastating Donkeys and Courageous Californians tied.
## Source
### Created by
- @glennj
### Contributed to by
- @guygastineau
- @IsaacG

View File

@@ -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 <zoltan dot tombol at gmail dot com>
#
# 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
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
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
}

View File

@@ -0,0 +1,284 @@
#!/usr/bin/env bats
load bats-extra
# local version: 1.4.0.0
# Your bash program should be able to accept input
# 1. via standard input, OR
# 2. as a filename given on the cmd line.
# uses external tool: mktemp
setup() {
export INPUT_FILE HAS_TTY
INPUT_FILE=$( mktemp )
[[ -t 0 ]] && HAS_TTY=1 || HAS_TTY=0
}
teardown() { rm -f "$INPUT_FILE"; }
@test "just the header if no input" {
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
input=$( cat <<INPUT
INPUT
)
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
EXPECTED
)
run bash tournament.sh <<< "$input"
assert_success
assert_output "$expected"
}
@test "a win is three points, a loss is zero points" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
# ignore this test in CI
(( HAS_TTY )) || skip
cat <<INPUT >"$INPUT_FILE"
Allegoric Alaskans;Blithering Badgers;win
INPUT
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
Allegoric Alaskans | 1 | 1 | 0 | 0 | 3
Blithering Badgers | 1 | 0 | 0 | 1 | 0
EXPECTED
)
run bash tournament.sh "$INPUT_FILE"
assert_success
assert_output "$expected"
}
@test "a win can also be expressed as a loss" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
input=$( cat <<INPUT
Blithering Badgers;Allegoric Alaskans;loss
INPUT
)
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
Allegoric Alaskans | 1 | 1 | 0 | 0 | 3
Blithering Badgers | 1 | 0 | 0 | 1 | 0
EXPECTED
)
run bash tournament.sh <<< "$input"
assert_success
assert_output "$expected"
}
@test "a different team can win" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
input=$( cat <<INPUT
Blithering Badgers;Allegoric Alaskans;win
INPUT
)
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
Blithering Badgers | 1 | 1 | 0 | 0 | 3
Allegoric Alaskans | 1 | 0 | 0 | 1 | 0
EXPECTED
)
run bash tournament.sh <<< "$input"
assert_success
assert_output "$expected"
}
@test "a draw is one point each" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
input=$( cat <<INPUT
Allegoric Alaskans;Blithering Badgers;draw
INPUT
)
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
Allegoric Alaskans | 1 | 0 | 1 | 0 | 1
Blithering Badgers | 1 | 0 | 1 | 0 | 1
EXPECTED
)
run bash tournament.sh <<< "$input"
assert_success
assert_output "$expected"
}
@test "There can be more than one match" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
input=$( cat <<INPUT
Allegoric Alaskans;Blithering Badgers;win
Allegoric Alaskans;Blithering Badgers;win
INPUT
)
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
Allegoric Alaskans | 2 | 2 | 0 | 0 | 6
Blithering Badgers | 2 | 0 | 0 | 2 | 0
EXPECTED
)
run bash tournament.sh <<< "$input"
assert_success
assert_output "$expected"
}
@test "There can be more than one winner" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
input=$( cat <<INPUT
Allegoric Alaskans;Blithering Badgers;loss
Allegoric Alaskans;Blithering Badgers;win
INPUT
)
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
Allegoric Alaskans | 2 | 1 | 0 | 1 | 3
Blithering Badgers | 2 | 1 | 0 | 1 | 3
EXPECTED
)
run bash tournament.sh <<< "$input"
assert_success
assert_output "$expected"
}
@test "There can be more than two teams" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
input=$( cat <<INPUT
Allegoric Alaskans;Blithering Badgers;win
Blithering Badgers;Courageous Californians;win
Courageous Californians;Allegoric Alaskans;loss
INPUT
)
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
Allegoric Alaskans | 2 | 2 | 0 | 0 | 6
Blithering Badgers | 2 | 1 | 0 | 1 | 3
Courageous Californians | 2 | 0 | 0 | 2 | 0
EXPECTED
)
run bash tournament.sh <<< "$input"
assert_success
assert_output "$expected"
}
@test "typical input" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
input=$( cat <<INPUT
Allegoric Alaskans;Blithering Badgers;win
Devastating Donkeys;Courageous Californians;draw
Devastating Donkeys;Allegoric Alaskans;win
Courageous Californians;Blithering Badgers;loss
Blithering Badgers;Devastating Donkeys;loss
Allegoric Alaskans;Courageous Californians;win
INPUT
)
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
Devastating Donkeys | 3 | 2 | 1 | 0 | 7
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
Blithering Badgers | 3 | 1 | 0 | 2 | 3
Courageous Californians | 3 | 0 | 1 | 2 | 1
EXPECTED
)
run bash tournament.sh <<< "$input"
assert_success
assert_output "$expected"
}
@test "incomplete competition (not all pairs have played)" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
# ignore this test in CI
(( HAS_TTY )) || skip
cat <<INPUT > "$INPUT_FILE"
Allegoric Alaskans;Blithering Badgers;loss
Devastating Donkeys;Allegoric Alaskans;loss
Courageous Californians;Blithering Badgers;draw
Allegoric Alaskans;Courageous Californians;win
INPUT
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
Blithering Badgers | 2 | 1 | 1 | 0 | 4
Courageous Californians | 2 | 0 | 1 | 1 | 1
Devastating Donkeys | 1 | 0 | 0 | 1 | 0
EXPECTED
)
run bash tournament.sh "$INPUT_FILE"
assert_success
assert_output "$expected"
}
@test "ties broken alphabetically" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
input=$( cat <<INPUT
Courageous Californians;Devastating Donkeys;win
Allegoric Alaskans;Blithering Badgers;win
Devastating Donkeys;Allegoric Alaskans;loss
Courageous Californians;Blithering Badgers;win
Blithering Badgers;Devastating Donkeys;draw
Allegoric Alaskans;Courageous Californians;draw
INPUT
)
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
Allegoric Alaskans | 3 | 2 | 1 | 0 | 7
Courageous Californians | 3 | 2 | 1 | 0 | 7
Blithering Badgers | 3 | 0 | 1 | 2 | 1
Devastating Donkeys | 3 | 0 | 1 | 2 | 1
EXPECTED
)
run bash tournament.sh <<< "$input"
assert_success
assert_output "$expected"
}
@test "ensure points sorted numerically" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
input=$( cat <<INPUT
Devastating Donkeys;Blithering Badgers;win
Devastating Donkeys;Blithering Badgers;win
Devastating Donkeys;Blithering Badgers;win
Devastating Donkeys;Blithering Badgers;win
Blithering Badgers;Devastating Donkeys;win
INPUT
)
expected=$( cat <<EXPECTED
Team | MP | W | D | L | P
Devastating Donkeys | 5 | 4 | 0 | 1 | 12
Blithering Badgers | 5 | 1 | 0 | 4 | 3
EXPECTED
)
run bash tournament.sh <<< "$input"
assert_success
assert_output "$expected"
}

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
declare -A matches_played wins draws losses points
print_table_row() {
printf '%-30s | %2s | %2s | %2s | %2s | %2s\n' "$@"
}
print_table_row "Team" "MP" "W" "D" "L" "P"
initialize_teams_stats() {
local team
for team in "$@"; do
: "${matches_played[$team]:=0}"
: "${wins[$team]:=0}"
: "${draws[$team]:=0}"
: "${losses[$team]:=0}"
: "${points[$team]:=0}"
done
}
initialize_teams_stats "$team1" "$team2"
while IFS= read -r -t1 line; do
# Break loop if an empty line is encountered
[ -z "$line" ] && break
# Parse the input line
IFS=';' read -r team1 team2 outcome <<<"$line"
((matches_played[$team1]++))
((matches_played[$team2]++))
case "$outcome" in
"win")
((wins[$team1]++))
((losses[$team2]++))
((points[$team1] += 3))
;;
"draw")
((draws[$team1]++))
((draws[$team2]++))
((points[$team1]++))
((points[$team2]++))
;;
"loss")
((losses[$team1]++))
((wins[$team2]++))
((points[$team2] += 3))
;;
esac
# Read input from a file or standard input (stdin)
done <"${1:-/dev/stdin}"
# Iterate over teams and print their statistics, sorted by points
for team in "${!matches_played[@]}"; do
print_table_row "$team" "${matches_played[$team]}" "${wins[$team]}" "${draws[$team]}" "${losses[$team]}" "${points[$team]}"
# Sort the table by points in descending order, then alphabetically by team name
done | sort -t'|' -k6nr,6 -k1,1

BIN
exercism

Binary file not shown.

View File

@@ -0,0 +1,26 @@
{
"authors": [
"SaschaMann",
"colinleach"
],
"contributors": [
"cmcaine",
"BNAndras"
],
"files": {
"solution": [
"annalyns-infiltration.jl"
],
"test": [
"runtests.jl"
],
"exemplar": [
".meta/exemplar.jl"
]
},
"language_versions": "≥1.0",
"forked_from": [
"javascript/annalyns-infiltration"
],
"blurb": "Learn about boolean expressions by implementing the quest logic for a new RPG."
}

View File

@@ -0,0 +1 @@
{"track":"julia","exercise":"annalyns-infiltration","id":"30cf911b357741e8a7c62eb77cf57308","url":"https://exercism.org/tracks/julia/exercises/annalyns-infiltration","handle":"Kimawari","is_requester":true,"auto_approve":false}

View File

@@ -0,0 +1,34 @@
# Help
## Running the tests
To run the tests, run this command from within the exercise directory:
```bash
$ julia runtests.jl
```
## Submitting your solution
You can submit your solution using the `exercism submit annalyns-infiltration.jl` 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 [Julia track's documentation](https://exercism.org/docs/tracks/julia)
- The [Julia track's programming category on the forum](https://forum.exercism.org/c/programming/julia)
- [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.
To get help if you're having trouble, we recommend that you submit your code and request mentoring :)
If you don't want to do that for whatever reason, then you can find the wider Julia community channels [here](https://julialang.org/community/).

View File

@@ -0,0 +1,23 @@
# Hints
## General
- There are three [boolean operators][boolean-operators] to work with boolean values.
- Multiple operators can be combined in a single expression.
## 1. Check if a fast attack can be made
- The logical NOT operator (`!`) can be placed before an expression to negate its value.
## 2. Check if the group can be spied upon
- Boolean operators are typically used to evaluate whether two or more expressions are true or not true.
## 3. Check if the prisoner can be signaled
- Boolean operators execute in the order of their precedence (from highest to lowest): `!`, `&&`, `||`.
- In general, use of parentheses is encouraged to make your intention clearer.
- For more details check out the Operator Precedence section on the [official Julia documentation][operator-precedence].
[boolean-operators]: https://docs.julialang.org/en/v1/manual/mathematical-operations/#Boolean-Operators
[operator-precedence]: https://docs.julialang.org/en/v1/manual/mathematical-operations/#Operator-Precedence-and-Associativity

View File

@@ -0,0 +1,162 @@
# Annalyns Infiltration
Welcome to Annalyns Infiltration on Exercism's Julia Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
## Booleans in Julia
True or false values are represented by the `Bool` type.
It contains only two values: `true` and `false`.
```julia-repl
julia> true
true
julia> false
false
```
## Boolean Operators
There are three Boolean operators in Julia.
`&&` is Boolean "and".
It evaluates to `true` if the expressions on *both* sides of `&&` are `true`.
```julia-repl
julia> true && true
true
julia> true && false
false
```
`||` is Boolean "or".
It evaluates to `true` if an expression on *either* side of `||` is `true`.
```julia-repl
julia> true || true
true
julia> false || true
true
```
`!` is Boolean "not".
It exchanges `true` and `false` values.
```julia-repl
julia> !true
false
julia> !false
true
```
For longer and more complicated expressions, it is best to use parentheses to make your intention clear.
```julia-repl
julia> (true || false) && (false && true)
false
```
## Instructions
In this exercise, you'll be writing some logic for a video game a friend is developing.
The game's main character is Annalyn, a brave girl with a fierce and loyal pet dog.
Unfortunately, disaster strikes, as her best friend is kidnapped while searching for berries in the forest.
Annalyn will try to find and free her friend, optionally taking her dog with her on this quest.
Annalyn eventually finds the camp in which her friend is imprisoned and it turns out there are two kidnappers: a mighty knight and a cunning archer.
The player is presented with some options for what to do next.
For each of the four possible options you need to write a function that tells the game whether it should show that option or not.
## 1. Check if the 'Fast Attack' option should be shown
If the knight is sleeping, then Annalyn will be able to make a quick attack into the camp before he can wake up properly and get his armour on.
Implement a function named `can_do_fast_attack` that takes a boolean value which indicates if the knight is awake.
This function returns `true` if the 'Fast Attack' action is available based on the state of the character. Otherwise, returns `false`:
```julia-repl
julia> knight_awake = true
julia> can_do_fast_attack(knight_awake)
false
```
## 2. Check if the 'Spy' option should be shown
The group can be spied upon if at least one of them is awake. Otherwise, spying is a waste of time.
Implement a function named `can_spy` that takes three boolean values, indicating if the knight, archer and prisoner, respectively, are awake.
The function returns `true` if the 'Spy' action is available based on the state of the characters.
Otherwise, returns `false`:
```julia-repl
# Output suppressed from the next 3 lines
julia> knight_awake = false
julia> archer_awake = true
julia> prisoner_awake = false
julia> can_spy(knight_awake, archer_awake, prisoner_awake)
true
```
## 3. Check if the 'Signal Prisoner' option should be shown
The prisoner can be signalled using bird sounds if she is awake and the archer is sleeping.
If the archer is awake then she can't be safely signaled because the archer is also trained in bird signalling.
Implement a function named `can_signal_prisoner` that takes two boolean values, indicating if the archer and prisoner, respectively, are awake.
The function returns `true` if the 'Signal Prisoner' action is available based on the state of the characters.
Otherwise, returns `false`:
```julia-repl
julia> archer_awake = false
julia> prisoner_awake = true
julia> can_signal_prisoner(archer_awake, prisoner_awake)
true
```
## 4. Check if the 'Free Prisoner' option should be shown
Annalyn can try sneaking into the camp to free her friend. This is a risky thing to do and can only succeed in one of two ways:
- If Annalyn has her pet dog with her, she can rescue the prisoner if the archer is asleep.
The knight is scared of the dog and the archer will not have time to get ready before Annalyn and her friend can escape.
- If Annalyn does not have her dog then she and the prisoner must be very sneaky!
Annalyn can free the prisoner if she is awake and the knight and archer are both sleeping, but if the prisoner is sleeping, she can't be rescued: she would be startled by Annalyn's sudden appearance and wake up the knight and archer.
Implement a function named `can_free_prisoner` that takes four boolean values.
The first three parameters indicate if the knight, archer and prisoner, respectively, are awake.
The last parameter indicates if Annalyn's pet dog is present.
The function returns `true` if the 'Free Prisoner' action is available based on the state of the characters. Otherwise, it returns `false`:
```julia-repl
julia> knight_awake = false
julia> archer_awake = true
julia> prisoner_awake = false
julia> dog_present = false
julia> can_free_prisoner(knight_awake, archer_awake, prisoner_awake, dog_present)
false
```
## Source
### Created by
- @SaschaMann
- @colinleach
### Contributed to by
- @cmcaine
- @BNAndras

View File

@@ -0,0 +1,15 @@
function can_do_fast_attack(knight_awake)
return !knight_awake
end
function can_spy(knight_awake, archer_awake, prisoner_awake)
return (knight_awake || archer_awake || prisoner_awake)
end
function can_signal_prisoner(archer_awake, prisoner_awake)
return !archer_awake && prisoner_awake
end
function can_free_prisoner(knight_awake, archer_awake, prisoner_awake, dog_present)
return (dog_present && !archer_awake) || (!dog_present && prisoner_awake && !knight_awake && !archer_awake)
end

View File

@@ -0,0 +1,71 @@
using Test
include("annalyns-infiltration.jl")
# Julia 1.0 compat
# The function definition of eachrow is taken from Julia Base,
# released under the MIT license: https://julialang.org/license
if VERSION < v"1.1"
@eval eachrow(A) = (view(A, i, :) for i in axes(A, 1))
end
@testset verbose = true "tests" begin
@testset "fast attack" begin
@test !can_do_fast_attack(true)
@test can_do_fast_attack(false)
end
@testset "spying" begin
character_state_combinations = Bool[
0 0 0 0;
0 0 1 1;
0 1 0 1;
0 1 1 1;
1 0 0 1;
1 0 1 1;
1 1 1 1;
]
for state in eachrow(character_state_combinations)
@test can_spy(state[1:3]...) == state[4]
end
end
@testset "signaling prisoner" begin
character_state_combinations = Bool[
0 0 0;
0 1 1;
1 0 0;
1 1 0;
]
for state in eachrow(character_state_combinations)
@test can_signal_prisoner(state[1:2]...) == state[3]
end
end
@testset "freeing prisoner" begin
character_state_combinations = Bool[
0 0 0 0 0;
0 0 0 1 1;
0 0 1 0 1;
0 0 1 1 1;
0 1 0 0 0;
0 1 0 1 0;
0 1 1 0 0;
0 1 1 1 0;
1 0 0 0 0;
1 0 0 1 1;
1 0 1 0 0;
1 0 1 1 1;
1 1 0 0 0;
1 1 0 1 0;
1 1 1 0 0;
1 1 1 1 0;
]
for state in eachrow(character_state_combinations)
@test can_free_prisoner(state[1:4]...) == state[5]
end
end
end

View File

@@ -0,0 +1,20 @@
{
"authors": [
"colinleach"
],
"files": {
"solution": [
"cars-assemble.jl"
],
"test": [
"runtests.jl"
],
"exemplar": [
".meta/exemplar.jl"
]
},
"forked_from": [
"csharp/cars-assemble"
],
"blurb": "Learn about conditionals by analyzing the production of an assembly line."
}

View File

@@ -0,0 +1 @@
{"track":"julia","exercise":"cars-assemble","id":"45c3c88693d84b2d88daec48b8f45391","url":"https://exercism.org/tracks/julia/exercises/cars-assemble","handle":"Kimawari","is_requester":true,"auto_approve":false}

View File

@@ -0,0 +1,34 @@
# Help
## Running the tests
To run the tests, run this command from within the exercise directory:
```bash
$ julia runtests.jl
```
## Submitting your solution
You can submit your solution using the `exercism submit cars-assemble.jl` 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 [Julia track's documentation](https://exercism.org/docs/tracks/julia)
- The [Julia track's programming category on the forum](https://forum.exercism.org/c/programming/julia)
- [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.
To get help if you're having trouble, we recommend that you submit your code and request mentoring :)
If you don't want to do that for whatever reason, then you can find the wider Julia community channels [here](https://julialang.org/community/).

View File

@@ -0,0 +1,22 @@
# Hints
## 1. Calculate the success rate
- You need to translate the speed into a success rate, using the rules given in the instructions.
- The returned value is a floating-point number.
- Julia does not have the `select`/`case` syntax present in some other languages, but the Introduction discussed `if-else` syntax.
## 2. Calculate the production rate per hour
- The total theoretical production rate depends on the base production rate (a constant) and the speed.
- Return the actual production rate, which also depends on the success rate.
## 3. Calculate the number of working items produced per minute
- The hourly production rate was calculated in task 2.
- This task requires the rate per minute.
- Only complete, working cars are counted in this exercise, so you will need to remove part-complete cars from the count.
- The return value must be an integer (for example `7`, not `7.0`).
- The [Numbers][numbers] Concept already discussed ways to round values.
[numbers]: https://exercism.org/tracks/julia/concepts/numbers

View File

@@ -0,0 +1,150 @@
# Cars Assemble
Welcome to Cars Assemble on Exercism's Julia Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
## Comparison operators
Comparison operators in Julia are similar to many other languages, though with some extra options for math-lovers.
For equality, the operators are `==` (equal) and `!=` or `≠` (not equal).
```julia
txt = "abc"
txt == "abc" # true
txt != "abc" # false
txt "abc" # false (synonym for !=)
```
In addition, we have the various greater/less than operators.
```julia
1 < 3 # true
3 > 3 # false
3 <= 3 # true
3 3 # true (synonym for <=)
4 >= 3 # true
4 3 # true (synonym for >=)
```
As often with Julia, an appropriate editor makes use of the mathematical symbol easy.
Type `\ne`, `\le` or `\ge` then `TAB` to get `≠`, `≤` or `≥`.
The previous example uses only numbers, but we will see in other parts of the syllabus that various additional types have a sense of ordering and can be tested for greater/less than.
Comparison operators can be chained, which allows a clear and concise syntax:
```julia
n = 3
1 n 5 # true (n "between" two limits)
```
The previous example is a synonym for `1 ≤ n && n ≤ 5`.
## Branching with `if`
This is the full form of an `if` statement:
```julia
if conditional1
statements...
elseif conditional2
statements...
else
statements...
end
```
There is no need for parentheses `()` or braces `{}`, and indentation is "only" to improve readability _(but readability is very important!)_.
Both `elseif` and `else` are optional, and there can be multiple `elseif` blocks.
However, the `end` is required.
It is possible to nest `if` statements, though you might want to help readability with the thoughtful use of parentheses, indents and comments.
The shortest form of an `if` statement would be something like this:
```julia
if n < 0
n = 0
end
```
As a reminder: only expressions that evaluate to `true` or `false` can be used as conditionals.
Julia deliberately avoids any concept of "truthiness", so zero values, empty strings and empty arrays are _not_ equivalent to `false`.
## Ternary operator
A simple and common situation is picking one of two values based on a conditional.
Julia, like many languages, has a ternary operator to make this more concise.
The syntax is `conditional ? value_if_true : value_if_false`.
So the previous example could be rewritten:
```julia
n = n < 0 ? 0 : n
```
Parentheses are not required by the compiler, but may improve readability.
## Instructions
In this exercise you will be writing code to analyze the production of an assembly line in a car factory.
The assembly line's speed can range from `0` (off) to `10` (maximum).
At its lowest speed (`1`), `221` cars are produced each hour.
The production increases linearly with the speed.
So with the speed set to `4`, it should produce `4 * 221 = 884` cars per hour.
However, higher speeds increase the likelihood that faulty cars are produced, which then have to be discarded.
You have three tasks.
Each of the required functions takes a single integer parameter, the speed of the assembly line.
## 1. Calculate the success rate
Implement the `success_rate()` method to calculate the probability of an item being created without error for a given speed.
The following table shows how speed influences the success rate:
- `0`: 0% success rate.
- `1` to `4`: 100% success rate.
- `5` to `8`: 90% success rate.
- `9`: 80% success rate.
- `10`: 77% success rate.
```julia-repl
julia> success_rate(10)
0.77
```
## 2. Calculate the production rate per hour
Implement the `production_rate_per_hour()` method to calculate the assembly line's production rate per hour, taking into account its success rate.
```julia-repl
julia> production_rate_per_hour(6)
1193.4
```
Note that the value returned is floating-point.
## 3. Calculate the number of working items produced per minute
Implement the `working_items_per_minute()` method to calculate how many working cars are produced per minute:
```julia-repl
julia> working_items_per_minute(6)
19
```
Note that the value returned is an integer: incomplete items are not included.
## Source
### Created by
- @colinleach

View File

@@ -0,0 +1,24 @@
CARS_PER_HOUR = 221
function success_rate(speed)
if speed == 0
success = 0
elseif 1 speed 4
success = 100
elseif 5 speed 8
success = 90
elseif speed == 9
success = 80
elseif speed == 10
success = 77
end
return success / 100
end
function production_rate_per_hour(speed)
return CARS_PER_HOUR * speed * success_rate(speed)
end
function working_items_per_minute(speed)
return Int(production_rate_per_hour(speed) ÷ 60)
end

View File

@@ -0,0 +1,108 @@
using Test
include("cars-assemble.jl")
@testset verbose = true "tests" begin
@testset "success_rate" begin
@testset "Success rate for speed zero" begin
speed = 0
@test isapprox(success_rate(speed), 0.0, atol=1e-3)
end
@testset "Success rate for speed one" begin
speed = 1
@test isapprox(success_rate(speed), 1.0, atol=1e-3)
end
@testset "Success rate for speed four" begin
speed = 4
@test isapprox(success_rate(speed), 1.0, atol=1e-3)
end
@testset "Success rate for speed five" begin
speed = 5
@test isapprox(success_rate(speed), 0.9, atol=1e-3)
end
@testset "Success rate for speed nine" begin
speed = 9
@test isapprox(success_rate(speed), 0.8, atol=1e-3)
end
@testset "Success rate for speed ten" begin
speed = 10
@test isapprox(success_rate(speed), 0.77, atol=1e-3)
end
end
@testset "production_rate_per_hour" begin
@testset "Production rate per hour for speed zero" begin
speed = 0
@test isapprox(production_rate_per_hour(speed), 0.0, atol=1e-3)
end
@testset "Production rate per hour for speed one" begin
speed = 1
@test isapprox(production_rate_per_hour(speed), 221.0, atol=1e-3)
end
@testset "Production rate per hour for speed four" begin
speed = 4
@test isapprox(production_rate_per_hour(speed), 884.0, atol=1e-3)
end
@testset "Production rate per hour for speed seven" begin
speed = 7
@test isapprox(production_rate_per_hour(speed), 1392.3, atol=1e-3)
end
@testset "Production rate per hour for speed nine" begin
speed = 9
@test isapprox(production_rate_per_hour(speed), 1591.2, atol=1e-3)
end
@testset "Production rate per hour for speed ten" begin
speed = 10
@test isapprox(production_rate_per_hour(speed), 1701.7, atol=1e-3)
end
end
@testset "working_items_per_minute" begin
@testset "Working items per minute for speed zero" begin
speed = 0
@test typeof(working_items_per_minute(speed)) == Int
@test working_items_per_minute(speed) == 0
end
@testset "Working items per minute for speed one" begin
speed = 1
@test typeof(working_items_per_minute(speed)) == Int
@test working_items_per_minute(speed) == 3
end
@testset "Working items per minute for speed five" begin
speed = 5
@test typeof(working_items_per_minute(speed)) == Int
@test working_items_per_minute(speed) == 16
end
@testset "Working items per minute for speed eight" begin
speed = 8
@test typeof(working_items_per_minute(speed)) == Int
@test working_items_per_minute(speed) == 26
end
@testset "Working items per minute for speed nine" begin
speed = 9
@test typeof(working_items_per_minute(speed)) == Int
@test working_items_per_minute(speed) == 26
end
@testset "Working items per minute for speed ten" begin
speed = 10
@test typeof(working_items_per_minute(speed)) == Int
@test working_items_per_minute(speed) == 28
end
end
end

View File

@@ -0,0 +1,20 @@
{
"authors": [
"colinleach"
],
"files": {
"solution": [
"chessboard.jl"
],
"test": [
"runtests.jl"
],
"exemplar": [
".meta/exemplar.jl"
]
},
"forked_from": [
"elixir/chessboard"
],
"blurb": "Learn about ranges by building a chessboard."
}

View File

@@ -0,0 +1 @@
{"track":"julia","exercise":"chessboard","id":"dc930c6af16b45df91a8634905880861","url":"https://exercism.org/tracks/julia/exercises/chessboard","handle":"Kimawari","is_requester":true,"auto_approve":false}

34
julia/chessboard/HELP.md Normal file
View File

@@ -0,0 +1,34 @@
# Help
## Running the tests
To run the tests, run this command from within the exercise directory:
```bash
$ julia runtests.jl
```
## Submitting your solution
You can submit your solution using the `exercism submit chessboard.jl` 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 [Julia track's documentation](https://exercism.org/docs/tracks/julia)
- The [Julia track's programming category on the forum](https://forum.exercism.org/c/programming/julia)
- [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.
To get help if you're having trouble, we recommend that you submit your code and request mentoring :)
If you don't want to do that for whatever reason, then you can find the wider Julia community channels [here](https://julialang.org/community/).

22
julia/chessboard/HINTS.md Normal file
View File

@@ -0,0 +1,22 @@
# Hints
This is a short and simple exercise, so it is best to avoid complicating it.
## 1. Define the rank range
- You need to return a range of integers
## 2. Define the file range
- You need to return a range of uppercase characters.
- Characters use single-quotes, such as `'W'`,
## 3. Transform the rank range into a list of ranks
- The [`collect()`][collect] function is your friend here.
## 4. Transform the file range into a list of files
- See task 3.
[collect]: https://docs.julialang.org/en/v1/base/collections/#Base.collect-Tuple{Any}

149
julia/chessboard/README.md Normal file
View File

@@ -0,0 +1,149 @@
# Chessboard
Welcome to Chessboard on Exercism's Julia Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
Suppose you want all the non-negative integers up to 1000.
It would be ridiculous if you had to type these into an array.
For this we have the `range` type:
```julia-repl
julia> 0:1000
0:1000
julia> typeof(0:1000)
UnitRange{Int64}
```
Ranges are very common: not just to save you typing, but also as return types from functions.
Note that ranges are _not_ vectors.
They are just a set of instructions to generate a sequence ("lazy" evaluation, or an "iterator").
If you need a range as a vector, use the `collect()` function for conversion:
```julia-repl
julia> collect(0:5)
6-element Vector{Int64}:
0
1
2
3
4
5
```
The step size can be specified, in this case 0.3:
```julia-repl
julia> collect(1.0:0.3:2.0)
4-element Vector{Float64}:
1.0
1.3
1.6
1.9
```
So the syntax is `start:stepsize:stop`.
Both end limits are _inclusive_, as seen in the integer example.
If the step size does not divide exactly into `stop - start`, the last element will avoid exceeding `stop`.
## Letter ranges
Non-numeric sequences can also be used in ranges.
The simplest example is ASCII letters:
```julia-repl
julia> 'a':'d'
'a':1:'d'
julia> typeof('a':'d')
StepRange{Char, Int64}
julia> collect('a':'d')
4-element Vector{Char}:
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
```
The `Char` type will be covered in more detail in another Concept.
For now, just treat these as single characters in single-quotes.
## Functions and operators for ranges
Check the limits of a range with `first()` and `last()`.
```julia
r = 1:10 # => 1:10
first(r) # => 1
last(r) # => 10
```
## More on vector indexing
Integer ranges and vectors can be used in vector indexing:
```julia
nums = collect(10.0:50.0)
nums[3:2:7] # gives [12.0, 14.0, 16.0]
nums[ [3, 5, 7] ] # also gives [12.0, 14.0, 16.0]
```
## Instructions
As a chess enthusiast, you would like to write your own version of the game. Yes, there maybe plenty of implementations of chess available online already, but yours will be unique!
But before you can let your imagination run wild, you need to take care of the basics. Let's start by generating the board.
Each square of the chessboard is identified by a letter-number pair. The vertical columns of squares, called files, are labeled A through H. The horizontal rows of squares, called ranks, are numbered 1 to 8.
## 1. Define the rank range
Implement the `rank_range()` function. It should return a range of integers, from 1 to 8.
```julia-repl
julia> rank_range()
# output omitted
```
## 2. Define the file range
Implement the `file_range()` function.
It should return a range of integers, from the uppercase letter A, to the uppercase letter H.
```julia-repl
julia> file_range()
# output omitted
```
## 3. Transform the rank range into a vector of ranks
Implement the `ranks()` function. It should return a vector of integers, from 1 to 8.
Do not write the vector by hand, generate it from the range returned by the `rank_range()` function.
```julia-repl
julia> ranks()
[1, 2, 3, 4, 5, 6, 7, 8]
```
## 4. Transform the file range into a vector of files
Implement the `files` function. It should return a vector of characters, from 'A' to 'H'.
Do not write the vector by hand, generate it from the range returned by the `file_range()` function.
```julia-repl
julia> files()
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
```
## Source
### Created by
- @colinleach

View File

@@ -0,0 +1,15 @@
function rank_range()
return 1:8
end
function file_range()
return 'A':'H'
end
function ranks()
return collect(rank_range())
end
function files()
return collect(file_range())
end

View File

@@ -0,0 +1,31 @@
using Test
include("chessboard.jl")
@testset verbose = true "tests" begin
@testset "rank_range is a range from 1 to 8" begin
result = rank_range()
@test first(result) == 1
@test last(result) == 8
@test length(result) == 8
@test typeof(result) == UnitRange{Int}
end
@testset "file_range is a range from 'A' to 'H'" begin
result = file_range()
@test first(result) == 'A'
@test last(result) == 'H'
@test length(result) == 8
@test typeof(result) == StepRange{Char, Int}
end
@testset "ranks is a vector of integers from 1 to 8" begin
@test ranks() == [1, 2, 3, 4, 5, 6, 7, 8]
end
@testset "files is a vector of characters from 'A' to 'H'" begin
@test ranks() == [1, 2, 3, 4, 5, 6, 7, 8]
end
end

View File

@@ -0,0 +1,22 @@
{
"authors": [
"SaschaMann"
],
"contributors": [
"guilhermebodin"
],
"files": {
"solution": [
"collatz-conjecture.jl"
],
"test": [
"runtests.jl"
],
"example": [
".meta/example.jl"
]
},
"blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture.",
"source": "Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/Collatz_conjecture"
}

View File

@@ -0,0 +1 @@
{"track":"julia","exercise":"collatz-conjecture","id":"3beda5faa27740749db53e30351afca0","url":"https://exercism.org/tracks/julia/exercises/collatz-conjecture","handle":"Kimawari","is_requester":true,"auto_approve":false}

View File

@@ -0,0 +1,34 @@
# Help
## Running the tests
To run the tests, run this command from within the exercise directory:
```bash
$ julia runtests.jl
```
## Submitting your solution
You can submit your solution using the `exercism submit collatz-conjecture.jl` 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 [Julia track's documentation](https://exercism.org/docs/tracks/julia)
- The [Julia track's programming category on the forum](https://forum.exercism.org/c/programming/julia)
- [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.
To get help if you're having trouble, we recommend that you submit your code and request mentoring :)
If you don't want to do that for whatever reason, then you can find the wider Julia community channels [here](https://julialang.org/community/).

View File

@@ -0,0 +1,51 @@
# Collatz Conjecture
Welcome to Collatz Conjecture on Exercism's Julia Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Introduction
One evening, you stumbled upon an old notebook filled with cryptic scribbles, as though someone had been obsessively chasing an idea.
On one page, a single question stood out: **Can every number find its way to 1?**
It was tied to something called the **Collatz Conjecture**, a puzzle that has baffled thinkers for decades.
The rules were deceptively simple.
Pick any positive integer.
- If it's even, divide it by 2.
- If it's odd, multiply it by 3 and add 1.
Then, repeat these steps with the result, continuing indefinitely.
Curious, you picked number 12 to test and began the journey:
12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1
Counting from the second number (6), it took 9 steps to reach 1, and each time the rules repeated, the number kept changing.
At first, the sequence seemed unpredictable — jumping up, down, and all over.
Yet, the conjecture claims that no matter the starting number, we'll always end at 1.
It was fascinating, but also puzzling.
Why does this always seem to work?
Could there be a number where the process breaks down, looping forever or escaping into infinity?
The notebook suggested solving this could reveal something profound — and with it, fame, [fortune][collatz-prize], and a place in history awaits whoever could unlock its secrets.
[collatz-prize]: https://mathprize.net/posts/collatz-conjecture/
## Instructions
Given a positive integer, return the number of steps it takes to reach 1 according to the rules of the Collatz Conjecture.
## Source
### Created by
- @SaschaMann
### Contributed to by
- @guilhermebodin
### Based on
Wikipedia - https://en.wikipedia.org/wiki/Collatz_conjecture

View File

@@ -0,0 +1,23 @@
function is_even(n::Int)
return n % 2 == 0
end
function is_odd(n::Int)
return n % 2 != 0
end
function collatz_steps(n::Int)
if n <= 0
throw(DomainError("Input must be a positive integer."))
end
steps = 0
while n != 1
if is_even(n)
n ÷= 2
else
n = 3 * n + 1
end
steps += 1
end
return steps
end

View File

@@ -0,0 +1,15 @@
using Test
include("collatz-conjecture.jl")
@testset verbose = true "tests" begin
# canonical data
@testset "Canonical data" begin
@test collatz_steps(1) == 0
@test collatz_steps(16) == 4
@test collatz_steps(12) == 9
@test collatz_steps(1000000) == 152
@test_throws DomainError collatz_steps(0)
@test_throws DomainError collatz_steps(-15)
end
end

View File

@@ -0,0 +1,21 @@
{
"authors": [
"colinleach"
],
"files": {
"solution": [
"currency-exchange.jl"
],
"test": [
"runtests.jl"
],
"exemplar": [
".meta/exemplar.jl"
]
},
"forked_from": [
"python/currency-exchange"
],
"icon": "hyperia-forex",
"blurb": "Learn about numbers by solving Chandler's currency exchange conundrums."
}

View File

@@ -0,0 +1 @@
{"track":"julia","exercise":"currency-exchange","id":"57c3aae34f93402a953b2840e9fbe904","url":"https://exercism.org/tracks/julia/exercises/currency-exchange","handle":"Kimawari","is_requester":true,"auto_approve":false}

View File

@@ -0,0 +1,34 @@
# Help
## Running the tests
To run the tests, run this command from within the exercise directory:
```bash
$ julia runtests.jl
```
## Submitting your solution
You can submit your solution using the `exercism submit currency-exchange.jl` 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 [Julia track's documentation](https://exercism.org/docs/tracks/julia)
- The [Julia track's programming category on the forum](https://forum.exercism.org/c/programming/julia)
- [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.
To get help if you're having trouble, we recommend that you submit your code and request mentoring :)
If you don't want to do that for whatever reason, then you can find the wider Julia community channels [here](https://julialang.org/community/).

View File

@@ -0,0 +1,41 @@
# Hints
## General
## 1. Estimate value after exchange
- You can use the [division operator][division-operator] to get the value of exchanged currency.
## 2. Calculate currency left after an exchange
- You can use the [subtraction operator][subtraction-operator] to get the amount of change.
## 3. Calculate value of bills
- You can use the [multiplication operator][multiplication-operator] to get the value of bills.
## 4. Calculate number of bills
- You need to divide `amount` into `denomination`.
- To remove decimal places from a floating point value, you can use the [`floor()`][floor] function.
- You need an integer to get the exact number of bills.
The `floor` function can take the desired output type as a parameter.
**Note:** The [`÷` operator][div], or `div()` function, also does floor division. However, if an operand is floating point the result is still floating point.
## 5. Calculate leftover after exchanging into bills
- You need to find the remainder of `amount` that does not equal a whole `denomination`.
- The [Modulo operator][div] `%` can help find the remainder.
## 6. Calculate value after exchange
- You need to calculate `spread` percent of `exchange_rate` using multiplication operator and add it to `exchange_rate` to get the exchanged currency.
- The actual rate needs to be computed. Remember to add exchange _rate_ and exchange _fee_.
- You can get exchanged money affected by commission by using a divide operation, plus type casting to integer if necessary.
[division-operator]: https://docs.julialang.org/en/v1/manual/mathematical-operations/#Arithmetic-Operators
[multiplication-operator]: https://docs.julialang.org/en/v1/manual/mathematical-operations/#Arithmetic-Operators#Arithmetic-Operators
[subtraction-operator]: https://docs.julialang.org/en/v1/manual/mathematical-operations/#Arithmetic-Operators
[floor]: https://docs.julialang.org/en/v1/base/math/#Base.floor
[div]: https://benlauwens.github.io/ThinkJulia.jl/latest/book.html#_floor_division_and_modulus

View File

@@ -0,0 +1,313 @@
# Currency Exchange
Welcome to Currency Exchange on Exercism's Julia Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
Julia is a general-purpose language that can be used for most programming tasks.
In practice, however, the main use cases tend to be in engineering and science.
Fast, versatile, sophisticated numerical calculations are central to the design.
## Integers
An integer is a "round" number with no decimal point.
In the [Basics][basics] concept, we saw that an integer value can be assigned to a variable without specifying a type.
For readability, underscores can be used as a digit separator.
They are ignored by the compiler.
```julia-repl
julia> x = 3
3
julia> typeof(x)
Int64
julia> large_number = 1_234_567_890
1234567890
```
## Floating-point
It will be no surprise that floating-point numbers have a decimal point, and a fractional part after the point.
```julia-repl
julia> f = 3.45
3.45
julia> typeof(f)
Float64
```
Of course, scientific notation is supported.
```julia-repl
julia> avogadro = 6.02e23
6.02e23
```
The maximum and minimum values may come as a surprise:
```julia-repl
julia> typemax(Float64)
Inf
julia> typemin(Float64)
-Inf
```
Infinity is a valid value!
## Arithmetic operators
As discussed in the Basics concept, arithmetic operators mostly work the same as standard arithmetic, as taught to children.
Note that exponentiation uses `^`, _not_ `**` (both are common in other languages).
```julia
2 + 3 # 5 (addition)
2 - 3 # -1 (subtraction)
2 * 3 # 6 (multiplication)
8 / 2 # 4.0 (division)
8 % 3 # 2 (remainder)
2 ^ 3 # 8 (exponentiation)
```
However, a few Julia-specific details are worth discussing.
### Multiplication
```julia-repl
julia> x = 4.2
4.2
julia> 2 * x
8.4
julia> 2x
8.4
julia> 2.4x
10.08
```
That may be surprising.
It is always possible to use `*` as an infix operator, as in most other computer languages.
However, Julia is designed by people who believe that code should look as much as possible like mathematical equations.
Because variable names must start with a letter, prefacing the name with a number (integer or floating-point) is treated as implicit multiplication.
For example, if we want the surface area of a sphere, instead of `4 * pi * r * r` we could do this :
```julia-repl
julia> surface(r) = 4π * r^2
surface (generic function with 1 method)
julia> surface(3)
113.09733552923255
```
Although π is a built-in constant, it is also a (Greek) letter.
The parser therefore still needs one explicit `*` to separate `π` from `r`.
### Division
Using `/` as the infix operator will always give a floating-point result, even for integer inputs.
For integer division, there are more options.
```julia-repl
julia> 10 / 3 # floating-point division
3.3333333333333335
julia> div(10, 3) # integer division
3
julia> 10 ÷ 3 # synonym for div()
3
julia> 10 // 3 # rational number (fraction)
10//3
```
The `div()` function is for integer division, with the result truncated towards zero: downwards for positive numbers, upwards for negative numbers.
As a synonym, we can use the infix operator `÷`, again aiming to make it look more mathematical.
If you are using a Julia-aware editor, enter this as `\div` then hit the `<Tab>` key.
The `//` operator is beyond the scope of this Concept.
For now, we can just say that the result of `//` is a "rational" number, which most people call a _fraction_.
## Conversion of numeric types
This can often happen automatically:
```julia-repl
julia> x = 2 + 3.5
5.5
julia> typeof(x)
Float64
```
We added an `Int64` to a `Float64`, and got a `Float64` result.
In fact, the integer was silently converted to a `Float64` before doing the addition.
**Float-to-integer** conversions are inevitably more complicated.
What do you want to do with anything after the decimal point?
- The `round()` function converts to the nearest whole number, with ties such as 4.5 rounding to the nearest _even_ whole number.
- `floor()` rounds down, `ceil()` rounds up, `trunc()` rounds towards zero.
- Attempting to cast directly, for example with `Int32()`, will fail with an `InexactError`.
However, by default these functions do not return the integer type you might have wanted.
The desired output type can be specified.
```julia-repl
julia> round(4.5)
4.0
julia> ceil(Int, 4.3)
5
```
Rounding to a specified number of digits after the decimal point is also possible with the `digits` keyword.
```julia-repl
julia> round(π, digits=10)
3.1415926536
```
## Divide-by-zero
Surely this just throws an error?
In fact, the situation is not that simple.
Integer division with `÷` or `//` will result in an error, as you might expect.
Floating-point division with `/` takes what might be considered an engineering approach, rather than a standard computer science approach:
```julia-repl
julia> 2 / 0
Inf
julia> 0 / 0
NaN
```
As discussed in a previous section, infinity is a valid floating-point number in Julia, represented by `Inf`.
When the numerator is also zero, the result is mathematically undefined.
Julia then treats it as "not a number", represented by `NaN`.
[basics]: https://exercism.org/tracks/julia/concepts/basics
## Instructions
Your friend Chandler plans to visit exotic countries all around the world. Sadly, Chandler's math skills aren't good. He's pretty worried about being scammed by currency exchanges during his trip - and he wants you to make a currency calculator for him. Here are his specifications for the app:
## 1. Estimate value after exchange
Create the `exchange_money()` function, taking 2 parameters:
1. `budget` : The amount of money you are planning to exchange.
2. `exchange_rate` : The amount of domestic currency equal to one unit of foreign currency.
This function should return the value of the exchanged currency.
**Note:** If your currency is USD and you want to exchange USD for EUR with an exchange rate of `1.20`, then `1.20 USD == 1 EUR`.
```julia
julia> exchange_money(127.5, 1.2)
106.25
```
## 2. Calculate currency left after an exchange
Create the `get_change()` function, taking 2 parameters:
1. `budget` : Amount of money before exchange.
2. `exchanging_value` : Amount of money that is *taken* from the budget to be exchanged.
This function should return the amount of money that *is left* from the budget.
```julia
julia> get_change(127.5, 120)
7.5
```
## 3. Calculate value of bills
Create the `get_value_of_bills()` function, taking 2 parameters:
1. `denomination` : The value of a single bill.
2. `number_of_bills` : The total number of bills.
This exchanging booth only deals in cash of certain increments.
The total you receive must be divisible by the value of one "bill" or unit, which can leave behind a fraction or remainder.
Your function should return only the total value of the bills (_excluding fractional amounts_) the booth would give back.
Unfortunately, the booth gets to keep the remainder/change as an added bonus.
```julia
julia> get_value_of_bills(5, 128)
640
```
## 4. Calculate number of bills
Create the `get_number_of_bills()` function, taking `amount` and `denomination`.
This function should return the _number of currency bills_ that you can receive within the given _amount_.
In other words: How many _whole bills_ of currency fit into the starting amount?
Remember -- you can only receive _whole bills_, not fractions of bills, so remember to divide accordingly.
Effectively, you are rounding _down_ to the nearest whole bill/denomination.
```julia
julia> get_number_of_bills(127.5, 5)
25
```
## 5. Calculate leftover after exchanging into bills
Create the `get_leftover_of_bills()` function, taking `amount` and `denomination`.
This function should return the _leftover amount_ that cannot be returned from your starting _amount_ given the denomination of bills.
It is very important to know exactly how much the booth gets to keep.
```julia
julia> get_leftover_of_bills(127.5, 20)
7.5
```
## 6. Calculate value after exchange
Create the `exchangeable_value()` function, taking `budget`, `exchange_rate`, `spread`, and `denomination`.
Parameter `spread` is the *percentage taken* as an exchange fee, written as an integer.
It needs to be converted to decimal by dividing it by 100.
If `1.00 EUR == 1.20 USD` and the *spread* is `10`, the actual exchange rate will be: `1.00 EUR == 1.32 USD` because 10% of 1.20 is 0.12, and this additional fee is added to the exchange.
This function should return the maximum value of the new currency after calculating the *exchange rate* plus the *spread*.
Remember that the currency *denomination* is a whole number, and cannot be sub-divided.
**Note:** Returned value should be an integer type.
```julia
julia> exchangeable_value(127.25, 1.20, 10, 20)
80
julia> exchangeable_value(127.25, 1.20, 10, 5)
95
```
## Source
### Created by
- @colinleach

View File

@@ -0,0 +1,24 @@
function exchange_money(budget, exchange_rate)
return budget / exchange_rate
end
function get_change(budget, exchanging_value)
return budget - exchanging_value
end
function get_value_of_bills(denomination, number_of_bills)
return denomination * number_of_bills
end
function get_number_of_bills(amount, denomination)
return amount ÷ denomination
end
function get_leftover_of_bills(amount, denomination)
return amount % denomination
end
function exchangeable_value(budget, exchange_rate, spread, denomination)
actual_exchange_rate = exchange_rate * (1 + spread / 100)
return floor(Int, budget / actual_exchange_rate / denomination) * denomination
end

View File

@@ -0,0 +1,41 @@
using Test
include("currency-exchange.jl")
@testset verbose = true "tests" begin
@testset "1. exchange_money" begin
@test isapprox(exchange_money(100000, 0.8), 125000; atol=0.1)
@test isapprox(exchange_money(700000, 10.0), 70000; atol=0.1)
end
@testset "2. get_change" begin
@test isapprox(get_change(463000, 5000), 458000; atol=0.1)
@test isapprox(get_change(1250, 120), 1130; atol=0.1)
@test isapprox(get_change(15000, 1380), 13620; atol=0.1)
end
@testset "3. get_value_of_bills" begin
@test get_value_of_bills(10000, 128) == 1280000
@test get_value_of_bills(50, 360) == 18000
@test get_value_of_bills(200, 200) == 40000
end
@testset "4. get_number_of_bills" begin
@test get_number_of_bills(163270, 50000) == 3
@test get_number_of_bills(54361, 1000) == 54
end
@testset "5. get_leftover_of_bills" begin
@test isapprox(get_leftover_of_bills(10.1, 10), 0.1; atol=1e-8)
@test isapprox(get_leftover_of_bills(654321.0, 5), 1.0; atol=1e-8)
@test isapprox(get_leftover_of_bills(3.14, 2) , 1.14; atol=1e-8)
end
@testset "6. exchangeable_value" begin
@test exchangeable_value(100000, 10.61, 10, 1) == 8568
@test exchangeable_value(1500, 0.84, 25, 40) == 1400
@test exchangeable_value(470000, 1050, 30, 10000000000) == 0
@test exchangeable_value(470000, 0.00000009, 30, 700) == 4017094016600
@test exchangeable_value(425.33, 0.0009, 30, 700) == 363300
end
end

View File

@@ -0,0 +1,21 @@
{
"authors": [
"vyu"
],
"contributors": [
"cmcaine"
],
"files": {
"solution": [
"darts.jl"
],
"test": [
"runtests.jl"
],
"example": [
".meta/example.jl"
]
},
"blurb": "Calculate the points scored in a single toss of a Darts game.",
"source": "Inspired by an exercise created by a professor Della Paolera in Argentina"
}

View File

@@ -0,0 +1 @@
{"track":"julia","exercise":"darts","id":"f274fafe88954ab6bace971d3f6adf19","url":"https://exercism.org/tracks/julia/exercises/darts","handle":"Kimawari","is_requester":true,"auto_approve":false}

34
julia/darts/HELP.md Normal file
View File

@@ -0,0 +1,34 @@
# Help
## Running the tests
To run the tests, run this command from within the exercise directory:
```bash
$ julia runtests.jl
```
## Submitting your solution
You can submit your solution using the `exercism submit darts.jl` 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 [Julia track's documentation](https://exercism.org/docs/tracks/julia)
- The [Julia track's programming category on the forum](https://forum.exercism.org/c/programming/julia)
- [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.
To get help if you're having trouble, we recommend that you submit your code and request mentoring :)
If you don't want to do that for whatever reason, then you can find the wider Julia community channels [here](https://julialang.org/community/).

50
julia/darts/README.md Normal file
View File

@@ -0,0 +1,50 @@
# Darts
Welcome to Darts on Exercism's Julia Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Calculate the points scored in a single toss of a Darts game.
[Darts][darts] is a game where players throw darts at a [target][darts-target].
In our particular instance of the game, the target rewards 4 different amounts of points, depending on where the dart lands:
![Our dart scoreboard with values from a complete miss to a bullseye](https://assets.exercism.org/images/exercises/darts/darts-scoreboard.svg)
- If the dart lands outside the target, player earns no points (0 points).
- If the dart lands in the outer circle of the target, player earns 1 point.
- If the dart lands in the middle circle of the target, player earns 5 points.
- If the dart lands in the inner circle of the target, player earns 10 points.
The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1.
Of course, they are all centered at the same point — that is, the circles are [concentric][] defined by the coordinates (0, 0).
Given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), calculate the correct score earned by a dart landing at that point.
## Credit
The scoreboard image was created by [habere-et-dispertire][habere-et-dispertire] using [Inkscape][inkscape].
[darts]: https://en.wikipedia.org/wiki/Darts
[darts-target]: https://en.wikipedia.org/wiki/Darts#/media/File:Darts_in_a_dartboard.jpg
[concentric]: https://mathworld.wolfram.com/ConcentricCircles.html
[cartesian-coordinates]: https://www.mathsisfun.com/data/cartesian-coordinates.html
[real-numbers]: https://www.mathsisfun.com/numbers/real-numbers.html
[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire
[inkscape]: https://en.wikipedia.org/wiki/Inkscape
## Source
### Created by
- @vyu
### Contributed to by
- @cmcaine
### Based on
Inspired by an exercise created by a professor Della Paolera in Argentina

12
julia/darts/darts.jl Normal file
View File

@@ -0,0 +1,12 @@
function score(x, y)
dist = sqrt(x^2 + y^2)
if dist <= 1
return 10
elseif dist <= 5
return 5
elseif dist <= 10
return 1
else
return 0
end
end

57
julia/darts/runtests.jl Normal file
View File

@@ -0,0 +1,57 @@
using Test
include("darts.jl")
@testset verbose = true "tests" begin
@testset "Missed target" begin
@test score(-9, 9) == 0
end
@testset "On the outer circle" begin
@test score(0, 10) == 1
end
@testset "On the middle circle" begin
@test score(-5, 0) == 5
end
@testset "On the inner circle" begin
@test score(0, -1) == 10
end
@testset "Exactly on centre" begin
@test score(0, 0) == 10
end
@testset "Near the centre" begin
@test score(-0.1, -0.1) == 10
end
@testset "Just within the inner circle" begin
@test score(0.7, 0.7) == 10
end
@testset "Just outside the inner circle" begin
@test score(0.8, -0.8) == 5
end
@testset "Just within the middle circle" begin
@test score(-3.5, 3.5) == 5
end
@testset "Just outside the middle circle" begin
@test score(-3.6, -3.6) == 1
end
@testset "Just within the outer circle" begin
@test score(-7.0, 7.0) == 1
end
@testset "Just outside the outer circle" begin
@test score(7.1, -7.1) == 0
end
@testset "Asymmetric position between the inner and middle circles" begin
@test score(0.5, -4) == 5
end
end

View File

@@ -0,0 +1,23 @@
{
"authors": [
"SaschaMann"
],
"contributors": [
"bovine3dom",
"cmcaine"
],
"files": {
"solution": [
"difference-of-squares.jl"
],
"test": [
"runtests.jl"
],
"example": [
".meta/example.jl"
]
},
"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"
}

View File

@@ -0,0 +1 @@
{"track":"julia","exercise":"difference-of-squares","id":"43726f141cb245cbb9d9c8b9cbdc4dc7","url":"https://exercism.org/tracks/julia/exercises/difference-of-squares","handle":"Kimawari","is_requester":true,"auto_approve":false}

View File

@@ -0,0 +1,34 @@
# Help
## Running the tests
To run the tests, run this command from within the exercise directory:
```bash
$ julia runtests.jl
```
## Submitting your solution
You can submit your solution using the `exercism submit difference-of-squares.jl` 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 [Julia track's documentation](https://exercism.org/docs/tracks/julia)
- The [Julia track's programming category on the forum](https://forum.exercism.org/c/programming/julia)
- [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.
To get help if you're having trouble, we recommend that you submit your code and request mentoring :)
If you don't want to do that for whatever reason, then you can find the wider Julia community channels [here](https://julialang.org/community/).

Some files were not shown because too many files have changed in this diff Show More