From c45f2d8a7ae3836921747c832d2bfe9db7cc9aa9 Mon Sep 17 00:00:00 2001 From: Archargelod Date: Fri, 8 Mar 2024 19:02:07 +0800 Subject: weeks 27-40, 259 in Nim --- challenge-027/archargelod/README | 1 + challenge-027/archargelod/nim/ch_1.nim | 40 ++++++++++++ challenge-027/archargelod/nim/ch_2.nim | 59 ++++++++++++++++++ challenge-028/archargelod/README | 1 + challenge-028/archargelod/nim/ch_1.nim | 17 +++++ challenge-028/archargelod/nim/ch_2.nim | 23 +++++++ challenge-029/archargelod/README | 1 + challenge-029/archargelod/nim/ch_1.nim | 26 ++++++++ challenge-029/archargelod/nim/ch_2.nim | 38 ++++++++++++ challenge-030/archargelod/README | 1 + challenge-030/archargelod/nim/ch_1.nim | 11 ++++ challenge-030/archargelod/nim/ch_2.nim | 44 +++++++++++++ challenge-031/archargelod/README | 1 + challenge-031/archargelod/nim/ch_1.nim | 17 +++++ challenge-032/archargelod/README | 1 + challenge-032/archargelod/nim/ch_1.nim | 37 +++++++++++ challenge-032/archargelod/nim/ch_2.nim | 23 +++++++ challenge-033/archargelod/README | 1 + challenge-033/archargelod/nim/ch_1.nim | 20 ++++++ challenge-033/archargelod/nim/ch_2.nim | 22 +++++++ challenge-034/archargelod/README | 1 + challenge-034/archargelod/nim/ch_1.nim | 20 ++++++ challenge-034/archargelod/nim/ch_2.nim | 24 +++++++ challenge-035/archargelod/README | 1 + challenge-035/archargelod/nim/ch_1.nim | 110 +++++++++++++++++++++++++++++++++ challenge-035/archargelod/nim/ch_2.nim | 109 ++++++++++++++++++++++++++++++++ challenge-036/archargelod/README | 1 + challenge-036/archargelod/nim/ch_1.nim | 47 ++++++++++++++ challenge-036/archargelod/nim/ch_2.nim | 56 +++++++++++++++++ challenge-037/archargelod/README | 1 + challenge-037/archargelod/nim/ch_1.nim | 37 +++++++++++ challenge-037/archargelod/nim/ch_2.nim | 84 +++++++++++++++++++++++++ challenge-038/archargelod/README | 1 + challenge-038/archargelod/nim/ch_1.nim | 47 ++++++++++++++ challenge-038/archargelod/nim/ch_2.nim | 86 ++++++++++++++++++++++++++ challenge-039/archargelod/README | 1 + challenge-039/archargelod/nim/ch_1.nim | 47 ++++++++++++++ challenge-039/archargelod/nim/ch_2.nim | 54 ++++++++++++++++ challenge-040/archargelod/README | 1 + challenge-040/archargelod/nim/ch_1.nim | 33 ++++++++++ challenge-040/archargelod/nim/ch_2.nim | 26 ++++++++ challenge-259/archargelod/nim/ch_1.nim | 32 ++++++++++ challenge-259/archargelod/nim/ch_2.nim | 96 ++++++++++++++++++++++++++++ 43 files changed, 1299 insertions(+) create mode 100644 challenge-027/archargelod/README create mode 100755 challenge-027/archargelod/nim/ch_1.nim create mode 100755 challenge-027/archargelod/nim/ch_2.nim create mode 100644 challenge-028/archargelod/README create mode 100755 challenge-028/archargelod/nim/ch_1.nim create mode 100755 challenge-028/archargelod/nim/ch_2.nim create mode 100644 challenge-029/archargelod/README create mode 100755 challenge-029/archargelod/nim/ch_1.nim create mode 100755 challenge-029/archargelod/nim/ch_2.nim create mode 100644 challenge-030/archargelod/README create mode 100755 challenge-030/archargelod/nim/ch_1.nim create mode 100755 challenge-030/archargelod/nim/ch_2.nim create mode 100644 challenge-031/archargelod/README create mode 100755 challenge-031/archargelod/nim/ch_1.nim create mode 100644 challenge-032/archargelod/README create mode 100755 challenge-032/archargelod/nim/ch_1.nim create mode 100755 challenge-032/archargelod/nim/ch_2.nim create mode 100644 challenge-033/archargelod/README create mode 100755 challenge-033/archargelod/nim/ch_1.nim create mode 100755 challenge-033/archargelod/nim/ch_2.nim create mode 100644 challenge-034/archargelod/README create mode 100755 challenge-034/archargelod/nim/ch_1.nim create mode 100755 challenge-034/archargelod/nim/ch_2.nim create mode 100644 challenge-035/archargelod/README create mode 100755 challenge-035/archargelod/nim/ch_1.nim create mode 100755 challenge-035/archargelod/nim/ch_2.nim create mode 100644 challenge-036/archargelod/README create mode 100755 challenge-036/archargelod/nim/ch_1.nim create mode 100755 challenge-036/archargelod/nim/ch_2.nim create mode 100644 challenge-037/archargelod/README create mode 100755 challenge-037/archargelod/nim/ch_1.nim create mode 100755 challenge-037/archargelod/nim/ch_2.nim create mode 100644 challenge-038/archargelod/README create mode 100755 challenge-038/archargelod/nim/ch_1.nim create mode 100755 challenge-038/archargelod/nim/ch_2.nim create mode 100644 challenge-039/archargelod/README create mode 100755 challenge-039/archargelod/nim/ch_1.nim create mode 100755 challenge-039/archargelod/nim/ch_2.nim create mode 100644 challenge-040/archargelod/README create mode 100755 challenge-040/archargelod/nim/ch_1.nim create mode 100755 challenge-040/archargelod/nim/ch_2.nim create mode 100755 challenge-259/archargelod/nim/ch_1.nim create mode 100755 challenge-259/archargelod/nim/ch_2.nim diff --git a/challenge-027/archargelod/README b/challenge-027/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-027/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-027/archargelod/nim/ch_1.nim b/challenge-027/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..15e65d5812 --- /dev/null +++ b/challenge-027/archargelod/nim/ch_1.nim @@ -0,0 +1,40 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/options + +type + Point = tuple[x, y: float] + Line = (Point, Point) + +func getIntersection(l1, l2: Line): Option[Point] = + ## can't handle co-linear lines + let + ix: Line = + ((l1[1].x - l1[0].x, l1[1].y - l1[0].y), (l2[1].x - l2[0].x, l2[1].y - l2[0].y)) + s: float = + (-ix[0].y * (l1[0].x - l2[0].x) + ix[0].x * (l1[0].y - l2[0].y)) / + (-ix[1].x * ix[0].y + ix[0].x * ix[1].y) + t: float = + (ix[1].x * (l1[0].y - l2[0].y) - ix[1].y * (l1[0].x - l2[0].x)) / + (-ix[1].x * ix[0].y + ix[0].x * ix[1].y) + + if s >= 0 and s <= 1 and t >= 0 and t <= 1: + # Collision detected + return some((l1[0].x + (t * ix[0].x), l1[0].y + (t * ix[0].y))) + + none(Point) + +when isMainModule: + import std/unittest + + const + TestLines = [ + ((0.0, 0.0), (10.0, 10.0)), ((10.0, 0.0), (0.0, 10.0)), + ((0.0, 0.0), (10.0, 10.0)), ((10.0, 0.0), (6.0, 4.0)), + ] + Expected = [some(Point (5.0, 5.0)), none(Point)] + + suite "Intersection of two lines": + test "intersection detected": + check getIntersection(TestLines[0], TestLines[1]) == Expected[0] + test "no intersection": + check getIntersection(TestLines[2], TestLines[3]) == Expected[1] diff --git a/challenge-027/archargelod/nim/ch_2.nim b/challenge-027/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..2cd2eec204 --- /dev/null +++ b/challenge-027/archargelod/nim/ch_2.nim @@ -0,0 +1,59 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +type Hist*[T] = object + # object fields without `*` are private and can't be accesed + h_val: T + h_history: seq[T] + +func initHist*[T](value: T): Hist[T] = + Hist[T](h_val: value) + +func `history`*[T](obj: Hist[T]): seq[T] = + ## getter for previous values of a historical type + obj.h_history + +func `val`*[T](obj: Hist[T]): T = + ## getter for current value of a historical type + obj.h_val + +func `val=`*[T](obj: var Hist[T], value: T) = + ## setter for value of a historical type + if obj.h_val != value: + obj.h_history.add obj.h_val + obj.h_val = value + +func `clearHistory`*[T](obj: var Hist[T]) = + ## clear previous values of a historical type + obj.h_history.setLen(0) + +func `$`*[T](obj: Hist[T]): string = + $obj.h_val + +when isMainModule: + import std/unittest + + suite "Historical values": + test "basic example": + var x = initHist[int](10) + x.val = 20 + x.val = x.val - 5 + + check x.val == 15 + check x.history == [10, 20] + + test "history doesn't change if value didn't change": + var x = initHist[int](69) + x.val = 69 + x.val = 420 + x.val = 420 + + check x.val == 420 + check x.history == [69] + + test "clearHistory clears the history": + var x = initHist[int](42) + x.val = x.val + 1 + x.clearHistory() + x.val = 133 + + check x.val == 133 + check x.history == [43] diff --git a/challenge-028/archargelod/README b/challenge-028/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-028/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-028/archargelod/nim/ch_1.nim b/challenge-028/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..d2d316087d --- /dev/null +++ b/challenge-028/archargelod/nim/ch_1.nim @@ -0,0 +1,17 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[mimetypes, os] + +proc contentsVerbose(filename: string): string = + if not fileExists(filename): + raise newException(IOError, "'" & filename & "' does not exist.") + + let + db = newMimetypes() + (_, name, ext) = filename.splitFile() + # couldn't think of other way to check contents without reading the file ¯\_(0_0)_/¯ + mimetype = db.getMimetype(ext, "data") + + "The file '" & name & ext & "' is of type '" & mimetype & "'." + +when isMainModule: + echo contentsVerbose("/usr/share/mime/image/gif.xml") diff --git a/challenge-028/archargelod/nim/ch_2.nim b/challenge-028/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..fede825f37 --- /dev/null +++ b/challenge-028/archargelod/nim/ch_2.nim @@ -0,0 +1,23 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[times, strutils] + +const + Dg_On = "•" + Dg_Off = "·" + +proc displayBinaryClock*() = + var columns: array[6, array[4, bool]] + + for colId, decDigit in format(now(), "HHmmss"): + for bitId, bit in (decDigit.ord - '0'.ord).toBin(4): + if bit == '1': + columns[colId][bitId] = true + + for y in columns[0].low .. columns[0].high: + for x in columns.low .. columns.high: + stdout.write if columns[x][y]: Dg_On else: Dg_Off + stdout.write '\n' + stdout.flushFile() + +when isMainModule: + displayBinaryClock() diff --git a/challenge-029/archargelod/README b/challenge-029/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-029/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-029/archargelod/nim/ch_1.nim b/challenge-029/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..6a21d7795b --- /dev/null +++ b/challenge-029/archargelod/nim/ch_1.nim @@ -0,0 +1,26 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[parseutils, strutils] + +proc expandBraces(input: string): string = + let start = input.skipUntil('{') + let finish = start + input.skipUntil('}', start) + + let (before, after) = (input[0.. n[2]: + inc n[2] + dec n[1] + let newSeries = n.sorted() + if newSeries notin result: + result.add newSeries + +when isMainModule: + import std/unittest + + const + Expected = [ + [1, 2, 9], + [2, 2, 8], + [2, 3, 7], + [2, 4, 6], + [2, 5, 5], + [1, 4, 7], + [3, 4, 5], + [4, 4, 4], + [1, 5, 6], + [3, 3, 6], + [1, 3, 8], + [1, 1, 10], + ] + + suite "Sums of 3 numbers equal to 12": + test "12 possible series": + check possibleSeriesOf3(12) == Expected diff --git a/challenge-031/archargelod/README b/challenge-031/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-031/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-031/archargelod/nim/ch_1.nim b/challenge-031/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..44186b3e54 --- /dev/null +++ b/challenge-031/archargelod/nim/ch_1.nim @@ -0,0 +1,17 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off + +proc isDivisionByZero(num, denom: int): bool = + num / denom == Inf + +when isMainModule: + import std/unittest + + const + Test = [(15, 0), (15, 5)] + Expected = [true, false] + + suite "Division by zero": + test "Example 1": + check isDivisionByZero(Test[0][0], Test[0][1]) == Expected[0] + test "Example 2": + check isDivisionByZero(Test[1][0], Test[1][1]) == Expected[1] diff --git a/challenge-032/archargelod/README b/challenge-032/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-032/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-032/archargelod/nim/ch_1.nim b/challenge-032/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..70d030f3e2 --- /dev/null +++ b/challenge-032/archargelod/nim/ch_1.nim @@ -0,0 +1,37 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[parseutils, tables] + +proc countUniqLines(input: string): CountTable[string] = + var index = 0 + while index < input.len: + let lineLength = input.skipUntil('\n', index) + let line = input[index ..< index+lineLength] + result.inc(line) + + index += lineLength + 1 + +when isMainModule: + import std/unittest + + const + Expected = {"apple": 3, "banana": 1, "cherry": 2} + Test = """ +apple +banana +apple +cherry +cherry +apple""" + + template checkTable(table, tuples: typed) = + for (val, cnt) in tuples: + check table[val] == cnt + + when defined(csv): + for val, cnt in countUniqLines(Test): + echo val, ',', cnt + else: + suite "Count instances": + test "Example 1": + checkTable(countUniqLines(Test), Expected) + diff --git a/challenge-032/archargelod/nim/ch_2.nim b/challenge-032/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..ecdc9c099b --- /dev/null +++ b/challenge-032/archargelod/nim/ch_2.nim @@ -0,0 +1,23 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[strutils] + +proc barGraph(valuePairs: openarray[(string, int)], magnitude = 1): string = + const minOffset = 8 + for (label, value) in valuePairs: + let offset = ' '.repeat(max(0, minOffset - label.len)) + result &= label & offset & " | " & '#'.repeat(value * magnitude) & '\n' + +when isMainModule: + import std/unittest + + const + Test = {"apple": 3, "banana": 1, "cherry": 2} + Expected = """ +apple | ############ +banana | #### +cherry | ######## +""" + + suite "ASCII bar chart": + test "Example 1": + check barGraph(Test, 4) == Expected diff --git a/challenge-033/archargelod/README b/challenge-033/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-033/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-033/archargelod/nim/ch_1.nim b/challenge-033/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..211e001742 --- /dev/null +++ b/challenge-033/archargelod/nim/ch_1.nim @@ -0,0 +1,20 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[strutils, tables, streams] + +proc countLetters(filename: string): CountTable[char] = + let f = newFileStream(filename) + defer: + f.close() + while not f.atEnd(): + let c = f.readChar() + if c in Letters: + result.inc(c) + +proc main = + var counts = countLetters("/usr/share/dict/words") + counts.sort() + for c, cnt in counts: + echo c, ": ", cnt + +when isMainModule: + main() diff --git a/challenge-033/archargelod/nim/ch_2.nim b/challenge-033/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..f93e0e00d3 --- /dev/null +++ b/challenge-033/archargelod/nim/ch_2.nim @@ -0,0 +1,22 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[strutils, strformat] + +proc printMultiplicationTable(max: Positive) = + # header + stdout.write " x|" + for i in 1..max: stdout.write &"{i:4}" + stdout.write '\n' + echo "---+", '-'.repeat(max*4) + # body + for i in 1..max: + stdout.write &"{i:3}|" + for j in 1..max: + if j < i: + stdout.write ' '.repeat(4) + else: + stdout.write &"{i*j:4}" + + stdout.write '\n' + +when isMainModule: + printMultiplicationTable(11) diff --git a/challenge-034/archargelod/README b/challenge-034/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-034/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-034/archargelod/nim/ch_1.nim b/challenge-034/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..4598f9e0ee --- /dev/null +++ b/challenge-034/archargelod/nim/ch_1.nim @@ -0,0 +1,20 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off + +when isMainModule: + import std/unittest + + const + Test = "Write a program that demonstrates using hash slices and/or array slices." + Expected = "Here's a program that demonstrates using the array slices." + + suite "Hash and/or array slices": + test "Example 1": + var res = Test + res[0..4] = "Here's" + res[41..58] = "the" + check res == Expected + test "Example 2": + let res = "Here's" & Test[5..39] & "the" & Test[58..^1] + check res == Expected + + diff --git a/challenge-034/archargelod/nim/ch_2.nim b/challenge-034/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..7ce993f04e --- /dev/null +++ b/challenge-034/archargelod/nim/ch_2.nim @@ -0,0 +1,24 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/tables + +var Procedures = { + "-h": func(): int = 0, + "-g": func(): int = 1, +}.toTable + +proc dispatch(s: string): int = + Procedures[s]() + +when isMainModule: + import std/[unittest, random] + + const + Test = ["-h", "-g"] + Expected = [1, 0] + + suite "Dispatch table": + test "Execute function from table at runtime": + check dispatch(Test[rand(1)]) == Expected[0] + test "Execute different function from table at runtime": + check dispatch(Test[rand(1)]) == Expected[1] + diff --git a/challenge-035/archargelod/README b/challenge-035/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-035/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-035/archargelod/nim/ch_1.nim b/challenge-035/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..660488a451 --- /dev/null +++ b/challenge-035/archargelod/nim/ch_1.nim @@ -0,0 +1,110 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[strutils, tables] + +const + MorseDot = "1" + MorseDash = "111" + MorseGap = "0" + MorseCharGap = "00" # CharGap is Gap + Extra 2 zeroes = 3 zeroes total + MorseWordGap = "0000" # WordGap is Gap + CharGap + Extra 4 zeroes = 7 zeroes total + + CharToMorse = { + '0': "-----", + '1': ".----", + '2': "..---", + '3': "...--", + '4': "....-", + '5': ".....", + '6': "-....", + '7': "--...", + '8': "---..", + '9': "----.", + 'a': ".-", + 'b': "-...", + 'c': "-.-.", + 'd': "-..", + 'e': ".", + 'f': "..-.", + 'g': "--.", + 'h': "....", + 'i': "..", + 'j': ".---", + 'k': "-.-", + 'l': ".-..", + 'm': "--", + 'n': "-.", + 'o': "---", + 'p': ".--.", + 'q': "--.-", + 'r': ".-.", + 's': "...", + 't': "-", + 'u': "..-", + 'v': "...-", + 'w': ".--", + 'x': "-..-", + 'y': "-.--", + 'z': "--..", + '.': ".-.-.-", + ',': "--..--", + '?': "..--..", + '!': "-.-.--", + '-': "-....-", + '/': "-..-.", + '@': ".--.-.", + '(': "-.--.", + ')': "-.--.-", + }.toTable() + +proc normalizeWhitespace(input: string): string = + var lastCharIsSpace = false + for c in input: + if c in Whitespace: + if lastCharIsSpace: + continue + else: + result.add c + lastCharIsSpace = true + else: + result.add c + lastCharIsSpace = false + +proc toBinaryMorse*(input: string): seq[byte] = + var binString = "" + let input = input.strip().normalizeWhitespace() + for c in input: + if c == ' ': + binString.add MorseWordGap + continue + + let c = c.toLowerAscii() + if c in CharToMorse: + let morse = CharToMorse[c] + for c in morse: + case c + of '.': binString.add MorseDot + of '-': binString.add MorseDash + else: raiseAssert("Invalid morse character:" & c) + binString.add MorseGap + binString.add MorseCharGap + + binString.setLen(binString.len - MorseGap.len - MorseCharGap.len) + + for i in countUp(0, binString.high, 8): + var section = binString[i..min(i+7, binString.high)] + if section.len < 8: + section &= '0'.repeat(8 - section.len) + result.add cast[byte](parseBinInt(section)) + +when isMainModule: + import std/[unittest] + + const + Test = " Some test sTriNg " + Expected = [168'u8, 238, 227, 184, 128, 226, 42, 56, 10, 142, 46, 138, 58, 59, 160] + + suite "Encode binary morse code": + test "Encode simple message": + check toBinaryMorse(Test) == Expected + test "Save message as file for part 2": + writeFile("message", toBinaryMorse(Test)) diff --git a/challenge-035/archargelod/nim/ch_2.nim b/challenge-035/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..a09460359f --- /dev/null +++ b/challenge-035/archargelod/nim/ch_2.nim @@ -0,0 +1,109 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[strutils, tables, parseutils, streams] + +const + MorseToChar = { + "-----" : '0', + ".----" : '1', + "..---" : '2', + "...--" : '3', + "....-" : '4', + "....." : '5', + "-...." : '6', + "--..." : '7', + "---.." : '8', + "----." : '9', + ".-" : 'a', + "-..." : 'b', + "-.-." : 'c', + "-.." : 'd', + "." : 'e', + "..-." : 'f', + "--." : 'g', + "...." : 'h', + ".." : 'i', + ".---" : 'j', + "-.-" : 'k', + ".-.." : 'l', + "--" : 'm', + "-." : 'n', + "---" : 'o', + ".--." : 'p', + "--.-" : 'q', + ".-." : 'r', + "..." : 's', + "-" : 't', + "..-" : 'u', + "...-" : 'v', + ".--" : 'w', + "-..-" : 'x', + "-.--" : 'y', + "--.." : 'z', + ".-.-.-": '.', + "--..--": ',', + "..--..": '?', + "-.-.--": '!', + "-....-": '-', + "-..-." : '/', + ".--.-.": '@', + "-.--." : '(', + "-.--.-": ')', + }.toTable() + +proc parseBinaryMorse(data: openarray[byte]): string = + var binString = "" + for b in data: + binString &= cast[int](b).toBin(8) + binString = binString.strip(chars = {'0'}) # might be unnecessary + + var morseWord = "" + + var ind = 0 + while ind < binString.len: + let tokenLength = binString.skipUntil('0', ind) + case tokenLength + of 1: + morseWord.add '.' + of 3: + morseWord.add '-' + else: + raise newException(ValueError, "Incorrectly formatted binary data") + + let gapLength = binString.skipUntil('1', ind + tokenLength) + case gapLength + of 0: # last character + result.add MorseToChar[morseWord] + of 1: discard + of 3: + result.add MorseToChar[morseWord] + morseWord.setLen(0) + of 7: + result.add MorseToChar[morseWord] + result.add ' ' + morseWord.setLen(0) + else: + raise newException(ValueError, "Incorrectly formatted binary data") + + ind += tokenLength + gapLength + +proc readBinaryMorse(filename: string): string = + var buffer: seq[byte] + var f = newFileStream("message") + while not f.atEnd: + buffer.add f.readUint8() + f.close + parseBinaryMorse(buffer) + +when isMainModule: + import std/[unittest] + + const + Test = [168'u8, 238, 227, 184, 128, 226, 42, 56, 10, 142, 46, 138, 58, 59, 160] + Expected = "some test string" + + suite "Decode binary morse code": + test "Decode a simple message": + check parseBinaryMorse(Test) == Expected + + test "Decode message from part 1": + check readBinaryMorse("message") == Expected diff --git a/challenge-036/archargelod/README b/challenge-036/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-036/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-036/archargelod/nim/ch_1.nim b/challenge-036/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..89db5e0b0f --- /dev/null +++ b/challenge-036/archargelod/nim/ch_1.nim @@ -0,0 +1,47 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[strutils, tables] + +const + Weights = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2] + TranslitTable = { + 'A':1, 'B':2, 'C':3, 'D':4, 'E':5, 'F':6, 'G':7, 'H':8, + 'J':1, 'K':2, 'L':3, 'M':4, 'N':5, 'P':7, 'R':9, + 'S':2, 'T':3, 'U':4, 'V':5, 'W':6, 'X':7, 'Y':8, 'Z':9, + }.toTable() + +proc isValidVIN(input: string): bool = + if input.len != 17: return false + + var values: array[17, int] + for i, c in input: + if i == 8: continue # skip check digit + if c in Letters: + values[i] = TranslitTable[c] + elif c in Digits: + values[i] = c.ord - '0'.ord + else: + return false + + var productSum: int + for i, v in values: + productSum += v * Weights[i] + + let checkDigit = block: + let tmp = productSum mod 11 + if tmp == 10: 'X' + else: chr('0'.ord + tmp) + + checkDigit == input[8] + +when isMainModule: + import std/unittest + + const + Test = ["5GZCZ43D13S812715", "1M8GDM9ASKP042788"] + Expected = [true, false] + + suite "Vehicle Identification Numbers": + test "valid number": + check isValidVIN(Test[0]) == Expected[0] + test "invalid number": + check isValidVIN(Test[1]) == Expected[1] diff --git a/challenge-036/archargelod/nim/ch_2.nim b/challenge-036/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..3a3c3fdeb2 --- /dev/null +++ b/challenge-036/archargelod/nim/ch_2.nim @@ -0,0 +1,56 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[strutils, sequtils, math] + +type Box = object + label: string + weight, amount: int + +proc `$`*(b: Box): string = + b.label + +proc allSubsets[T](baseSet: openarray[T]): seq[seq[T]] = + for i in 1 ..< 2 ^ baseSet.len: + let pattern = i.toBin(baseSet.len) + result.add @[] + for j, bit in pattern: + if bit == '1': + result[^1].add baseSet[j] + +proc bestCombinations*(boxes: openarray[Box], maxWeight, maxBoxes: int): seq[seq[Box]] = + var bestValue: int + + let subsetsB = boxes.toOpenArray(boxes.len div 2 + 1, boxes.high).allSubsets() + for subsetA in boxes.toOpenArray(0, boxes.len div 2).allSubsets(): + let weightA = subsetA.mapIt(it.weight).sum() + let valueA = subsetA.mapIt(it.amount).sum() + + for subsetB in subsetsB: + let combinedWeight = weightA + subsetB.mapIt(it.weight).sum() + let combinedValue = valueA + subsetB.mapIt(it.amount).sum() + if (subsetA.len + subsetB.len) <= maxBoxes and combinedWeight <= maxWeight: + if combinedValue > bestValue: + bestValue = combinedValue + result = @[subsetA & subsetB] + elif combinedValue == bestValue: + result.add subsetA & subsetB + +when isMainModule: + import std/unittest + + const + Boxes = [ + Box(label: "R", weight: 1, amount: 1), + Box(label: "B", weight: 1, amount: 2), + Box(label: "G", weight: 2, amount: 2), + Box(label: "Y", weight: 12, amount: 4), + Box(label: "P", weight: 4, amount: 10), + ] + Expected = ["@[@[R, B, G, P]]", "@[@[B, G, P]]", "@[@[G, P], @[B, P]]"] + + suite "Knapsack Problem 0-1": + test "5 or less boxes": + check $bestCombinations(Boxes, 15, 5) == Expected[0] + test "3 or less boxes": + check $bestCombinations(Boxes, 15, 3) == Expected[1] + test "2 or less boxes": + check $bestCombinations(Boxes, 15, 2) == Expected[2] diff --git a/challenge-037/archargelod/README b/challenge-037/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-037/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-037/archargelod/nim/ch_1.nim b/challenge-037/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..77a4e43e6f --- /dev/null +++ b/challenge-037/archargelod/nim/ch_1.nim @@ -0,0 +1,37 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/times + +proc findSmallestMultiple(x, max: int, offset = 0): tuple[count, number: int] = + ## Find the smallest number, greater than or equal to max + ## that is divisible by x + let max0 = max - offset + let smallestK = + if max0 mod x == 0: + max0 div x + else: + max0 div x + 1 + + (smallestK+1, x * smallestK + offset) + +proc main = + const Year = 2019 + for month in Month: + let monthDays = getDaysInMonth(month, Year) + let firstSunday = abs(7 - getDayOfWeek(1, month, Year).ord) + let (weekEnds, lastSunday) = findSmallestMultiple(7, monthDays, offset = firstSunday) + + var weekDays = weekEnds * 2 + + if firstSunday == 1: + dec weekDays # remove first saturday + + if lastSunday > monthDays: + dec weekDays # remove last sunday + if lastSunday - 1 > monthDays: + dec weekDays # remove last saturday + + echo month, ": ", monthDays - weekDays, " days" + + +when isMainModule: + main() diff --git a/challenge-037/archargelod/nim/ch_2.nim b/challenge-037/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..4102589e8f --- /dev/null +++ b/challenge-037/archargelod/nim/ch_2.nim @@ -0,0 +1,84 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +# Notes: Vim and AWK had done 99% of work here. +import std/times + +const + DecemberDaylight = [ + initDuration(hours = 8, minutes = 11, seconds = 53), + initDuration(hours = 8, minutes = 09, seconds = 53), + initDuration(hours = 8, minutes = 07, seconds = 57), + initDuration(hours = 8, minutes = 06, seconds = 07), + initDuration(hours = 8, minutes = 04, seconds = 22), + initDuration(hours = 8, minutes = 02, seconds = 42), + initDuration(hours = 8, minutes = 01, seconds = 08), + initDuration(hours = 7, minutes = 59, seconds = 40), + initDuration(hours = 7, minutes = 58, seconds = 17), + initDuration(hours = 7, minutes = 57, seconds = 01), + initDuration(hours = 7, minutes = 55, seconds = 50), + initDuration(hours = 7, minutes = 54, seconds = 45), + initDuration(hours = 7, minutes = 53, seconds = 46), + initDuration(hours = 7, minutes = 52, seconds = 54), + initDuration(hours = 7, minutes = 52, seconds = 08), + initDuration(hours = 7, minutes = 51, seconds = 28), + initDuration(hours = 7, minutes = 50, seconds = 54), + initDuration(hours = 7, minutes = 50, seconds = 27), + initDuration(hours = 7, minutes = 50, seconds = 06), + initDuration(hours = 7, minutes = 49, seconds = 52), + initDuration(hours = 7, minutes = 49, seconds = 44), + initDuration(hours = 7, minutes = 49, seconds = 43), + initDuration(hours = 7, minutes = 49, seconds = 49), + initDuration(hours = 7, minutes = 50, seconds = 01), + initDuration(hours = 7, minutes = 50, seconds = 19), + initDuration(hours = 7, minutes = 50, seconds = 44), + initDuration(hours = 7, minutes = 51, seconds = 15), + initDuration(hours = 7, minutes = 51, seconds = 53), + initDuration(hours = 7, minutes = 52, seconds = 37), + initDuration(hours = 7, minutes = 53, seconds = 28), + initDuration(hours = 7, minutes = 54, seconds = 24), + ] + + NovemberDaylight = [ + initDuration(hours = 9, minutes = 40, seconds = 44), + initDuration(hours = 9, minutes = 37, seconds = 10), + initDuration(hours = 9, minutes = 33, seconds = 38), + initDuration(hours = 9, minutes = 30, seconds = 07), + initDuration(hours = 9, minutes = 26, seconds = 38), + initDuration(hours = 9, minutes = 23, seconds = 11), + initDuration(hours = 9, minutes = 19, seconds = 45), + initDuration(hours = 9, minutes = 16, seconds = 22), + initDuration(hours = 9, minutes = 13, seconds = 01), + initDuration(hours = 9, minutes = 09, seconds = 42), + initDuration(hours = 9, minutes = 06, seconds = 25), + initDuration(hours = 9, minutes = 03, seconds = 11), + initDuration(hours = 8, minutes = 59, seconds = 59), + initDuration(hours = 8, minutes = 56, seconds = 50), + initDuration(hours = 8, minutes = 53, seconds = 44), + initDuration(hours = 8, minutes = 50, seconds = 40), + initDuration(hours = 8, minutes = 47, seconds = 39), + initDuration(hours = 8, minutes = 44, seconds = 42), + initDuration(hours = 8, minutes = 41, seconds = 48), + initDuration(hours = 8, minutes = 38, seconds = 57), + initDuration(hours = 8, minutes = 36, seconds = 09), + initDuration(hours = 8, minutes = 33, seconds = 25), + initDuration(hours = 8, minutes = 30, seconds = 45), + initDuration(hours = 8, minutes = 28, seconds = 09), + initDuration(hours = 8, minutes = 25, seconds = 36), + initDuration(hours = 8, minutes = 23, seconds = 08), + initDuration(hours = 8, minutes = 20, seconds = 44), + initDuration(hours = 8, minutes = 18, seconds = 24), + initDuration(hours = 8, minutes = 16, seconds = 09), + initDuration(hours = 8, minutes = 13, seconds = 59), + ] + +proc diff(a, b: openarray[Duration]): Duration = + for d in a: + result += d + for d in b: + result -= d + +proc main() = + let dayLightLoss = abs(diff(DecemberDaylight, NovemberDaylight)) + echo "December had lost ", dayLightLoss, " of daylight, compared to November in 2019, London." + +when isMainModule: + main() diff --git a/challenge-038/archargelod/README b/challenge-038/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-038/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-038/archargelod/nim/ch_1.nim b/challenge-038/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..a5663f3205 --- /dev/null +++ b/challenge-038/archargelod/nim/ch_1.nim @@ -0,0 +1,47 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/strutils + +proc formattedDate(date: string): string = + template throwError = + raise newException(ValueError, "Invalid date format, use: (0-1)(00-99)(01-12)(01-31)") + + if date.len != 7: + throwError() + + # year + case date[0] + of '1': result.add "20" + of '2': result.add "19" + else: + throwError() + + if {date[1], date[2]} <= Digits: + result.add date[1] & date[2] + else: + throwError() + + try: + # month + if parseInt(date[3] & date[4]) in 1..12: + result.add '-' & date[3] & date[4] + else: + throwError() + + # day + if parseInt(date[5] & date[6]) in 1..31: + result.add '-' & date[5] & date[6] + else: + throwError() + except ValueError: + throwError() + +when isMainModule: + import std/unittest + + const + Test = "2230120" + Expected = "1923-01-20" + + suite "Date parser": + test "Example 1": + check formattedDate(Test) == Expected diff --git a/challenge-038/archargelod/nim/ch_2.nim b/challenge-038/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..e98dbf0fee --- /dev/null +++ b/challenge-038/archargelod/nim/ch_2.nim @@ -0,0 +1,86 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[strutils, strformat, tables, sets, random] + +type + WordGame = ref object + points: Table[char, int] + bag: CountTable[char] + dict: HashSet[string] + +proc draw*(game: var WordGame, n: Natural): seq[char] = + for _ in 1..n: + let notEmpty: seq[char] = block: + var tmp: seq[char] + for val, cnt in game.bag: + if cnt > 0: tmp.add val + tmp + + let choice = notEmpty[rand(notEmpty.low..notEmpty.high)] + result.add choice + game.bag.inc(choice, -1) + +proc rate*(game: WordGame, word: string): int = + for c in word: + result += game.points[c] + +proc spellBestWord*(game: WordGame, letters: seq[char]): string = + let available = letters.toCountTable() + var bestRating = 0 + + for word in game.dict: + block validWord: + var available = available + for c in word: + if c in available: + available.inc(c, -1) + else: + break validWord + + let rating = game.rate(word) + if rating > bestRating: + bestRating = rating + result = word + +proc loadDict(filename: string, maxLen = int.high): HashSet[string] = + for word in lines filename: + if word.len > maxLen: continue + block onlyLetters: + for c in word: + if c notin Letters: + break onlyLetters + + result.incl word.toLowerAscii + +proc main = + const + MaxWordLength = 7 + Points = { + 'a': 1, 'g': 1, 'i': 1, 's': 1, 'u': 1, 'x': 1, 'z': 1, + 'e': 2, 'j': 2, 'l': 2, 'r': 2, 'v': 2, 'y': 2, + 'f': 3, 'd': 3, 'p': 3, 'w': 3, + 'b': 4, 'n': 4, + 't': 5, 'o': 5, 'h': 5, 'm': 5, 'c': 5, + 'k': 10, 'q': 10, + }.toTable() + Bag = toCountTable( + "aaaaaaaagggiiiiisssssssuuuuuxxzzzzzeeeeeeeeejjjlllrrrvvvyyyyyfffdddpppppwwwwwbbbbbnnnntttttooohhhmmmmcccckkqq" + ) + + var + game = WordGame( + points: Points, + bag: Bag, + dict: loadDict("/usr/share/dict/words", MaxWordLength) + ) + + randomize() + + let + letters = game.draw(MaxWordLength) + bestWord = game.spellBestWord(letters) + rating = game.rate(bestWord) + + echo &"The best word you can spell out of letters {letters} is '{bestWord}', with rating of {rating} points." + +when isMainModule: + main() diff --git a/challenge-039/archargelod/README b/challenge-039/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-039/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-039/archargelod/nim/ch_1.nim b/challenge-039/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..5b52e3268f --- /dev/null +++ b/challenge-039/archargelod/nim/ch_1.nim @@ -0,0 +1,47 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[strutils, times, algorithm] +type + TimeRange = HSlice[Time, Time] + +proc parseGuestBook(input: string): seq[TimeRange] = + for line in input.splitLines(): + let fields = line.splitWhitespace() + let (inTime, outTime) = (fields[3], fields[5]) + + result.add parseTime(inTime, "HH:mm", utc())..parseTime(outTime, "HH:mm", utc()) + +proc mergeOverlaps[T](input: openarray[HSlice[T,T]]): seq[HSlice[T,T]] = + let input = input.sortedByIt(it.a) + result.add input[0] + for range in input[1..^1]: + if range.a <= result[^1].b: + result[^1].b = range.b + else: + result.add range + +proc guestHouseLightDuration*(guestBook: string): Duration = + var timeIntervals = guestBook.parseGuestBook().mergeOverlaps() + + for interval in timeIntervals: + result += interval.b - interval.a + +when isMainModule: + import std/unittest + + const + Test = """ +1) Alex IN: 09:10 OUT: 09:45 +2) Arnold IN: 09:15 OUT: 09:33 +3) Bob IN: 09:22 OUT: 09:55 +4) Charlie IN: 09:25 OUT: 10:05 +5) Steve IN: 09:33 OUT: 10:01 +6) Roger IN: 09:44 OUT: 10:12 +7) David IN: 09:57 OUT: 10:23 +8) Neil IN: 10:01 OUT: 10:19 +9) Chris IN: 10:10 OUT: 11:00""" + Expected = initDuration(minutes = 110) + + suite "Guest house lights": + test "Example 1": + check guestHouseLightDuration(Test) == Expected + diff --git a/challenge-039/archargelod/nim/ch_2.nim b/challenge-039/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..60aa0d2ba7 --- /dev/null +++ b/challenge-039/archargelod/nim/ch_2.nim @@ -0,0 +1,54 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/strutils + +proc popExcept[T](stack: var seq[T]): T = + if stack.len < 1: + raise newException(ValueError, "Stack is Empty") + stack.pop() + +proc evaluateRPN*(expression: string): float = + var stack: seq[float] + for token in expression.splitWhitespace(): + if token in ["+", "-", "*", "/"]: + let val2 = stack.popExcept() + let val1 = stack.popExcept() + case token + of "+": stack.add val1 + val2 + of "-": stack.add val1 - val2 + of "*": stack.add val1 * val2 + of "/": stack.add val1 / val2 + else: + stack.add parseFloat(token) + + if stack.len == 1: + stack[0] + else: + raise newException(ValueError, "Invalid expression") + +when isMainModule: + import std/unittest + + suite "Reverse Polish Notation": + test "Basic Subtraction": + check evaluateRPN("3 2 -") == 1.0 + + test "Multiple Operations": + check evaluateRPN("3 4 + 2 *") == 14.0 + + test "Division": + check evaluateRPN("10 2 /") == 5.0 + + test "Float Operations": + check evaluateRPN("3.5 2.5 +") == 6.0 + + test "Empty Stack Error": + expect ValueError: + discard evaluateRPN("2 +") + + test "Invalid Expression Error": + expect ValueError: + discard evaluateRPN("2 3 + 4") + + test "Invalid Float Literal Error": + expect ValueError: + discard evaluateRPN("10.5.3 2 +") diff --git a/challenge-040/archargelod/README b/challenge-040/archargelod/README new file mode 100644 index 0000000000..6cd57e1074 --- /dev/null +++ b/challenge-040/archargelod/README @@ -0,0 +1 @@ +Solution by archargelod diff --git a/challenge-040/archargelod/nim/ch_1.nim b/challenge-040/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..2de2e1f87b --- /dev/null +++ b/challenge-040/archargelod/nim/ch_1.nim @@ -0,0 +1,33 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off + +proc showMultipleSeqContent[T](arrays: openarray[seq[T]]): string = + for i in arrays[0].low .. arrays[0].high: + result &= arrays[0][i] + for arr in arrays[1..^1]: + result &= ' ' + result &= arr[i] + result &= '\n' + +when isMainModule: + import std/unittest + + const + Test = [ + @"ILOVEYOU", + @"24032019", + @"!?f$%^&*", + ] + Expected = """ +I 2 ! +L 4 ? +O 0 f +V 3 $ +E 2 % +Y 0 ^ +O 1 & +U 9 * +""" + + suite "Show multiple arrays content": + test "Example 1": + check Test.showMultipleSeqContent() == Expected diff --git a/challenge-040/archargelod/nim/ch_2.nim b/challenge-040/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..06fea76693 --- /dev/null +++ b/challenge-040/archargelod/nim/ch_2.nim @@ -0,0 +1,26 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/algorithm + +proc sortSublist[T](arr: var openarray[T], indices: varargs[int]) = + var tmp: seq[int] + for i in indices: + tmp.add arr[i] + + tmp.sort() + + for i, val in tmp: + arr[indices[i]] = val + + +when isMainModule: + import std/unittest + + const + Test = ([10, 4, 1, 8, 12, 3], [0, 2, 5]) + Expected = [1, 4, 3, 8, 12, 10] + + suite "Sort SubList": + test "Example 1": + var a = Test[0] + a.sortSublist(Test[1]) + check a == Expected diff --git a/challenge-259/archargelod/nim/ch_1.nim b/challenge-259/archargelod/nim/ch_1.nim new file mode 100755 index 0000000000..9f058082b6 --- /dev/null +++ b/challenge-259/archargelod/nim/ch_1.nim @@ -0,0 +1,32 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/times + +proc bankingDay( + date: DateTime, offset: int, holidays: openarray[DateTime] = @[] +): DateTime = + result = date + + var offset = offset + while offset > 0: + result += 1.days + if result.weekday notin {dSat, dSun} and result notin holidays: + dec offset + +when isMainModule: + import std/unittest + + let + Test: seq[tuple[start: DateTime, offset: int, holidays: seq[DateTime]]] = @[ + (dateTime(2018, mJun, 28), 3, @[dateTime(2018, mJul, 3)]), + (dateTime(2018, mJun, 28), 3, @[]), + ] + Expected = [ + dateTime(2018, mJul, 4), + dateTime(2018, mJul, 3), + ] + + suite "Banking Day Offset": + test "Example 1": + check bankingDay(Test[0].start, Test[0].offset, Test[0].holidays) == Expected[0] + test "Example 2": + check bankingDay(Test[1].start, Test[1].offset, Test[1].holidays) == Expected[1] diff --git a/challenge-259/archargelod/nim/ch_2.nim b/challenge-259/archargelod/nim/ch_2.nim new file mode 100755 index 0000000000..0928ec77c3 --- /dev/null +++ b/challenge-259/archargelod/nim/ch_2.nim @@ -0,0 +1,96 @@ +#!/usr/bin/env -S nim r -d:release --verbosity:0 --hints:off +import std/[strutils, parseutils, tables] + +type + ValueKind = enum + StringValue + IntValue + + Value = object + case kind: ValueKind + of StringValue: + strval: string + of IntValue: + intval: int + + Struct = object + id: string + fields: OrderedTable[string, Value] # assuming order is important + +proc `==`(v: Value, s: string): bool = + v.kind == StringValue and v.strval == s + +proc `==`(v: Value, i: SomeInteger): bool = + v.kind == IntValue and v.intval == i + +template consume(input: string, amount: int = 1) = + index += amount + if index > input.high: + raise newException(ValueError, "Unexpected end.") + +template consume(input, token: string) = + for i, c in token: + if index + i > input.high or c != input[index + i]: + raise newException(ValueError, "Expected: " & token) + index += token.len + +proc parseStruct(input: string): Struct = + var index = 0 + input.consume("{%") + + # parse ID + index += input.skipWhitespace(index) + let tokenLength = input.skipWhile(Letters + Digits + {'_'}, index) + result.id = input[index ..< index + tokenLength] + index += tokenLength + + index += input.skipWhitespace(index) + + # parse fields + while index < input.len: + if input[index] notin Letters + Digits + {'_'}: + break + + let fieldNameLength = input.skipWhile(Letters + Digits + {'_'}, index) + if index + fieldNameLength > input.high: + raise newException(ValueError, "Unexpected end.") + + let fieldName = input[index ..< index + fieldNameLength] + input.consume fieldNameLength + + input.consume "=" + + if input[index] == '"': + input.consume() + var strVal = "" + while true: + if input[index] == '\\': + input.consume() + elif input[index] == '"': + break + strVal &= input[index] + input.consume() + + result.fields[fieldName] = Value(kind: StringValue, strval: strVal) + input.consume "\"" + else: + let intLength = input.skipWhile(Digits, index) + let intVal = parseInt(input[index ..< index + intLength]) + result.fields[fieldName] = Value(kind: IntValue, intval: intVal) + input.consume intLength + + input.consume input.skipWhitespace(index) + input.consume "%}" + +when isMainModule: + import std/unittest + + const + Test = """{% youtube title="Title \\ \"quoted\" done" some_field=12 %}""" + + suite "Line Parser": + test "Example 1": + let struct = parseStruct(Test) + check struct.id == "youtube" + check struct.fields["title"] == "Title \\ \"quoted\" done" + check struct.fields["some_field"] == 12 -- cgit