From d87e83206ea21bc2290ce459ffba878e94e49f95 Mon Sep 17 00:00:00 2001 From: Ian Goodnight Date: Sun, 16 Jun 2024 14:44:45 -0400 Subject: week 273 --- challenge-273/iangoodnight/README.md | 1 + challenge-273/iangoodnight/perl/README.md | 320 ++ challenge-273/iangoodnight/perl/t/task1.t | 37 + challenge-273/iangoodnight/perl/t/task2.t | 27 + challenge-273/iangoodnight/perl/task1.pm | 201 ++ challenge-273/iangoodnight/perl/task2.pm | 157 + challenge-273/iangoodnight/python/.gitignore | 1 + challenge-273/iangoodnight/python/README.md | 244 ++ challenge-273/iangoodnight/python/__init__.py | 0 challenge-273/iangoodnight/python/requirements.txt | 4 + challenge-273/iangoodnight/python/task1.py | 85 + challenge-273/iangoodnight/python/task2.py | 61 + .../iangoodnight/python/tests/__init__.py | 7 + .../iangoodnight/python/tests/test_task1.py | 22 + .../iangoodnight/python/tests/test_task2.py | 10 + challenge-273/iangoodnight/typescript/README.md | 256 ++ .../typescript/__tests__/task1.test.ts | 10 + .../typescript/__tests__/task2.test.ts | 19 + .../iangoodnight/typescript/jest.config.json | 4 + .../iangoodnight/typescript/package-lock.json | 3598 ++++++++++++++++++++ challenge-273/iangoodnight/typescript/package.json | 11 + challenge-273/iangoodnight/typescript/task1.ts | 78 + challenge-273/iangoodnight/typescript/task2.ts | 55 + .../iangoodnight/typescript/tsconfig.json | 35 + 24 files changed, 5243 insertions(+) create mode 100644 challenge-273/iangoodnight/perl/README.md create mode 100644 challenge-273/iangoodnight/perl/t/task1.t create mode 100644 challenge-273/iangoodnight/perl/t/task2.t create mode 100644 challenge-273/iangoodnight/perl/task1.pm create mode 100644 challenge-273/iangoodnight/perl/task2.pm create mode 100644 challenge-273/iangoodnight/python/.gitignore create mode 100644 challenge-273/iangoodnight/python/README.md create mode 100644 challenge-273/iangoodnight/python/__init__.py create mode 100644 challenge-273/iangoodnight/python/requirements.txt create mode 100644 challenge-273/iangoodnight/python/task1.py create mode 100644 challenge-273/iangoodnight/python/task2.py create mode 100644 challenge-273/iangoodnight/python/tests/__init__.py create mode 100644 challenge-273/iangoodnight/python/tests/test_task1.py create mode 100644 challenge-273/iangoodnight/python/tests/test_task2.py create mode 100644 challenge-273/iangoodnight/typescript/README.md create mode 100644 challenge-273/iangoodnight/typescript/__tests__/task1.test.ts create mode 100644 challenge-273/iangoodnight/typescript/__tests__/task2.test.ts create mode 100644 challenge-273/iangoodnight/typescript/jest.config.json create mode 100644 challenge-273/iangoodnight/typescript/package-lock.json create mode 100644 challenge-273/iangoodnight/typescript/package.json create mode 100644 challenge-273/iangoodnight/typescript/task1.ts create mode 100644 challenge-273/iangoodnight/typescript/task2.ts create mode 100644 challenge-273/iangoodnight/typescript/tsconfig.json diff --git a/challenge-273/iangoodnight/README.md b/challenge-273/iangoodnight/README.md index 2a4a23a28c..482703debb 100644 --- a/challenge-273/iangoodnight/README.md +++ b/challenge-273/iangoodnight/README.md @@ -1 +1,2 @@ Solution by Ian Goodnight + diff --git a/challenge-273/iangoodnight/perl/README.md b/challenge-273/iangoodnight/perl/README.md new file mode 100644 index 0000000000..6634a635ac --- /dev/null +++ b/challenge-273/iangoodnight/perl/README.md @@ -0,0 +1,320 @@ +# Perl Weekly Challenge - 273 + +Welcome to [Week #273][0] of [The Weekly Challenge][1]. + +## Task 1: Percentage of Character + +**Submitted by:** [Mohammad Sajid Anwar][2] + +You are given a string, `$str` and a character `$char`. + +Write a script to return the percentage, nearest whole, of given character in +the given string. + +**Example 1** + +``` +Input: $str = "perl", $char = "e" +Output: 25 +``` + +**Example 2** + +``` +Input: $str = "java", $char = "a" +Output: 50 +``` + +**Example 3** + +``` +Input: $str = "python", $char = "m" +Output: 0 +``` + +**Example 4** + +``` +Input: $str = "ada", $char = "a" +Output: 67 +``` + +**Example 5** + +``` +Input: $str = "ballerina", $char = "l" +Output: 22 +``` + +**Example 6** + +``` +Input: $str = "analitik", $char = "k" +Output: 13 +``` + +### Solution + +Our solution is implemented as the [task1.pm][3] module. The module provides a +single exported function `percentage_of_character` that takes two arguments as +described in the task and returns the percentage of the given character in the +given string. + +```perl +use strict; +use warnings; +use autodie; + +use Exporter; +use Readonly; + +our $VERSION = '1.0.0'; + +our @EXPORT_OK = qw(percentage_of_character); + +Readonly::Scalar my $EMPTY => q{}; # for splitting the string +Readonly::Scalar my $PERCENTAGE_FACTOR => 100; # for converting decimal +Readonly::Scalar my $ROUNDING_THRESHOLD => 0.5; # for rounding the percentage + +sub percentage_of_character { + my ( $str, $char ) = @_; + + # string length ends up as the denominator of our percentage calculation + my $string_length = length $str // 0; + + # if the string is empty, return 0 rather than dividing by zero + if ( $string_length == 0 || !defined $char ) { + return 0; + } + + # grab characters as a list + my @characters = split $EMPTY, $str; + + # filter down to the characters that match the given character + my @matching_characters = grep { $_ eq $char } @characters; + + # count the number of matching characters + my $matching_characters_count = scalar @matching_characters; + + # calculate the percentage as a decimal + my $decimal_percentage = $matching_characters_count / $string_length; + + # convert the decimal to a percentage + my $percentage = $decimal_percentage * $PERCENTAGE_FACTOR; + + # round the percentage to the nearest whole number + my $rounded_percentage = int( $percentage + $ROUNDING_THRESHOLD ); + + # return the rounded percentage as the result + return $rounded_percentage; +} + +1; +``` + +This solution could be a lot more concise, but we have opted for clarity and +readability (possibly at the expense of brevity). For the most part, the +comments in the code should be self-explanatory. We have used the `Readonly` +module to define some constants that are used in the calculation of the +percentage. The `percentage_of_character` function takes the string and the +character as arguments and returns the percentage of the character in the string +as an integer. + +### Testing + +We have included a test script to verify the correctness of our solution. The +test script uses the `Test::More` module to run a series of tests against the +`percentage_of_character` function. The test script is as follows: + +```perl +use strict; +use warnings; +use autodie; + +use Readonly; +use Test::More; + +use lib qw( . ); + +use Task1 qw(percentage_of_character); + +Readonly::Scalar my $EMPTY_STR => q{}; + +subtest 'Examples' => sub { + plan tests => 6; + + is( percentage_of_character( 'perl', 'e' ), 25, 'Example 1' ); + is( percentage_of_character( 'java', 'a' ), 50, 'Example 2' ); + is( percentage_of_character( 'python', 'm' ), 0, 'Example 3' ); + is( percentage_of_character( 'ada', 'a' ), 67, 'Example 4' ); + is( percentage_of_character( 'ballerina', 'l' ), 22, 'Example 5' ); + is( percentage_of_character( 'analitik', 'k' ), 13, 'Example 6' ); +}; + +subtest 'Additional tests' => sub { + plan tests => 7; + + is( percentage_of_character( 'perl', 'x' ), 0, 'Character not found' ); + is( percentage_of_character( 'perl', 2 ), 0, 'Input other than strings' ); + is( percentage_of_character( 2, 'e' ), 0, 'Input other than strings' ); + is( percentage_of_character( $EMPTY_STR, 'e' ), 0, 'Empty string' ); + is( percentage_of_character( 'perl', $EMPTY_STR ), 0, 'Empty character' ); + is( percentage_of_character('perl'), 0, 'No character argument' ); + is( percentage_of_character(), 0, 'No input' ); +}; + +done_testing(); +``` + +You can run the test script using the following command (assumes the `prove` +utility is available): + +```bash +prove ./t/task1.t +``` + +## Task 2: B After A + +**Submitted by:** [Mohammad Sajid Anwar][2] + +You are given a string, `$str`. + +Write a script to return `true` if there is at least one `b`, and no `a` appears +after the first `b`. + +**Example 1** + +``` +Input: $str = "aabb" +Output: true +``` + +**Example 2** + +``` +Input: $str = "abab" +Output: false +``` + +**Example 3** + +``` +Input: $str = "aaa" +Output: false +``` + +**Example 4** + +``` +Input: $str = "bbb" +Output: true +``` + +### Solution + +Our solution is implemented as the [task2.pm][4] module. The module provides a +single exported function `b_after_a` that takes a single argument as described +in the task and returns a boolean value indicating whether the given string +satisfies the conditions of the task. + +```perl + +use strict; +use warnings; +use autodie; +use Exporter; + +our $VERSION = '1.0.0'; + +our @EXPORT_OK = qw(b_after_a); + +sub b_after_a { + my $str = shift // q{}; + + # Regular expression to match the string + ## no critic (RegularExpressions::RequireDotMatchAnything) + my $re = qr{ + ^ # Start of the string + [^b]* # Match any character except 'b' zero or more times + b+ # Match 'b' one or more times + [^a]* # Match any character except 'a' zero or more times + $ # End of the string + }mx; + ## use critic + # Test the string against the regular expression and return the result + if ( $str =~ $re ) { + return 1; + } + return 0; +} + +1; +``` + +Our function uses a regular expression to match the against the conditions input +string. Arguably, the regular expression could be more concise, but despite the +prompt example describing all the input strings as being comprised of only 'a' +and 'b' characters, the initial problem statement does not explicitly constrain +the input to only those characters. Therefore, we have opted for a more general +regular expression that will matches strings that contain any any character +after the first 'b' that is not an 'a'. Perlcritic has reminded us that in many +regex implementations, the dot character '.' is a wildcard that matches any +character except a newline. This is certainly worth considering, but in this +particular case, we do not use the dot character in our regular expression. We +opt to disable this particular critic rule for this regular expression rather +than add a superfluous flag to the regular expression. + +### Testing + +We have included a test script to verify the correctness of our solution. The +test script uses the `Test::More` module to run a series of tests against the +`b_after_a` function. The test script is as follows: + +```perl +use strict; +use warnings; +use autodie; +use Test::More; + +use lib qw( . ); + +use task2 qw(b_after_a); + +subtest 'Examples' => sub { + plan tests => 4; + + is( b_after_a('aabb'), 1, 'Example 1' ); + is( b_after_a('abab'), 0, 'Example 2' ); + is( b_after_a('aaa'), 0, 'Example 3' ); + is( b_after_a('bbb'), 1, 'Example 4' ); +}; + +subtest 'Additional tests' => sub { + plan tests => 3; + + is( b_after_a('abbc'), 1, 'Characters other than "a" after "b"' ); + is( b_after_a(2), 0, 'Input other than strings' ); + is( b_after_a(), 0, 'No input' ); +}; + +done_testing(); +``` + +You can run the test script using the following command (assumes the `prove` +utility is available): + +```bash +prove ./t/task2.t +``` + +You can run both test scripts using the following command: + +```bash +prove -r +``` + +[0]: https://perlweeklychallenge.org/blog/perl-weekly-challenge-272/ +[1]: https://perlweeklychallenge.org +[2]: https://manwar.org/ +[3]: ./task1.pm +[4]: ./task2.pm diff --git a/challenge-273/iangoodnight/perl/t/task1.t b/challenge-273/iangoodnight/perl/t/task1.t new file mode 100644 index 0000000000..5c134834d3 --- /dev/null +++ b/challenge-273/iangoodnight/perl/t/task1.t @@ -0,0 +1,37 @@ +use strict; +use warnings; +use autodie; + +use Readonly; +use Test::More; + +use lib qw( . ); + +use Task1 qw(percentage_of_character); + +Readonly::Scalar my $EMPTY_STR => q{}; + +subtest 'Examples' => sub { + plan tests => 6; + + is( percentage_of_character( 'perl', 'e' ), 25, 'Example 1' ); + is( percentage_of_character( 'java', 'a' ), 50, 'Example 2' ); + is( percentage_of_character( 'python', 'm' ), 0, 'Example 3' ); + is( percentage_of_character( 'ada', 'a' ), 67, 'Example 4' ); + is( percentage_of_character( 'ballerina', 'l' ), 22, 'Example 5' ); + is( percentage_of_character( 'analitik', 'k' ), 13, 'Example 6' ); +}; + +subtest 'Additional tests' => sub { + plan tests => 7; + + is( percentage_of_character( 'perl', 'x' ), 0, 'Character not found' ); + is( percentage_of_character( 'perl', 2 ), 0, 'Input other than strings' ); + is( percentage_of_character( 2, 'e' ), 0, 'Input other than strings' ); + is( percentage_of_character( $EMPTY_STR, 'e' ), 0, 'Empty string' ); + is( percentage_of_character( 'perl', $EMPTY_STR ), 0, 'Empty character' ); + is( percentage_of_character('perl'), 0, 'No character argument' ); + is( percentage_of_character(), 0, 'No input' ); +}; + +done_testing(); diff --git a/challenge-273/iangoodnight/perl/t/task2.t b/challenge-273/iangoodnight/perl/t/task2.t new file mode 100644 index 0000000000..92563be8e3 --- /dev/null +++ b/challenge-273/iangoodnight/perl/t/task2.t @@ -0,0 +1,27 @@ +use strict; +use warnings; +use autodie; +use Test::More; + +use lib qw( . ); + +use task2 qw(b_after_a); + +subtest 'Examples' => sub { + plan tests => 4; + + is( b_after_a('aabb'), 1, 'Example 1' ); + is( b_after_a('abab'), 0, 'Example 2' ); + is( b_after_a('aaa'), 0, 'Example 3' ); + is( b_after_a('bbb'), 1, 'Example 4' ); +}; + +subtest 'Additional tests' => sub { + plan tests => 3; + + is( b_after_a('abbc'), 1, 'Characters other than "a" after "b"' ); + is( b_after_a(2), 0, 'Input other than strings' ); + is( b_after_a(), 0, 'No input' ); +}; + +done_testing(); diff --git a/challenge-273/iangoodnight/perl/task1.pm b/challenge-273/iangoodnight/perl/task1.pm new file mode 100644 index 0000000000..645852ce09 --- /dev/null +++ b/challenge-273/iangoodnight/perl/task1.pm @@ -0,0 +1,201 @@ +#!/usr/bin/perl + +=begin comment + +## Task 1: Percentage of Character + +**Submitted by:** [Mohammad Sajid Anwar][1] + +You are given a string, `$str` and a character `$char`. + +Write a script to return the percentage, nearest whole, of given character in +the given string. + +**Example 1** + +``` +Input: $str = "perl", $char = "e" +Output: 25 +``` + +**Example 2** + +``` +Input: $str = "java", $char = "a" +Output: 50 +``` + +**Example 3** + +``` +Input: $str = "python", $char = "m" +Output: 0 +``` + +**Example 4** + +``` +Input: $str = "ada", $char = "a" +Output: 67 +``` + +**Example 5** + +``` +Input: $str = "ballerina", $char = "l" +Output: 22 +``` + +**Example 6** + +``` +Input: $str = "analitik", $char = "k" +Output: 13 +``` + +[1]: https://manwar.org/ + +=end comment +=cut + +use strict; +use warnings; +use autodie; + +use Exporter; +use Readonly; + +our $VERSION = '1.0.0'; + +our @EXPORT_OK = qw(percentage_of_character); + +Readonly::Scalar my $EMPTY => q{}; # for splitting the string +Readonly::Scalar my $PERCENTAGE_FACTOR => 100; # for converting decimal +Readonly::Scalar my $ROUNDING_THRESHOLD => 0.5; # for rounding the percentage + +sub percentage_of_character { + my ( $str, $char ) = @_; + + # string length ends up as the denominator of our percentage calculation + my $string_length = length $str // 0; + + # if the string is empty, return 0 rather than dividing by zero + if ( $string_length == 0 || !defined $char ) { + return 0; + } + + # grab characters as a list + my @characters = split $EMPTY, $str; + + # filter down to the characters that match the given character + my @matching_characters = grep { $_ eq $char } @characters; + + # count the number of matching characters + my $matching_characters_count = scalar @matching_characters; + + # calculate the percentage as a decimal + my $decimal_percentage = $matching_characters_count / $string_length; + + # convert the decimal to a percentage + my $percentage = $decimal_percentage * $PERCENTAGE_FACTOR; + + # round the percentage to the nearest whole number + my $rounded_percentage = int( $percentage + $ROUNDING_THRESHOLD ); + + # return the rounded percentage as the result + return $rounded_percentage; +} + +1; + +__END__ + +=encoding utf-8 + +=head1 NAME + +Task 1: Percentage of Character + +=head1 VERSION + +This documentation refers to Task 1: Percentage of Character version 1.0.0 + +=head1 EXPORTS + +The function C is exported by default. + +=head1 USAGE + + use strict; + use warnings; + use feature qw(say); + use Task1 qw(percentage_of_character); + + my $str = 'perl'; + my $char = 'e'; + say percentage_of_character( $str, $char ); # output: 25 + +=head1 DESCRIPTION + +Given a string, C<$str> and a character C<$char>, the function +C returns the percentage, rounded to the nearest whole +number, of the given character in the given string. + +=head1 REQUIRED ARGUMENTS + +=over 4 + +=item C<$str> + +A string. + +=item C<$char> + +A character. + +=back + +=head1 OPTIONS + +None. + +=head1 DIAGNOSTICS + +None. + +=head1 EXIT STATUS + +None. + +=head1 CONFIGURATION + +None. + +=head1 DEPENDENCIES + +=over 4 + +=item * Exporter + +=item * Readonly + +=back + +=head1 INCOMPATIBILITIES + +None reported. + +=head1 BUGS AND LIMITATIONS + +None reported. + +=head1 AUTHOR + +Ian Goodnight + +=head1 LICENSE AND COPYRIGHT + +This software is released under the terms of the GNU General Public License +Version 3. + +=cut diff --git a/challenge-273/iangoodnight/perl/task2.pm b/challenge-273/iangoodnight/perl/task2.pm new file mode 100644 index 0000000000..14ee6b18de --- /dev/null +++ b/challenge-273/iangoodnight/perl/task2.pm @@ -0,0 +1,157 @@ +#!/usr/bin/perl + +=begin comment + +## Task 2: B After A + +**Submitted by:** [Mohammad Sajid Anwar][2] + +You are given a string, `$str`. + +Write a script to return `true` if there is at least one `b`, and no `a` appears +after the first `b`. + +**Example 1** + +``` +Input: $str = "aabb" +Output: true +``` + +**Example 2** + +``` +Input: $str = "abab" +Output: false +``` + +**Example 3** + +``` +Input: $str = "aaa" +Output: false +``` + +**Example 4** + +``` +Input: $str = "bbb" +Output: true +``` + +[2]: https://manwar.org/ + +=end comment +=cut + +use strict; +use warnings; +use autodie; +use Exporter; + +our $VERSION = '1.0.0'; + +our @EXPORT_OK = qw(b_after_a); + +sub b_after_a { + my $str = shift // q{}; + + # Regular expression to match the string + ## no critic (RegularExpressions::RequireDotMatchAnything) + my $re = qr{ + ^ # Start of the string + [^b]* # Match any character except 'b' zero or more times + b+ # Match 'b' one or more times + [^a]* # Match any character except 'a' zero or more times + $ # End of the string + }mx; + ## use critic + # Test the string against the regular expression and return the result + if ( $str =~ $re ) { + return 1; + } + return 0; +} + +1; + +__END__ + +=head1 NAME + +Task2: B After A + +=head1 VERSION + +This documentation refers to Task2 version 1.0.0 + +=head1 EXPORTS + +The function L is exported by default. + +=head1 USAGE + + use strict; + use warnings; + use feature qw(say); + use Task2 qw(b_after_a); + + my $str1 = 'aabb'; + my $str2 = 'abab'; + + say b_after_a($str1); # Output: 1 + say b_after_a($str2); # Output: 0 + +=head1 DESCRIPTION + +Given a string, C<$str>, the function C returns true if there is at +least one 'b', and no 'a' appears after the first 'b'. + +=head1 REQUIRED ARGUMENTS + +=over 4 + +=item C<$str> + +A string. + +=back + +=head1 OPTIONS + +None. + +=head1 DIAGNOSTICS + +None. + +=head1 EXIT STATUS + +None. + +=head1 CONFIGURATION + +None. + +=head1 DEPENDENCIES + +None. + +=head1 INCOMPATIBILITIES + +None reported. + +=head1 BUGS AND LIMITATIONS + +None reported. + +=head1 AUTHOR + +Ian Goodnight + +=head1 LICENSE AND COPYRIGHT + +This software is released under the terms of theGNU General Public License +Version 3. + +=cut diff --git a/challenge-273/iangoodnight/python/.gitignore b/challenge-273/iangoodnight/python/.gitignore new file mode 100644 index 0000000000..c18dd8d83c --- /dev/null +++ b/challenge-273/iangoodnight/python/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/challenge-273/iangoodnight/python/README.md b/challenge-273/iangoodnight/python/README.md new file mode 100644 index 0000000000..31ae6aed6e --- /dev/null +++ b/challenge-273/iangoodnight/python/README.md @@ -0,0 +1,244 @@ +# Perl Weekly Challenge - 273 + +Welcome to [Week #273][0] of [The Weekly Challenge][1]. + +## Task 1: Percentage of Character + +**Submitted by:** [Mohammad Sajid Anwar][2] + +You are given a string, `$str` and a character `$char`. + +Write a script to return the percentage, nearest whole, of given character in +the given string. + +**Example 1** + +``` +Input: $str = "perl", $char = "e" +Output: 25 +``` + +**Example 2** + +``` +Input: $str = "java", $char = "a" +Output: 50 +``` + +**Example 3** + +``` +Input: $str = "python", $char = "m" +Output: 0 +``` + +**Example 4** + +``` +Input: $str = "ada", $char = "a" +Output: 67 +``` + +**Example 5** + +``` +Input: $str = "ballerina", $char = "l" +Output: 22 +``` + +**Example 6** + +``` +Input: $str = "analitik", $char = "k" +Output: 13 +``` + +### Solution + +### Testing + +Our [solution][3] is intentionally designed to be simple and easy to understand. +We have defined two constants, `PERCENTAGE_FACTOR` and `ROUNDING_THRESHOLD`, to +help with the calculation of the percentage. The `percentage_of_character` +function takes two arguments, `string` and `char`, and returns the percentage of +the character in the string, rounded to the nearest whole number. Using a +declarative approach, we first calculate the length of the string and the number +of times the character appears in the string. We then calculate the percentage +of the character in the string as a decimal, convert it to a percentage, and +round it to the nearest whole number using half up rounding. It came as a +surprise to find that python implements an `even up` rounding by default, which +could lead to incorrect results. With an `even up` rounding strategy, a value of +`0.5` would be rounded down to `0`, whereas we want it to be rounded up to `1`. +The rounding method we have used is known as `half up` rounding, which rounds +`0.5` up to `1`. The function returns `0` if the string or character are empty. + +```python +PERCENTAGE_FACTOR = 100 # multiply the decimal value by 100 to get percentage +ROUNDING_THRESHOLD = 0.5 # ensure we are rounding half up, rather than even up + + +def percentage_of_character(string: str, char: str) -> int: + """Return the percentage of given character in the given string. + + Args: + string: The string to search for the character. + char: The character to search for in the string. + + Returns: + The percentage of the character in the string, rounded to the nearest + whole number. + """ + # Get the length of the string for testing and calculating the percentage + string_length = len(string) + # Return 0 if the string or character are empty + if string_length == 0 or len(char) == 0: + return 0 + # Count the number of times the character appears in the string + char_count = string.count(char) + # Calculate the percentage of the character in the string as a decimal + decimal_percentage = char_count / string_length + # Convert the decimal percentage to a percentage + percentage = decimal_percentage * PERCENTAGE_FACTOR + # Round the percentage to the nearest whole number (half up rounding) + rounded_percentage = int(percentage + ROUNDING_THRESHOLD) + + return rounded_percentage +``` + +### Testing + +We have written a comprehensive test suite to validate our solution. We have +covered both the base and extra test cases to ensure our solution works as +expected. Runnning the test suite requires the `pytest` module, which can be +installed using `pip install pytest`. + +```python +from challenge273.task1 import percentage_of_character + + +def test_examples() -> None: + """Test if function returns correct percentage of character""" + assert percentage_of_character("perl", "e") == 25 + assert percentage_of_character("java", "a") == 50 + assert percentage_of_character("python", "m") == 0 + assert percentage_of_character("ada", "a") == 67 + assert percentage_of_character("ballerina", "l") == 22 + assert percentage_of_character("analitik", "k") == 13 + + +def test_empty_string() -> None: + """Test if function returns 0 for empty string""" + assert percentage_of_character("", "a") == 0 + + +def test_empty_character() -> None: + """Test if function returns 0 for empty character""" + assert percentage_of_character("python", "") == 0 +``` + +The test suite can be run using the following command: + +```bash +$ pytest test_task1.py +``` + +## Task 2: B After A + +**Submitted by:** [Mohammad Sajid Anwar][2] + +You are given a string, `$str`. + +Write a script to return `true` if there is at least one `b`, and no `a` appears +after the first `b`. + +**Example 1** + +``` +Input: $str = "aabb" +Output: true +``` + +**Example 2** + +``` +Input: $str = "abab" +Output: false +``` + +**Example 3** + +``` +Input: $str = "aaa" +Output: false +``` + +**Example 4** + +``` +Input: $str = "bbb" +Output: true +``` + +### Solution + +Our [solution][4] is simple and easy to understand. The `b_after_a` function +takes a single argument, `string`, and returns `True` if there is at least one +`b` in the string and no `a` appears after the first `b`. We first check if +there is no `b` in the string, in which case we return `False`. We then find the +index of the first `b` in the string and slice the string after the first `b`. +Finally, we check if there is no `a` in the slice and return `True` if that is +the case; otherwise, we return `False`. + +```python +def b_after_a(string: str) -> bool: + """ + Returns `true` if there is at least one `b`, and no `a` appears after the + first `b`. + + Args: + string: The input string to check for the presence of `b` and `a`. + + Returns: + `True` if there is at least one `b`, and no `a` appears after the first + `b`; otherwise, `False`. + """ + if "b" not in string: # fails if there is no `b` in the string + return False + b_index = string.index("b") + # grab a slice of the string after the first `b` + sliced_after_b = string[b_index + 1:] + # check if there is no `a` in the slice + return "a" not in sliced_after_b +``` + +### Testing + +We have written a comprehensive test suite to validate our solution. We have +covered both the base and extra test cases to ensure our solution works as +expected. Runnning the test suite requires the `pytest` module, which can be +installed using `pip install pytest`. + +```python +from challenge273.task2 import b_after_a + + +def test_examples() -> None: + """Test if function returns correct percentage of character""" + assert b_after_a("aabb") is True + assert b_after_a("abab") is False + assert b_after_a("aaa") is False + assert b_after_a("bbb") is True +``` + +The test suite can be run using the following command: + +```bash +$ pytest test_task2.py +``` + +[0]: https://perlweeklychallenge.org/blog/perl-weekly-challenge-272/ +[1]: https://perlweeklychallenge.org +[2]: https://manwar.org/ +[3]: ./task1.py +[4]: ./task2.py + diff --git a/challenge-273/iangoodnight/python/__init__.py b/challenge-273/iangoodnight/python/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/challenge-273/iangoodnight/python/requirements.txt b/challenge-273/iangoodnight/python/requirements.txt new file mode 100644 index 0000000000..4631673210 --- /dev/null +++ b/challenge-273/iangoodnight/python/requirements.txt @@ -0,0 +1,4 @@ +iniconfig==2.0.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.2.2 diff --git a/challenge-273/iangoodnight/python/task1.py b/challenge-273/iangoodnight/python/task1.py new file mode 100644 index 0000000000..47f77081f6 --- /dev/null +++ b/challenge-273/iangoodnight/python/task1.py @@ -0,0 +1,85 @@ +""" +## Task 1: Percentage of Character + +**Submitted by:** [Mohammad Sajid Anwar][0] + +You are given a string, `$str` and a character `$char`. + +Write a script to return the percentage, nearest whole, of given character in +the given string. + +**Example 1** + +``` +Input: $str = "perl", $char = "e" +Output: 25 +``` + +**Example 2** + +``` +Input: $str = "java", $char = "a" +Output: 50 +``` + +**Example 3** + +``` +Input: $str = "python", $char = "m" +Output: 0 +``` + +**Example 4** + +``` +Input: $str = "ada", $char = "a" +Output: 67 +``` + +**Example 5** + +``` +Input: $str = "ballerina", $char = "l" +Output: 22 +``` + +**Example 6** + +``` +Input: $str = "analitik", $char = "k" +Output: 13 +``` + +[0]: https://manwar.org/ +""" + +PERCENTAGE_FACTOR = 100 # multiply the decimal value by 100 to get percentage +ROUNDING_THRESHOLD = 0.5 # ensure we are rounding half up, rather than even up + + +def percentage_of_character(string: str, char: str) -> int: + """Return the percentage of given character in the given string. + + Args: + string: The string to search for the character. + char: The character to search for in the string. + + Returns: + The percentage of the character in the string, rounded to the nearest + whole number. + """ + # Get the length of the string for testing and calculating the percentage + string_length = len(string) + # Return 0 if the string or character are empty + if string_length == 0 or len(char) == 0: + return 0 + # Count the number of times the character appears in the string + char_count = string.count(char) + # Calculate the percentage of the character in the string as a decimal + decimal_percentage = char_count / string_length + # Convert the decimal percentage to a percentage + percentage = decimal_percentage * PERCENTAGE_FACTOR + # Round the percentage to the nearest whole number (half up rounding) + rounded_percentage = int(percentage + ROUNDING_THRESHOLD) + + return rounded_percentage diff --git a/challenge-273/iangoodnight/python/task2.py b/challenge-273/iangoodnight/python/task2.py new file mode 100644 index 0000000000..bda082bb9e --- /dev/null +++ b/challenge-273/iangoodnight/python/task2.py @@ -0,0 +1,61 @@ +""" +## Task 2: B After A + +**Submitted by:** [Mohammad Sajid Anwar][0] + +You are given a string, `$str`. + +Write a script to return `true` if there is at least one `b`, and no `a` +appears after the first `b`. + +**Example 1** + +``` +Input: $str = "aabb" +Output: true +``` + +**Example 2** + +``` +Input: $str = "abab" +Output: false +``` + +**Example 3** + +``` +Input: $str = "aaa" +Output: false +``` + +**Example 4** + +``` +Input: $str = "bbb" +Output: true +``` + +[0]: https://manwar.org/ +""" + + +def b_after_a(string: str) -> bool: + """ + Returns `true` if there is at least one `b`, and no `a` appears after the + first `b`. + + Args: + string: The input string to check for the presence of `b` and `a`. + + Returns: + `True` if there is at least one `b`, and no `a` appears after the first + `b`; otherwise, `False`. + """ + if "b" not in string: # fails if there is no `b` in the string + return False + b_index = string.index("b") + # grab a slice of the string after the first `b` + sliced_after_b = string[b_index + 1:] + # check if there is no `a` in the slice + return "a" not in sliced_after_b diff --git a/challenge-273/iangoodnight/python/tests/__init__.py b/challenge-273/iangoodnight/python/tests/__init__.py new file mode 100644 index 0000000000..c06655e4a4 --- /dev/null +++ b/challenge-273/iangoodnight/python/tests/__init__.py @@ -0,0 +1,7 @@ +""" +Adds the parent directory to the path so that the modules can be imported from +our tests. +""" +import sys + +sys.path.append("..") diff --git a/challenge-273/iangoodnight/python/tests/test_task1.py b/challenge-273/iangoodnight/python/tests/test_task1.py new file mode 100644 index 0000000000..b27365fdac --- /dev/null +++ b/challenge-273/iangoodnight/python/tests/test_task1.py @@ -0,0 +1,22 @@ +"""Tests for task1.py""" +from python.task1 import percentage_of_character + + +def test_examples() -> None: + """Test if function returns correct percentage of character""" + assert percentage_of_character("perl", "e") == 25 + assert percentage_of_character("java", "a") == 50 + assert percentage_of_character("python", "m") == 0 + assert percentage_of_character("ada", "a") == 67 + assert percentage_of_character("ballerina", "l") == 22 + assert percentage_of_character("analitik", "k") == 13 + + +def test_empty_string() -> None: + """Test if function returns 0 for empty string""" + assert percentage_of_character("", "a") == 0 + + +def test_empty_character() -> None: + """Test if function returns 0 for empty character""" + assert percentage_of_character("python", "") == 0 diff --git a/challenge-273/iangoodnight/python/tests/test_task2.py b/challenge-273/iangoodnight/python/tests/test_task2.py new file mode 100644 index 0000000000..12dee45e22 --- /dev/null +++ b/challenge-273/iangoodnight/python/tests/test_task2.py @@ -0,0 +1,10 @@ +"""Tests for task2.py""" +from python.task2 import b_after_a + + +def test_examples() -> None: + """Test if function returns correct percentage of character""" + assert b_after_a("aabb") is True + assert b_after_a("abab") is False + assert b_after_a("aaa") is False + assert b_after_a("bbb") is True diff --git a/challenge-273/iangoodnight/typescript/README.md b/challenge-273/iangoodnight/typescript/README.md new file mode 100644 index 0000000000..cc71407a30 --- /dev/null +++ b/challenge-273/iangoodnight/typescript/README.md @@ -0,0 +1,256 @@ +# Perl Weekly Challenge - 273 + +Welcome to [Week #273][0] of [The Weekly Challenge][1]. + +## Task 1: Percentage of Character + +**Submitted by:** [Mohammad Sajid Anwar][2] + +You are given a string, `$str` and a character `$char`. + +Write a script to return the percentage, nearest whole, of given character in +the given string. + +**Example 1** + +``` +Input: $str = "perl", $char = "e" +Output: 25 +``` + +**Example 2** + +``` +Input: $str = "java", $char = "a" +Output: 50 +``` + +**Example 3** + +``` +Input: $str = "python", $char = "m" +Output: 0 +``` + +**Example 4** + +``` +Input: $str = "ada", $char = "a" +Output: 67 +``` + +**Example 5** + +``` +Input: $str = "ballerina", $char = "l" +Output: 22 +``` + +**Example 6** + +``` +Input: $str = "analitik", $char = "k" +Output: 13 +``` + +### Solution + +Our solution is implemented in the [task1.ts][3] file. The solution provides a +single function, `percentageOfCharacter`, that takes two arguments: a string and +a character. The function calculates the percentage of the character in the +string and returns the nearest whole number. + +```typescript +// the factor to convert a decimal to a percentage +const PERCENTAGE_FACTOR = 100; + +function percentageOfCharacter(str: string, char: string): number { + // the length of the string becomes the denominator + const length = str.length; + // the count of the character becomes the numerator + const characters = str.split(''); + // filter the characters that match the given character + const matchingCharacters = characters.filter((c) => c === char); + // count the matching characters + const matchingCharactersCount = matchingCharacters.length; + // calculate the percentage + const decimalPercentage = matchingCharactersCount / length; + // convert the percentage to a number out of 100 + const percentage = decimalPercentage * PERCENTAGE_FACTOR; + // round the percentage to the nearest whole number + const roundedPercentage = Math.round(percentage); + // return the rounded percentage + return roundedPercentage; +} +``` + +This solution could be a lot shorter. For example we could write it in far +fewer lines like this: + +```typescript +const percentageOfCharacter = (str: string, char: string): number => { + return Math.round( + (str.split('').filter((c) => c === char).length / str.length) * 100, + ); +}; +``` + +However, we chose to write it in a more verbose manner to make it easier to read +and follow along with the logic. In general, code that is written in a more +declarative style is easier to maintain and understand. + +### Testing + +We have included a test suited found in the `__tests__` directory. The test +relies on `jest` to run. If you do not have the `jest` package installed +globally, you can install from our `package.json`. Also included is our +`jest.config.js` file which sets up some typescript specific configurations for +jest. + +```bash +npm install --only=dev +``` + +Our test script takes the example inputs from the problem statement and asserts +that the output matches the expected output. + +```typescript +import percentageOfCharacter from '../task1'; + +test('Task 1: Percentage of Character', () => { + expect(percentageOfCharacter('perl', 'e')).toBe(25); + expect(percentageOfCharacter('java', 'a')).toBe(50); + expect(percentageOfCharacter('python', 'm')).toBe(0); + expect(percentageOfCharacter('ada', 'a')).toBe(67); + expect(percentageOfCharacter('ballerina', 'l')).toBe(22); + expect(percentageOfCharacter('analitik', 'k')).toBe(13); +}); +``` + +Assuming you have `jest` available, or that you have installed from our +`package.json`, you can run the test script like so: + +```bash +# npm test will match against whatever argument is passed as a regex. here, we +# point to the test file for task 1. +npm test 273/__tests__/task1.test.ts +``` + +## Task 2: B After A + +**Submitted by:** [Mohammad Sajid Anwar][2] + +You are given a string, `$str`. + +Write a script to return `true` if there is at least one `b`, and no `a` appears +after the first `b`. + +**Example 1** + +``` +Input: $str = "aabb" +Output: true +``` + +**Example 2** + +``` +Input: $str = "abab" +Output: false +``` + +**Example 3** + +``` +Input: $str = "aaa" +Output: false +``` + +**Example 4** + +``` +Input: $str = "bbb" +Output: true +``` + +### Solution + +Our solution is implemented in the [task2.ts][4] file. The solution provides a +single function, `bAfterA`, that takes a single argument: a string. The function +returns a boolean value indicating whether the string contains at least one `b` +and no `a` appears after the first `b`. + +```typescript +function bAfterA(str: string): boolean { + // the regular expression checks for the presence of a `b` followed by any + // number of characters that are not `a` + // / - start of the regular expression + // ^ - start of the string + // [^b]* - any number of characters that are not `b` + // b - at least one `b` + // [^a]* - any number of characters that are not `a` (even not `b`) + // $ - end of the string + // / - end of the regular expression + return str.match(/^[^b]*b+[^a]*$/) !== null; +} +``` + +We could have just as easily used the `RegExp.test` method to check for a match +and return a boolean value. The `RegExp.test` method returns `true` if the +regular expression matches the string, and `false` otherwise. Our regular +expression could arguably be simplified to `/b[^a]*$/` to achieve the same +result, but we chose to be more explicit in our pattern. + +### Testing + +We have again included a test suite found in the `__tests__` directory. The test +relies on `jest` to run. If you do not have the `jest` package installed +globally, you can install from our `package.json` (if you didn't install it +previously). + +```bash +npm install --only=dev +``` + +Our test script takes the example inputs from the problem statement and asserts +that the output matches the expected output. + +```typescript +import bAfterA from '../task2'; + +describe('Task 2: B After A', () => { + test('Example 1', () => { + expect(bAfterA('aabb')).toBe(true); + }); + + test('Example 2', () => { + expect(bAfterA('abab')).toBe(false); + }); + + test('Example 3', () => { + expect(bAfterA('aaa')).toBe(false); + }); + + test('Example 4', () => { + expect(bAfterA('bbb')).toBe(true); + }); +}); +``` + +Assuming you have `jest` available, or that you have installed from our +`package.json`, you can run the test script like so: + +```bash +# npm test will match against whatever argument is passed as a regex. here, we +# point to the test file for task 2. +npm test 273/__tests__/task2.test.ts +``` + +You can run both test suites at once by running `npm test 273` which will run +both test suites for Task 1 and Task 2. + +[0]: https://perlweeklychallenge.org/blog/perl-weekly-challenge-272/ +[1]: https://perlweeklychallenge.org +[2]: https://manwar.org/ +[3]: ./task1.ts +[4]: ./task2.ts diff --git a/challenge-273/iangoodnight/typescript/__tests__/task1.test.ts b/challenge-273/iangoodnight/typescript/__tests__/task1.test.ts new file mode 100644 index 0000000000..f82bf8e6eb --- /dev/null +++ b/challenge-273/iangoodnight/typescript/__tests__/task1.test.ts @@ -0,0 +1,10 @@ +import percentageOfCharacter from '../task1'; + +test('Task 1: Percentage of Character', () => { + expect(percentageOfCharacter('perl', 'e')).toBe(25); + expect(percentageOfCharacter('java', 'a')).toBe(50); + expect(percentageOfCharacter('python', 'm')).toBe(0); + expect(percentageOfCharacter('ada', 'a')).toBe(67); + expect(percentageOfCharacter('ballerina', 'l')).toBe(22); + expect(percentageOfCharacter('analitik', 'k')).toBe(13); +}); diff --git a/challenge-273/iangoodnight/typescript/__tests__/task2.test.ts b/challenge-273/iangoodnight/typescript/__tests__/task2.test.ts new file mode 100644 index 0000000000..5e28156a25 --- /dev/null +++ b/challenge-273/iangoodnight/typescript/__tests__/task2.test.ts @@ -0,0 +1,19 @@ +import bAfterA from '../task2'; + +describe('Task 2: B After A', () => { + test('Example 1', () => { + expect(bAfterA('aabb')).toBe(true); + }); + + test('Example 2', () => { + expect(bAfterA('abab')).toBe(false); + }); + + test('Example 3', () => { + expect(bAfterA('aaa')).toBe(false); + }); + + test('Example 4', () => { + expect(bAfterA('bbb')).toBe(true); + }); +}); diff --git a/challenge-273/iangoodnight/typescript/jest.config.json b/challenge-273/iangoodnight/typescript/jest.config.json new file mode 100644 index 0000000000..2717f5891f --- /dev/null +++ b/challenge-273/iangoodnight/typescript/jest.config.json @@ -0,0 +1,4 @@ +{ + "preset": "ts-jest", + "testEnvironment": "node" +} diff --git a/challenge-273/iangoodnight/typescript/package-lock.json b/challenge-273/iangoodnight/typescript/package-lock.json new file mode 100644 index 0000000000..82932022c4 --- /dev/null +++ b/challenge-273/iangoodnight/typescript/package-lock.json @@ -0,0 +1,3598 @@ +{ + "name": "typescript", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@types/jest": "^29.5.12", + "jest": "^29.7.0", + "ts-jest": "^29.1.4", + "typescript": "^5.4.5" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/t