diff options
| author | Mohammad Sajid Anwar <Mohammad.Anwar@yahoo.com> | 2024-03-12 11:33:07 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-12 11:33:07 +0000 |
| commit | 14ae65cd517d955713cab891458fd67172e57881 (patch) | |
| tree | 09585c0221ab9400923812de5417586231ce8133 | |
| parent | 131cc2d9c6653506aac96a6e1fd0dc9e12a2c37c (diff) | |
| parent | 0326b1d30f1ae291d80d865ae54e17e7c3e43479 (diff) | |
| download | perlweeklychallenge-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.go | 124 | ||||
| -rw-r--r-- | challenge-259/pokgopun/go/ch-2.go | 154 | ||||
| -rw-r--r-- | challenge-259/pokgopun/python/ch-1.py | 86 | ||||
| -rw-r--r-- | challenge-259/pokgopun/python/ch-2.py | 119 |
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() |
