aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMohammad Sajid Anwar <Mohammad.Anwar@yahoo.com>2024-03-12 11:33:07 +0000
committerGitHub <noreply@github.com>2024-03-12 11:33:07 +0000
commit14ae65cd517d955713cab891458fd67172e57881 (patch)
tree09585c0221ab9400923812de5417586231ce8133
parent131cc2d9c6653506aac96a6e1fd0dc9e12a2c37c (diff)
parent0326b1d30f1ae291d80d865ae54e17e7c3e43479 (diff)
downloadperlweeklychallenge-club-14ae65cd517d955713cab891458fd67172e57881.tar.gz
perlweeklychallenge-club-14ae65cd517d955713cab891458fd67172e57881.tar.bz2
perlweeklychallenge-club-14ae65cd517d955713cab891458fd67172e57881.zip
Merge pull request #9735 from pokgopun/pwc259
pwc259 solution
-rw-r--r--challenge-259/pokgopun/go/ch-1.go124
-rw-r--r--challenge-259/pokgopun/go/ch-2.go154
-rw-r--r--challenge-259/pokgopun/python/ch-1.py86
-rw-r--r--challenge-259/pokgopun/python/ch-2.py119
4 files changed, 483 insertions, 0 deletions
diff --git a/challenge-259/pokgopun/go/ch-1.go b/challenge-259/pokgopun/go/ch-1.go
new file mode 100644
index 0000000000..c013e96ffa
--- /dev/null
+++ b/challenge-259/pokgopun/go/ch-1.go
@@ -0,0 +1,124 @@
+//# https://theweeklychallenge.org/blog/perl-weekly-challenge-259/
+/*#
+
+Task 1: Banking Day Offset
+
+Submitted by: [42]Lee Johnson
+ __________________________________________________________________
+
+ 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'
+
+Task 2: Line Parser
+#*/
+//# solution by pokgopun@gmail.com
+
+package main
+
+import (
+ "io"
+ "log"
+ "os"
+ "slices"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+const datePttrn = "2006-01-02"
+
+// logic is similar to python version
+func bankingDayOffset(startDate string, offset uint, bankHolidays []string) string {
+ dstart, err := time.Parse(datePttrn, startDate)
+ if err != nil {
+ log.Fatal("invalid date string")
+ }
+ switch dstart.Weekday() { // skip back to Friday if start at weekend
+ case time.Saturday:
+ dstart = dstart.AddDate(0, 0, -1)
+ case time.Sunday:
+ dstart = dstart.AddDate(0, 0, -2)
+ }
+ days := 7*(int(offset)/5) + int(offset)%5 // convert workday offset to everyday offset => 7 days * weekCount (i.e. int(offset)/5) + partial week offset
+ if int(dstart.Weekday())+int(offset)%5 > 5 { // skip weekend if partial week offset int(offset)%5 move dstart beyond current weekday
+ days += 2
+ }
+ dend := dstart.AddDate(0, 0, days)
+ for _, hol := range newHolidays(bankHolidays) {
+ if dstart.Before(hol) && !dend.Before(hol) { // move forward with holidays one by one
+ dend = dend.AddDate(0, 0, 1)
+ if dend.Weekday() > time.Friday { // skip to next monday if such move hit Saturday
+ dend = dend.AddDate(0, 0, 2)
+ }
+ }
+ }
+ return dend.Format(datePttrn)
+}
+
+type holidays []time.Time
+
+// newHolidays silently filter out invalid and duplicated date strings and weekend, dates will be sorted ascendingly as well
+func newHolidays(dateStrs []string) holidays {
+ var hols holidays
+ m := make(map[time.Time]struct{})
+ slices.Sort(dateStrs)
+ for _, dateStr := range dateStrs {
+ date, err := time.Parse(datePttrn, dateStr)
+ if err == nil {
+ switch date.Weekday() {
+ case time.Saturday, time.Sunday:
+ continue
+ }
+ _, ok := m[date]
+ if !ok {
+ m[date] = struct{}{}
+ hols = append(hols, date)
+ }
+ }
+ }
+ return hols
+}
+
+func main() {
+ for _, data := range []struct {
+ output, start string
+ offset uint
+ holidays []string
+ }{
+ {"2018-07-04", "2018-06-28", 3, []string{"2018-07-03"}},
+ {"2018-07-03", "2018-06-28", 3, []string{}},
+ {"2018-07-05", "2018-06-28", 3, []string{"2018-07-02", "2018-07-03"}},
+ {"2018-07-05", "2018-06-28", 3, []string{"2018-07-06", "2018-07-03", "2018-07-02"}},
+ {"2018-07-06", "2018-06-28", 3, []string{"2018-07-02", "2018-07-03", "2018-07-05"}},
+ {"2018-07-06", "2018-06-28", 3, []string{"2018-07-02", "2018-07-03", "2018-07-05", "2024-03-05"}},
+ {"2018-07-06", "2018-06-28", 3, []string{"2018-07-02", "2018-07-03", "2018-07-05", "1979-07-09"}},
+ {"2018-07-13", "2018-06-28", 8, []string{"2018-07-02", "2018-07-03", "2018-07-05", "1979-07-09"}},
+ {"2018-07-16", "2018-06-28", 9, []string{"2018-07-02", "2018-07-03", "2018-07-05", "1979-07-09"}},
+ {"2018-07-23", "2018-06-28", 14, []string{"2018-07-02", "2018-07-03", "2018-07-05", "1979-07-09"}},
+ {"2018-07-25", "2018-06-28", 14, []string{"2018-07-02", "2018-07-03", "2018-07-05", "2018-07-11", "2018-07-18"}},
+ {"2018-07-05", "2018-06-29", 3, []string{"2018-07-03"}},
+ {"2018-07-05", "2018-06-30", 3, []string{"2018-07-03"}},
+ } {
+ io.WriteString(os.Stdout, cmp.Diff(bankingDayOffset(data.start, data.offset, data.holidays), data.output)) // blank if ok, otherwise show the differences
+ }
+}
diff --git a/challenge-259/pokgopun/go/ch-2.go b/challenge-259/pokgopun/go/ch-2.go
new file mode 100644
index 0000000000..186a72442b
--- /dev/null
+++ b/challenge-259/pokgopun/go/ch-2.go
@@ -0,0 +1,154 @@
+//# https://theweeklychallenge.org/blog/perl-weekly-challenge-259/
+/*#
+
+Task 2: Line Parser
+
+Submitted by: [43]Gabor Szabo
+ __________________________________________________________________
+
+ 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 parentheses or strin
+g in
+ which case we need parentheses 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
+}
+ __________________________________________________________________
+
+ Last date to submit the solution 23:59 (UK Time) Sunday 10th March
+ 2024.
+ __________________________________________________________________
+
+SO WHAT DO YOU THINK ?
+#*/
+//# solution by pokgopun@gmail.com
+
+package main
+
+import (
+ "fmt"
+ "reflect"
+ "regexp"
+ "strconv"
+)
+
+type kvpair map[string]string
+
+type parsed struct {
+ name, text string
+ fields kvpair
+}
+
+type parser struct {
+ m []string
+ rgxLine, rgxKV *regexp.Regexp
+}
+
+func newParser() parser {
+ return parser{
+ rgxLine: regexp.MustCompile(`{%\s(?P<name>\w+)(?P<kv>(?:\s\w+=(?:\d+|"(?:\\(?:"|\\)|[^"\\])+?"))+)?\s%}(?:\n(?P<tag>[\d\D]+?)\n\{%\sendid\s%})?`),
+ rgxKV: regexp.MustCompile(`\s(?P<key>\w+)=(?P<value>\d+|"(?:\\(?:"|\\)|[^"\\])+?")`),
+ }
+}
+
+func (ps parser) parse(msg string) parsed {
+ ps.m = ps.rgxLine.FindStringSubmatch(msg)
+ p := parsed{name: ps.m[1], text: ps.m[3], fields: make(kvpair)}
+ if ps.m[2] != "" {
+ for _, kv := range ps.rgxKV.FindAllStringSubmatch(ps.m[2], -1) {
+ _, err := strconv.Atoi(kv[2])
+ if err != nil {
+ kv[2], _ = strconv.Unquote(kv[2])
+ }
+ p.fields[kv[1]] = kv[2]
+ }
+ }
+ return p
+}
+
+func main() {
+ p := newParser()
+ for _, data := range []struct {
+ input string
+ output parsed
+ }{
+ {
+ `{% youtube id=1234 title="Title \"quoted\" done" %}`,
+ parsed{
+ name: "youtube",
+ fields: kvpair{
+ "id": "1234",
+ "title": `Title "quoted" done`,
+ },
+ },
+ },
+ {
+ `{% youtube title="Title with escaped backslash \\" id=4321 %}`,
+ parsed{
+ name: "youtube",
+ fields: kvpair{
+ "id": "4321",
+ "title": `Title with escaped backslash \`,
+ },
+ },
+ },
+ {
+ `{% id %}`,
+ parsed{name: "id", fields: kvpair{}},
+ },
+ {
+ `{% youtube id=12345 title="mejoo and cats" cats="\"monji\" \\ \"bongji\" \\ \"hyuji\" \\ \"yoji\" \\ \"audrey\"" %}
+soo
+mejoo
+{% endid %}`,
+ parsed{
+ name: "youtube",
+ fields: kvpair{
+ "id": "12345",
+ "cats": `"monji" \ "bongji" \ "hyuji" \ "yoji" \ "audrey"`,
+ "title": "mejoo and cats",
+ },
+ text: `soo
+mejoo`,
+ },
+ },
+ } {
+ //fmt.Println(p.parse(data.input))
+ fmt.Println(reflect.DeepEqual(p.parse(data.input), data.output))
+ }
+}
diff --git a/challenge-259/pokgopun/python/ch-1.py b/challenge-259/pokgopun/python/ch-1.py
new file mode 100644
index 0000000000..ab2c06878e
--- /dev/null
+++ b/challenge-259/pokgopun/python/ch-1.py
@@ -0,0 +1,86 @@
+### https://theweeklychallenge.org/blog/perl-weekly-challenge-259/
+"""
+
+Task 1: Banking Day Offset
+
+Submitted by: [42]Lee Johnson
+ __________________________________________________________________
+
+ 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'
+
+Task 2: Line Parser
+"""
+### solution by pokgopun@gmail.com
+
+from datetime import date,datetime,timedelta
+
+def str2date(datestr: str): ### convert date string to date object
+ return datetime.strptime(datestr,"%Y-%m-%d").date()
+
+def isWeekday(d: date): ### check if date object is a weekday, that is less than Saturday (i.e. 5 in python)
+ return d.weekday() < 5
+
+def BDO(start: str, ofst: int, hols: tuple = ()):
+ dstart = str2date(start)
+ if isWeekday(dstart) == False: ### if startdate is a weekend day, move back to last friday
+ dstart -= timedelta(days = dstart.weekday() - 4) ### move back number of day from friday
+
+ ### convert weekday offset (ofst) to everyday offset (dur), that is 7 * number of week (i.e. ofset // 5) and the remaing partial-weekday offset (ofst % 5)
+ dur = timedelta(days = 7*(ofst // 5) + ofst % 5)
+ ### now we need to account for partial-weekday offset in the everyday offset
+ if dstart.weekday() + ofst % 5 > 4: ### if partial-weekday offset move startdate over the current weekday, add weekend offset to everyday offset
+ dur += timedelta(days=2)
+ dend = dstart + dur ### calculate enddate
+
+ ### now account for holidays one by one, need to start from the earliest holiday first so we sort holidays before processing
+ for hol in sorted(set(hols)):
+ dhol = str2date(hol)
+ if dhol > dstart and dhol <= dend and isWeekday(dhol): ### only offset holiday if it is a weekday and is later than startdate but not later than current enddate
+ dend += timedelta(days=1)
+ if isWeekday(dend) == False: ### if a holiday offset move enddate to a weekend day, add weekend offset
+ dend += timedelta(days=2) ### this is ok as we account for holiday offset one by one
+ return dend.strftime("%Y-%m-%d")
+
+import unittest
+
+class TestBDO(unittest.TestCase):
+ def test(self):
+ for output, (start_date, offset, bank_holidays) in {
+ "2018-07-04": ("2018-06-28", 3, ("2018-07-03",)),
+ "2018-07-03": ("2018-06-28", 3, ()),
+ "2018-07-05": ("2018-06-28", 3, ("2018-07-02", "2018-07-03")),
+ "2018-07-05": ("2018-06-28", 3, ("2018-07-06", "2018-07-03", "2018-07-02")),
+ "2018-07-06": ("2018-06-28", 3, ("2018-07-02", "2018-07-03", "2018-07-05")),
+ "2018-07-06": ("2018-06-28", 3, ("2018-07-02", "2018-07-03", "2018-07-05","2024-03-05")),
+ "2018-07-06": ("2018-06-28", 3, ("2018-07-02", "2018-07-03", "2018-07-05","1979-07-09")),
+ "2018-07-13": ("2018-06-28", 8, ("2018-07-02", "2018-07-03", "2018-07-05","1979-07-09")),
+ "2018-07-16": ("2018-06-28", 9, ("2018-07-02", "2018-07-03", "2018-07-05","1979-07-09")),
+ "2018-07-23": ("2018-06-28", 14, ("2018-07-02", "2018-07-03", "2018-07-05","1979-07-09")),
+ "2018-07-25": ("2018-06-28", 14, ("2018-07-02", "2018-07-03", "2018-07-05","2018-07-11","2018-07-18")),
+ "2018-07-05": ("2018-06-29", 3, ("2018-07-03",)),
+ "2018-07-05": ("2018-06-30", 3, ("2018-07-03",)),
+ }.items():
+ self.assertEqual(output, BDO(start_date, offset, bank_holidays))
+
+unittest.main()
diff --git a/challenge-259/pokgopun/python/ch-2.py b/challenge-259/pokgopun/python/ch-2.py
new file mode 100644
index 0000000000..ac56114e24
--- /dev/null
+++ b/challenge-259/pokgopun/python/ch-2.py
@@ -0,0 +1,119 @@
+### https://theweeklychallenge.org/blog/perl-weekly-challenge-259/
+"""
+
+Task 2: Line Parser
+
+Submitted by: [43]Gabor Szabo
+ __________________________________________________________________
+
+ 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 parentheses or strin
+g in
+ which case we need parentheses 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
+}
+ __________________________________________________________________
+
+ Last date to submit the solution 23:59 (UK Time) Sunday 10th March
+ 2024.
+ __________________________________________________________________
+
+SO WHAT DO YOU THINK ?
+"""
+### solution by pokgopun@gmail.com
+
+import re
+
+rgxLine = re.compile(r'{%\s(?P<name>\w+)(?P<kv>(?:\s\w+=(?:\d+|"(?:\\(?:"|\\)|[^"\\])+?"))+)?\s%}(?:\n(?P<tag>[\d\D]+?)\n\{%\sendid\s%})?')
+rgxKV = re.compile(r'\s(?P<key>\w+)=(?P<value>\d+|"(?:\\(?:"|\\)|[^"\\])+?")')
+
+def lineParser(msg: str):
+ res = rgxLine.match(msg)
+ parsed = { "name": res.group("name") }
+ resKV = res.group("kv")
+ if resKV is not None:
+ parsed["fields"] = {}
+ for resKV in rgxKV.finditer(res.group("kv")):
+ k = resKV.group("key")
+ v = resKV.group("value")
+ if v.isnumeric():
+ v = int(v)
+ else:
+ v = v[1:-1].encode("utf-8").decode('unicode_escape')
+ parsed["fields"][k] = v
+ tag = res.group("tag")
+ if tag is not None:
+ parsed["text"] = tag
+ return parsed
+
+import unittest
+
+class TestLineParser(unittest.TestCase):
+ def test(self):
+ for inpt, otpt in {
+ r'{% youtube id=1234 title="Title \"quoted\" done" %}': {
+ 'name': 'youtube',
+ 'fields': {
+ 'id': 1234,
+ 'title': 'Title "quoted" done',
+ },
+ },
+ r'{% youtube title="Title with escaped backslash \\" id=4321 %}': {
+ 'name': 'youtube',
+ 'fields': {
+ 'id': 4321,
+ 'title': 'Title with escaped backslash \\',
+ },
+ },
+ r'''{% id field1="value1" field2=1324 %}
+LINE1
+LINE2
+{% endid %}''': {
+ 'name': 'id',
+ 'fields': {
+ 'field2': 1324,
+ 'field1': 'value1',
+ },
+ 'text': '''LINE1
+LINE2''',
+},
+}.items():
+ self.assertEqual(lineParser(inpt),otpt)
+
+unittest.main()