diff options
| author | Mohammad Sajid Anwar <Mohammad.Anwar@yahoo.com> | 2024-03-09 22:34:36 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-09 22:34:36 +0000 |
| commit | 3a95281e37c0cdfa97b2c0af3aa9d32315ab5b71 (patch) | |
| tree | 77014cc788e4f4c503c478e49f02c001d1d3f7db | |
| parent | 3a447cfd06a4be8a2f5512a48a4804dac0f05582 (diff) | |
| parent | 6f8f74356765e9d6ed331643d97d9402a8a69fa9 (diff) | |
| download | perlweeklychallenge-club-3a95281e37c0cdfa97b2c0af3aa9d32315ab5b71.tar.gz perlweeklychallenge-club-3a95281e37c0cdfa97b2c0af3aa9d32315ab5b71.tar.bz2 perlweeklychallenge-club-3a95281e37c0cdfa97b2c0af3aa9d32315ab5b71.zip | |
Merge pull request #9712 from jeanluc2020/jeanluc-259
Add solution 259.
| -rw-r--r-- | challenge-259/jeanluc2020/blog-1.txt | 1 | ||||
| -rw-r--r-- | challenge-259/jeanluc2020/blog-2.txt | 1 | ||||
| -rwxr-xr-x | challenge-259/jeanluc2020/perl/ch-1.pl | 80 | ||||
| -rwxr-xr-x | challenge-259/jeanluc2020/perl/ch-2.pl | 226 |
4 files changed, 308 insertions, 0 deletions
diff --git a/challenge-259/jeanluc2020/blog-1.txt b/challenge-259/jeanluc2020/blog-1.txt new file mode 100644 index 0000000000..f8e5cb0616 --- /dev/null +++ b/challenge-259/jeanluc2020/blog-1.txt @@ -0,0 +1 @@ +http://gott-gehabt.de/800_wer_wir_sind/thomas/Homepage/Computer/perl/theweeklychallenge-259-1.html diff --git a/challenge-259/jeanluc2020/blog-2.txt b/challenge-259/jeanluc2020/blog-2.txt new file mode 100644 index 0000000000..85df72a6ca --- /dev/null +++ b/challenge-259/jeanluc2020/blog-2.txt @@ -0,0 +1 @@ +http://gott-gehabt.de/800_wer_wir_sind/thomas/Homepage/Computer/perl/theweeklychallenge-259-2.html diff --git a/challenge-259/jeanluc2020/perl/ch-1.pl b/challenge-259/jeanluc2020/perl/ch-1.pl new file mode 100755 index 0000000000..d4544eae1d --- /dev/null +++ b/challenge-259/jeanluc2020/perl/ch-1.pl @@ -0,0 +1,80 @@ +#!/usr/bin/env perl +# https://theweeklychallenge.org/blog/perl-weekly-challenge-259/#TASK1 +# +# Task 1: Banking Day Offset +# ========================== +# +# You are given a start date and offset counter. Optionally you also get bank +# holiday date list. +# +# Given a number (of days) and a start date, return the number (of days) +# adjusted to take into account non-banking days. In other words: convert a +# banking day offset to a calendar day offset. +# +# Non-banking days are: +# +# a) Weekends +# b) Bank holidays +# +## Example 1 +## +## Input: $start_date = '2018-06-28', $offset = 3, $bank_holidays = ['2018-07-03'] +## Output: '2018-07-04' +## +## Thursday bumped to Wednesday (3 day offset, with Monday a bank holiday) +# +## Example 2 +## +## Input: $start_date = '2018-06-28', $offset = 3 +## Output: '2018-07-03' +# +############################################################ +## +## discussion +## +############################################################ +# +# As long as offset is > 0, we add one to the current date. If the +# new date is neither a bank holiday nor on a weekend, we decrease +# the remaining offset by one. +# I use Date::Calc to take care of the date calculation part. + +use strict; +use warnings; +use Date::Calc qw(Day_of_Week Add_Delta_Days); + +banking_day_offset('2018-06-28', 3, ['2018-07-03']); +banking_day_offset('2018-06-28', 3); + +sub banking_day_offset { + my ($start_date, $offset, $bank_holidays) = @_; + $bank_holidays //= []; + print "Input: start_date = $start_date, offset = $offset, bank_holidays = [" . join(", ", @$bank_holidays) . "]\n"; + while($offset > 0) { + my $next_date = next_day($start_date); + if( is_bankholiday_or_weekend($next_date, $bank_holidays) ) { + $start_date = $next_date; + next; + } + $offset--; + $start_date = $next_date; + } + print "Output: $start_date\n"; +} + +sub is_bankholiday_or_weekend { + my ($date, $bank_holidays) = @_; + return 1 if Day_of_Week(split(/-/, $date)) > 5; + foreach my $d (@$bank_holidays) { + return 1 if $d eq $date; + } + return 0; +} + +sub next_day { + my $date = shift; + my ($year, $month, $day) = split /-/, $date; + my @new_date = Add_Delta_Days($year, $month, $day, 1); + my $d = sprintf("%04d-%02d-%02d", @new_date); + return $d; +} diff --git a/challenge-259/jeanluc2020/perl/ch-2.pl b/challenge-259/jeanluc2020/perl/ch-2.pl new file mode 100755 index 0000000000..8a66ffe2c3 --- /dev/null +++ b/challenge-259/jeanluc2020/perl/ch-2.pl @@ -0,0 +1,226 @@ +#!/usr/bin/env perl +# https://theweeklychallenge.org/blog/perl-weekly-challenge-259/#TASK2 +# +# Task 2: Line Parser +# =================== +# +# You are given a line like below: +# +# {% id field1="value1" field2="value2" field3=42 %} +# +# Where +# +# a) "id" can be \w+. +# b) There can be 0 or more field-value pairs. +# c) The name of the fields are \w+. +# b) The values are either number in which case we don't need double quotes or +# string in which case we need double quotes around them. +# +# The line parser should return structure like below: +# +# { +# name => id, +# fields => { +# field1 => value1, +# field2 => value2, +# field3 => value3, +# } +# } +# +# It should be able to parse the following edge cases too: +# +# {% youtube title="Title \"quoted\" done" %} +# +# and +# +# {% youtube title="Title with escaped backslash \\" %} +# +# BONUS: Extend it to be able to handle multiline tags: +# +# {% id filed1="value1" ... %} +# LINES +# {% endid %} +# +# You should expect the following structure from your line parser: +# +# { +# name => id, +# fields => { +# field1 => value1, +# field2 => value2, +# field3 => value3, +# } +# text => LINES +# } +# +############################################################ +## +## discussion +## +############################################################ +# +# Parsers are always a bit complicated, so most solutions to +# this problem will probably be as well. +# There might also always be some corner cases that are not +# clear right away. In this case the description says: +# The values are either number in which case we don't need +# double quotes or string in which case we need double quotes +# around them. +# It remains unclear whether or not this includes floating point +# numbers or only integers, so I decided to at least also support +# decimal number written with a single "." like 12.34, but not +# the full list of possible representations of floats like 1E5 or +# similar stuff. +# A full parser usually needs to split everything into tokens which +# can then be processed one by one, and regular expressions are not +# enough to build a full parser in most cases. In our case, I used +# a mixed approach: handle everything with regular expressions +# that is easy to handle that way. However, parsing strings is done +# by handing in the rest of the current line into a function that +# will then return the string at the beginning of the line and the +# remainder of the input once the string is removed. This function +# parses the rest of the line one character at a time, no regular +# expressions there. The loop that calls this function goes line +# at a time, always picking up larger chunks of the line by using +# regular expressions. This outer loop makes use of the Switch +# module unnecessarily as my first intention was to implement a +# full character-by-character parser which would have required +# some kind of a state machine to always know where in the parsing +# process we are at any time, but then I decided to try an +# approach making use of regular expressions, so a single variable +# could have taken care of everything (Just keep the sectionname +# in a variable, and if empty assume we're in the default state +# and take it from there), but then I wouldn't have had the +# opportunity to try out the Switch module, so I kept the code as +# it was. + +use strict; +use warnings; +use Switch; +use Data::Dumper; + +my $DEFAULT = 'Default'; +my $INSECTION = 'InSection'; +my $SECTIONNAME = ""; + +my @ALL_DATA; # the result will be stored here +my $CURRENT_DATA = {}; # for temporarily storing the data we're currently parsing +my $state = $DEFAULT; # initialize the state machine + +my $input = <<EOF; +{% id field1="value1" field2="value2" field3=42 %} +{% endid %} +Some random data somewhere in between +{% youtube title="Title \\"quoted\\" done" %} +{% endyoutube %} +{% youtube title="Title with escaped backslash \\\\" %} +Some data +More data +{% endyoutube %} +{% id field1="value1" field2="value2" field3=42.32 field4="Hello, world!" %} +{% endid %} +{% foo %} +Bar. +{% endfoo %} +EOF +my @lines = split /\n/, $input; + +foreach my $line (@lines) { + switch($state) { + case "$DEFAULT" { + # not inside a section, so we expect either the begin of a new section + # or a plain string to ingest + if($line =~ m/^\s*\{\%\s*(\w+)/) { # new section starts + # keep name of section and switch to correct state + $SECTIONNAME = $1; + $state = $INSECTION; + # remove start of line including the section name + $line =~ s/\s*\{\%\s*$SECTIONNAME\s+//; + # initialize temporary data structure's name + $CURRENT_DATA->{name} = $SECTIONNAME; + while(length($line) > 0) { # ingest rest of line + # remove unnecessary whitespace + $line =~ s/^\s+//; + if($line =~ m/^\s*\%\}\s*$/) { + # we found the end of the line, let's just set everything + # into a state that will break out of the loop. + $line = ""; + } else { + # now we have a new field at the beginning of the line, so + # we remove (and capture) the field name + $line =~ s/^(\w+)=//; + my $key = $1; + if($line =~ m/^(\d+)(\.\d+){0,1}(\s|\%\})/) { + # number at beginning of line, remove it and + # capture it in the temporary data structure + $line =~ s/^(\d+(\.\d+){0,1})//; + $CURRENT_DATA->{fields}->{$key} = $1; + } elsif ( $line =~ m/^"/ ) { + # string at the beginning of the line, let's hand the + # function into the parser function for this case, then + # store the resulting string into the temporary data + # structure, keeping the remainder in $line for further + # processing in the next iteration of the loop + my $string; + ($string, $line) = parse_next_string($line); + $CURRENT_DATA->{fields}->{$key} = $string; + } else { + die "Parse error, value neither number nor string here ->$line!"; + } + } + } + } else { # plain string to ingest + push @ALL_DATA, $line; + } + } + case "$INSECTION" { + # either we find the end of the current section, or we copy the line as-is + # to the text inside our data structure + if($line =~ m/^\s*\{\%\s*end$SECTIONNAME\s*\%\}\s*$/) { + # end of section: store current temporary data into the result, + # reset the temporary data structure and switch back to the default + # state + push @ALL_DATA, $CURRENT_DATA; + $CURRENT_DATA = {}; + $state = $DEFAULT; + } else { + $CURRENT_DATA->{text} .= "$line\n"; + } + } + else { die "Unknown state $state!\n"; } + } +} +foreach my $entry (@ALL_DATA) { + print Dumper $entry; +} + +# given the rest of a line, split this into the string at the beginning +# of the line and the remainder of the line after removing this string +sub parse_next_string { + my $input = shift; + my @chars = split //, $input; + my ($string, $rest) = ("", ""); + die "Not a string" unless $chars[0] eq '"'; + my $index = 1; + while($index <= $#chars) { + if($chars[$index] eq '"') { # closing '"', finish everything up + $rest = substr($input, $index+1); + return ($string, $rest); + } elsif ($chars[$index] eq "\\") { + # if we find a '\', we just ingest the next character as is. + # This means '\\' turns into a single '\', '\"' turns into + # a single '"', and other stuff like '\a' turns into 'a'. + # No need to make things more complicated in this case. + $index++; + $string .= $chars[$index]; + } else { + # everything apart from '\' or '"' just indicates a character + # to be ingested as-is. + $string .= $chars[$index]; + } + $index++; + } + # if we end up here, there was no matching '"' + # so we ran into a parsing error + die "Parser error: Could not find closing '\"' in $input"; +} |
