Add day16 problem + solution
This commit is contained in:
parent
993b4e9009
commit
4d00e36a1b
4 changed files with 405 additions and 0 deletions
1
day16/input.txt
Normal file
1
day16/input.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
E20D41802B2984BD00540010F82D09E35880350D61A41D3004E5611E585F40159ED7AD7C90CF6BD6BE49C802DEB00525272CC1927752698693DA7C70029C0081002140096028C5400F6023C9C00D601ED88070070030005C2201448400E400F40400C400A50801E20004C1000809D14700B67676EE661137ADC64FF2BBAD745B3F2D69026335E92A0053533D78932A9DFE23AC7858C028920A973785338832CFA200F47C81D2BBBC7F9A9E1802FE00ACBA44F4D1E775DDC19C8054D93B7E72DBE7006AA200C41A8510980010D8731720CB80132918319804738AB3A8D3E773C4A4015A498E680292B1852E753E2B29D97F0DE6008CB3D4D031802D2853400D24DEAE0137AB8210051D24EB600844B95C56781B3004F002B99D8F635379EDE273AF26972D4A5610BA51004C12D1E25D802F32313239377B37100105343327E8031802B801AA00021D07231C2F10076184668693AC6600BCD83E8025231D752E5ADE311008A4EA092754596C6789727F069F99A4645008247D2579388DCF53558AE4B76B257200AAB80107947E94789FE76E36402868803F0D62743F00043A1646288800084C3F8971308032996A2BD8023292DF8BE467BB3790047F2572EF004A699E6164C013A007C62848DE91CC6DB459B6B40087E530AB31EE633BD23180393CBF36333038E011CBCE73C6FB098F4956112C98864EA1C2801D2D0F319802D60088002190620E479100622E4358952D84510074C0188CF0923410021F1CE1146E3006E3FC578EE600A4B6C4B002449C97E92449C97E92459796EB4FF874400A9A16100A26CEA6D0E5E5EC8841C9B8FE37109C99818023A00A4FD8BA531586BB8B1DC9AE080293B6972B7FA444285CC00AE492BC910C1697B5BDD8425409700562F471201186C0120004322B42489A200D4138A71AA796D00374978FE07B2314E99BFB6E909678A0
|
||||||
131
day16/p1.nim
Normal file
131
day16/p1.nim
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
import os, bitops
|
||||||
|
import std/[strutils]
|
||||||
|
|
||||||
|
type
|
||||||
|
ReadResult =
|
||||||
|
tuple[value: int, bitsRead: int]
|
||||||
|
|
||||||
|
PacketType = enum
|
||||||
|
Literal = 4
|
||||||
|
|
||||||
|
|
||||||
|
proc hexToBin(hex: char): int {.inline.} =
|
||||||
|
case hex:
|
||||||
|
of '0': 0b0000
|
||||||
|
of '1': 0b0001
|
||||||
|
of '2': 0b0010
|
||||||
|
of '3': 0b0011
|
||||||
|
of '4': 0b0100
|
||||||
|
of '5': 0b0101
|
||||||
|
of '6': 0b0110
|
||||||
|
of '7': 0b0111
|
||||||
|
of '8': 0b1000
|
||||||
|
of '9': 0b1001
|
||||||
|
of 'A', 'a': 0b1010
|
||||||
|
of 'B', 'b': 0b1011
|
||||||
|
of 'C', 'c': 0b1100
|
||||||
|
of 'D', 'd': 0b1101
|
||||||
|
of 'E', 'e': 0b1110
|
||||||
|
of 'F', 'f': 0b1111
|
||||||
|
else: 0
|
||||||
|
|
||||||
|
template readBits(packet: string, numBits: int): ReadResult =
|
||||||
|
let bytesToRead = (((bitPos mod 4) + numBits) div 4) + 1
|
||||||
|
var skipBits = bitPos mod 4
|
||||||
|
var numBitsToRead = numBits
|
||||||
|
var res: ReadResult
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
while numBitsToRead > 0 and i < bytesToRead and ((bitPos div 4) + i) < packet.len:
|
||||||
|
var bin = packet[(bitPos div 4) + i].hexToBin
|
||||||
|
|
||||||
|
var currentBit = 4
|
||||||
|
|
||||||
|
case skipBits:
|
||||||
|
of 1: currentBit = 3
|
||||||
|
of 2: currentBit = 2
|
||||||
|
of 3: currentBit = 1
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
skipBits = 0
|
||||||
|
|
||||||
|
while currentBit > 0 and numBitsToRead > 0:
|
||||||
|
let currentBVal = bin.testBit(currentBit - 1).int
|
||||||
|
res.value += currentBVal shl (numBitsToRead - 1)
|
||||||
|
res.bitsRead += 1
|
||||||
|
currentBit -= 1
|
||||||
|
numBitsToRead -= 1
|
||||||
|
|
||||||
|
i.inc
|
||||||
|
|
||||||
|
bitPos += res.bitsRead
|
||||||
|
res
|
||||||
|
|
||||||
|
template readLiteral(packet: string): ReadResult =
|
||||||
|
var
|
||||||
|
indicator = packet.readBits(1)
|
||||||
|
nextSection: ReadResult
|
||||||
|
res: ReadResult
|
||||||
|
|
||||||
|
res.bitsRead += indicator.bitsRead
|
||||||
|
|
||||||
|
while indicator.value == 1:
|
||||||
|
nextSection = packet.readBits(4)
|
||||||
|
|
||||||
|
res.value = res.value shl 4
|
||||||
|
res.value += nextSection.value
|
||||||
|
res.bitsRead += nextSection.bitsRead
|
||||||
|
|
||||||
|
indicator = packet.readBits(1)
|
||||||
|
res.bitsRead += indicator.bitsRead
|
||||||
|
|
||||||
|
# read the last line
|
||||||
|
nextSection = packet.readBits(4)
|
||||||
|
|
||||||
|
res.value = res.value shl 4
|
||||||
|
res.value += nextSection.value
|
||||||
|
res.bitsRead += nextSection.bitsRead
|
||||||
|
|
||||||
|
res
|
||||||
|
|
||||||
|
proc parsePacket(packet: string, bitPos: var int, verSum: var int) =
|
||||||
|
let
|
||||||
|
version = packet.readBits(3)
|
||||||
|
packetType = packet.readBits(3)
|
||||||
|
|
||||||
|
verSum += version.value
|
||||||
|
|
||||||
|
if packetType.value == Literal.int:
|
||||||
|
# Read Literal value
|
||||||
|
discard packet.readLiteral()
|
||||||
|
else:
|
||||||
|
let lengthTypeId = packet.readBits(1)
|
||||||
|
|
||||||
|
if lengthTypeId.value == 0:
|
||||||
|
# 15 bit mode
|
||||||
|
let subPacketLen = packet.readBits(15)
|
||||||
|
var
|
||||||
|
bitsRead = 0
|
||||||
|
initialBitPos = bitPos
|
||||||
|
while bitsRead < subPacketLen.value:
|
||||||
|
packet.parsePacket(bitPos, verSum)
|
||||||
|
bitsRead = bitPos - initialBitPos
|
||||||
|
else:
|
||||||
|
# 11 bit mode
|
||||||
|
let numSubPackets = packet.readBits(11).value
|
||||||
|
for i in 0 ..< numSubPackets:
|
||||||
|
packet.parsePacket(bitPos, verSum)
|
||||||
|
|
||||||
|
proc main() =
|
||||||
|
let
|
||||||
|
fileName = paramStr(1)
|
||||||
|
packet = fileName.readFile().strip()
|
||||||
|
|
||||||
|
var bitPos = 0
|
||||||
|
var verSum = 0
|
||||||
|
|
||||||
|
packet.parsePacket(bitPos, verSum)
|
||||||
|
|
||||||
|
echo "VER SUM: ", verSum
|
||||||
|
|
||||||
|
main()
|
||||||
164
day16/p2.nim
Normal file
164
day16/p2.nim
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
import os, bitops, sugar
|
||||||
|
import std/[strutils, sequtils]
|
||||||
|
|
||||||
|
type
|
||||||
|
ReadResult =
|
||||||
|
tuple[value: int, bitsRead: int]
|
||||||
|
|
||||||
|
ParseResult = int
|
||||||
|
|
||||||
|
PacketType = enum
|
||||||
|
Sum
|
||||||
|
Product
|
||||||
|
Minimum
|
||||||
|
Maximum
|
||||||
|
Literal
|
||||||
|
GreaterThan
|
||||||
|
LessThan
|
||||||
|
Equal
|
||||||
|
|
||||||
|
|
||||||
|
proc hexToBin(hex: char): int {.inline.} =
|
||||||
|
case hex:
|
||||||
|
of '0': 0b0000
|
||||||
|
of '1': 0b0001
|
||||||
|
of '2': 0b0010
|
||||||
|
of '3': 0b0011
|
||||||
|
of '4': 0b0100
|
||||||
|
of '5': 0b0101
|
||||||
|
of '6': 0b0110
|
||||||
|
of '7': 0b0111
|
||||||
|
of '8': 0b1000
|
||||||
|
of '9': 0b1001
|
||||||
|
of 'A', 'a': 0b1010
|
||||||
|
of 'B', 'b': 0b1011
|
||||||
|
of 'C', 'c': 0b1100
|
||||||
|
of 'D', 'd': 0b1101
|
||||||
|
of 'E', 'e': 0b1110
|
||||||
|
of 'F', 'f': 0b1111
|
||||||
|
else: 0
|
||||||
|
|
||||||
|
template readBits(packet: string, numBits: int): ReadResult =
|
||||||
|
let bytesToRead = (((bitPos mod 4) + numBits) div 4) + 1
|
||||||
|
var skipBits = bitPos mod 4
|
||||||
|
var numBitsToRead = numBits
|
||||||
|
var res: ReadResult
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
while numBitsToRead > 0 and i < bytesToRead and ((bitPos div 4) + i) < packet.len:
|
||||||
|
var bin = packet[(bitPos div 4) + i].hexToBin
|
||||||
|
|
||||||
|
var currentBit = 4
|
||||||
|
|
||||||
|
case skipBits:
|
||||||
|
of 1: currentBit = 3
|
||||||
|
of 2: currentBit = 2
|
||||||
|
of 3: currentBit = 1
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
skipBits = 0
|
||||||
|
|
||||||
|
while currentBit > 0 and numBitsToRead > 0:
|
||||||
|
let currentBVal = bin.testBit(currentBit - 1).int
|
||||||
|
res.value += currentBVal shl (numBitsToRead - 1)
|
||||||
|
res.bitsRead += 1
|
||||||
|
currentBit -= 1
|
||||||
|
numBitsToRead -= 1
|
||||||
|
|
||||||
|
i.inc
|
||||||
|
|
||||||
|
bitPos += res.bitsRead
|
||||||
|
res
|
||||||
|
|
||||||
|
template readLiteral(packet: string): ReadResult =
|
||||||
|
var
|
||||||
|
indicator = packet.readBits(1)
|
||||||
|
nextSection: ReadResult
|
||||||
|
res: ReadResult
|
||||||
|
|
||||||
|
res.bitsRead += indicator.bitsRead
|
||||||
|
|
||||||
|
while indicator.value == 1:
|
||||||
|
nextSection = packet.readBits(4)
|
||||||
|
|
||||||
|
res.value = res.value shl 4
|
||||||
|
res.value += nextSection.value
|
||||||
|
res.bitsRead += nextSection.bitsRead
|
||||||
|
|
||||||
|
indicator = packet.readBits(1)
|
||||||
|
res.bitsRead += indicator.bitsRead
|
||||||
|
|
||||||
|
# read the last line
|
||||||
|
nextSection = packet.readBits(4)
|
||||||
|
|
||||||
|
res.value = res.value shl 4
|
||||||
|
res.value += nextSection.value
|
||||||
|
res.bitsRead += nextSection.bitsRead
|
||||||
|
|
||||||
|
res
|
||||||
|
|
||||||
|
proc parsePacket(packet: string, bitPos: var int, verSum: var int): ParseResult =
|
||||||
|
let
|
||||||
|
version = packet.readBits(3)
|
||||||
|
packetType = packet.readBits(3)
|
||||||
|
|
||||||
|
verSum += version.value
|
||||||
|
|
||||||
|
var operation: (int, int) -> int
|
||||||
|
|
||||||
|
case packetType.value.PacketType:
|
||||||
|
of Literal:
|
||||||
|
# Read Literal value
|
||||||
|
return packet.readLiteral().value
|
||||||
|
of Sum:
|
||||||
|
operation = (x, y: int) => x + y
|
||||||
|
of Product:
|
||||||
|
operation = (x, y: int) => x * y
|
||||||
|
of Minimum:
|
||||||
|
operation = (x, y: int) => min(x, y)
|
||||||
|
of Maximum:
|
||||||
|
operation = (x, y: int) => max(x, y)
|
||||||
|
of GreaterThan:
|
||||||
|
operation = (x, y: int) => (x > y).int
|
||||||
|
of LessThan:
|
||||||
|
operation = (x, y: int) => (x < y).int
|
||||||
|
of Equal:
|
||||||
|
operation = (x, y: int) => (x == y).int
|
||||||
|
|
||||||
|
let lengthTypeId = packet.readBits(1)
|
||||||
|
var acc: seq[int]
|
||||||
|
|
||||||
|
if lengthTypeId.value == 0:
|
||||||
|
# 15 bit mode
|
||||||
|
let subPacketLen = packet.readBits(15).value
|
||||||
|
var
|
||||||
|
bitsRead = 0
|
||||||
|
initialBitPos = bitPos
|
||||||
|
|
||||||
|
while bitsRead < subPacketLen:
|
||||||
|
acc.add packet.parsePacket(bitPos, verSum)
|
||||||
|
bitsRead = bitPos - initialBitPos
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 11 bit mode
|
||||||
|
let numSubPackets = packet.readBits(11).value
|
||||||
|
for i in 0 ..< numSubPackets:
|
||||||
|
acc.add packet.parsePacket(bitPos, verSum)
|
||||||
|
|
||||||
|
return acc.foldl(operation(a, b))
|
||||||
|
|
||||||
|
proc main() =
|
||||||
|
let
|
||||||
|
fileName = paramStr(1)
|
||||||
|
var packet: string
|
||||||
|
try:
|
||||||
|
packet = fileName.readFile().strip()
|
||||||
|
except IOError:
|
||||||
|
packet = paramStr(1)
|
||||||
|
|
||||||
|
var bitPos = 0
|
||||||
|
var verSum = 0
|
||||||
|
|
||||||
|
echo packet.parsePacket(bitPos, verSum)
|
||||||
|
|
||||||
|
main()
|
||||||
109
day16/prob.md
Normal file
109
day16/prob.md
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
--- Day 16: Packet Decoder ---
|
||||||
|
As you leave the cave and reach open waters, you receive a transmission from the Elves back on the ship.
|
||||||
|
|
||||||
|
The transmission was sent using the Buoyancy Interchange Transmission System (BITS), a method of packing numeric expressions into a binary sequence. Your submarine's computer has saved the transmission in hexadecimal (your puzzle input).
|
||||||
|
|
||||||
|
The first step of decoding the message is to convert the hexadecimal representation into binary. Each character of hexadecimal corresponds to four bits of binary data:
|
||||||
|
|
||||||
|
0 = 0000
|
||||||
|
1 = 0001
|
||||||
|
2 = 0010
|
||||||
|
3 = 0011
|
||||||
|
4 = 0100
|
||||||
|
5 = 0101
|
||||||
|
6 = 0110
|
||||||
|
7 = 0111
|
||||||
|
8 = 1000
|
||||||
|
9 = 1001
|
||||||
|
A = 1010
|
||||||
|
B = 1011
|
||||||
|
C = 1100
|
||||||
|
D = 1101
|
||||||
|
E = 1110
|
||||||
|
F = 1111
|
||||||
|
The BITS transmission contains a single packet at its outermost layer which itself contains many other packets. The hexadecimal representation of this packet might encode a few extra 0 bits at the end; these are not part of the transmission and should be ignored.
|
||||||
|
|
||||||
|
Every packet begins with a standard header: the first three bits encode the packet version, and the next three bits encode the packet type ID. These two values are numbers; all numbers encoded in any packet are represented as binary with the most significant bit first. For example, a version encoded as the binary sequence 100 represents the number 4.
|
||||||
|
|
||||||
|
Packets with type ID 4 represent a literal value. Literal value packets encode a single binary number. To do this, the binary number is padded with leading zeroes until its length is a multiple of four bits, and then it is broken into groups of four bits. Each group is prefixed by a 1 bit except the last group, which is prefixed by a 0 bit. These groups of five bits immediately follow the packet header. For example, the hexadecimal string D2FE28 becomes:
|
||||||
|
|
||||||
|
110100101111111000101000
|
||||||
|
VVVTTTAAAAABBBBBCCCCC
|
||||||
|
Below each bit is a label indicating its purpose:
|
||||||
|
|
||||||
|
The three bits labeled V (110) are the packet version, 6.
|
||||||
|
The three bits labeled T (100) are the packet type ID, 4, which means the packet is a literal value.
|
||||||
|
The five bits labeled A (10111) start with a 1 (not the last group, keep reading) and contain the first four bits of the number, 0111.
|
||||||
|
The five bits labeled B (11110) start with a 1 (not the last group, keep reading) and contain four more bits of the number, 1110.
|
||||||
|
The five bits labeled C (00101) start with a 0 (last group, end of packet) and contain the last four bits of the number, 0101.
|
||||||
|
The three unlabeled 0 bits at the end are extra due to the hexadecimal representation and should be ignored.
|
||||||
|
So, this packet represents a literal value with binary representation 011111100101, which is 2021 in decimal.
|
||||||
|
|
||||||
|
Every other type of packet (any packet with a type ID other than 4) represent an operator that performs some calculation on one or more sub-packets contained within. Right now, the specific operations aren't important; focus on parsing the hierarchy of sub-packets.
|
||||||
|
|
||||||
|
An operator packet contains one or more packets. To indicate which subsequent binary data represents its sub-packets, an operator packet can use one of two modes indicated by the bit immediately after the packet header; this is called the length type ID:
|
||||||
|
|
||||||
|
If the length type ID is 0, then the next 15 bits are a number that represents the total length in bits of the sub-packets contained by this packet.
|
||||||
|
If the length type ID is 1, then the next 11 bits are a number that represents the number of sub-packets immediately contained by this packet.
|
||||||
|
Finally, after the length type ID bit and the 15-bit or 11-bit field, the sub-packets appear.
|
||||||
|
|
||||||
|
For example, here is an operator packet (hexadecimal string 38006F45291200) with length type ID 0 that contains two sub-packets:
|
||||||
|
|
||||||
|
00111000000000000110111101000101001010010001001000000000
|
||||||
|
VVVTTTILLLLLLLLLLLLLLLAAAAAAAAAAABBBBBBBBBBBBBBBB
|
||||||
|
The three bits labeled V (001) are the packet version, 1.
|
||||||
|
The three bits labeled T (110) are the packet type ID, 6, which means the packet is an operator.
|
||||||
|
The bit labeled I (0) is the length type ID, which indicates that the length is a 15-bit number representing the number of bits in the sub-packets.
|
||||||
|
The 15 bits labeled L (000000000011011) contain the length of the sub-packets in bits, 27.
|
||||||
|
The 11 bits labeled A contain the first sub-packet, a literal value representing the number 10.
|
||||||
|
The 16 bits labeled B contain the second sub-packet, a literal value representing the number 20.
|
||||||
|
After reading 11 and 16 bits of sub-packet data, the total length indicated in L (27) is reached, and so parsing of this packet stops.
|
||||||
|
|
||||||
|
As another example, here is an operator packet (hexadecimal string EE00D40C823060) with length type ID 1 that contains three sub-packets:
|
||||||
|
|
||||||
|
11101110000000001101010000001100100000100011000001100000
|
||||||
|
VVVTTTILLLLLLLLLLLAAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCC
|
||||||
|
The three bits labeled V (111) are the packet version, 7.
|
||||||
|
The three bits labeled T (011) are the packet type ID, 3, which means the packet is an operator.
|
||||||
|
The bit labeled I (1) is the length type ID, which indicates that the length is a 11-bit number representing the number of sub-packets.
|
||||||
|
The 11 bits labeled L (00000000011) contain the number of sub-packets, 3.
|
||||||
|
The 11 bits labeled A contain the first sub-packet, a literal value representing the number 1.
|
||||||
|
The 11 bits labeled B contain the second sub-packet, a literal value representing the number 2.
|
||||||
|
The 11 bits labeled C contain the third sub-packet, a literal value representing the number 3.
|
||||||
|
After reading 3 complete sub-packets, the number of sub-packets indicated in L (3) is reached, and so parsing of this packet stops.
|
||||||
|
|
||||||
|
For now, parse the hierarchy of the packets throughout the transmission and add up all of the version numbers.
|
||||||
|
|
||||||
|
Here are a few more examples of hexadecimal-encoded transmissions:
|
||||||
|
|
||||||
|
8A004A801A8002F478 represents an operator packet (version 4) which contains an operator packet (version 1) which contains an operator packet (version 5) which contains a literal value (version 6); this packet has a version sum of 16.
|
||||||
|
620080001611562C8802118E34 represents an operator packet (version 3) which contains two sub-packets; each sub-packet is an operator packet that contains two literal values. This packet has a version sum of 12.
|
||||||
|
C0015000016115A2E0802F182340 has the same structure as the previous example, but the outermost packet uses a different length type ID. This packet has a version sum of 23.
|
||||||
|
A0016C880162017C3686B18A3D4780 is an operator packet that contains an operator packet that contains an operator packet that contains five literal values; it has a version sum of 31.
|
||||||
|
Decode the structure of your hexadecimal-encoded BITS transmission; what do you get if you add up the version numbers in all packets?
|
||||||
|
|
||||||
|
--- Part Two ---
|
||||||
|
Now that you have the structure of your transmission decoded, you can calculate the value of the expression it represents.
|
||||||
|
|
||||||
|
Literal values (type ID 4) represent a single number as described above. The remaining type IDs are more interesting:
|
||||||
|
|
||||||
|
Packets with type ID 0 are sum packets - their value is the sum of the values of their sub-packets. If they only have a single sub-packet, their value is the value of the sub-packet.
|
||||||
|
Packets with type ID 1 are product packets - their value is the result of multiplying together the values of their sub-packets. If they only have a single sub-packet, their value is the value of the sub-packet.
|
||||||
|
Packets with type ID 2 are minimum packets - their value is the minimum of the values of their sub-packets.
|
||||||
|
Packets with type ID 3 are maximum packets - their value is the maximum of the values of their sub-packets.
|
||||||
|
Packets with type ID 5 are greater than packets - their value is 1 if the value of the first sub-packet is greater than the value of the second sub-packet; otherwise, their value is 0. These packets always have exactly two sub-packets.
|
||||||
|
Packets with type ID 6 are less than packets - their value is 1 if the value of the first sub-packet is less than the value of the second sub-packet; otherwise, their value is 0. These packets always have exactly two sub-packets.
|
||||||
|
Packets with type ID 7 are equal to packets - their value is 1 if the value of the first sub-packet is equal to the value of the second sub-packet; otherwise, their value is 0. These packets always have exactly two sub-packets.
|
||||||
|
Using these rules, you can now work out the value of the outermost packet in your BITS transmission.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
C200B40A82 finds the sum of 1 and 2, resulting in the value 3.
|
||||||
|
04005AC33890 finds the product of 6 and 9, resulting in the value 54.
|
||||||
|
880086C3E88112 finds the minimum of 7, 8, and 9, resulting in the value 7.
|
||||||
|
CE00C43D881120 finds the maximum of 7, 8, and 9, resulting in the value 9.
|
||||||
|
D8005AC2A8F0 produces 1, because 5 is less than 15.
|
||||||
|
F600BC2D8F produces 0, because 5 is not greater than 15.
|
||||||
|
9C005AC2F8F0 produces 0, because 5 is not equal to 15.
|
||||||
|
9C0141080250320F1802104A08 produces 1, because 1 + 3 = 2 * 2.
|
||||||
|
What do you get if you evaluate the expression represented by your hexadecimal-encoded BITS transmission?
|
||||||
Loading…
Add table
Add a link
Reference in a new issue