avatar
Siz Long

My name is Siz. I am a computer science graduate student specializing in backend development with Golang and Python, seeking opportunities in innovative tech projects. My personal website is me.longsizhuo.com .Connect with me on LinkedIn: https://www.linkedin.com/in/longsizhuo/.

  • Resume
  • Archives
  • Categories
  • Photos
  • Music



{{ date }}

{{ time }}

avatar
Siz Long

My name is Siz. I am a computer science graduate student specializing in backend development with Golang and Python, seeking opportunities in innovative tech projects. My personal website is me.longsizhuo.com .Connect with me on LinkedIn: https://www.linkedin.com/in/longsizhuo/.

  • 主页
  • Resume
  • Archives
  • Categories
  • Photos
  • Music

9021_TUT_8

  2024-11-05        
字数统计: 3.6k字   |   阅读时长: 22min

Exercise 1

Problem Description

In this exercise, we are asked to create a class named Prime that keeps track of prime numbers, ensuring certain values are considered valid primes and others are not. The class needs to be capable of:

  1. Raising an error when an input is not a valid integer prime.

  2. Tracking previously seen primes to prevent duplicates.

  3. Resetting the state to allow reprocessing of the primes.

The following Python script is used to test the functionality:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from exercise_8_1 import *

Prime.reset()
try:
Prime(1)
except PrimeError as e:
# 1 is not a prime number
print(e)
try:
Prime(2.0)
except PrimeError as e:
# 2.0 is not a prime number
print(e)
try:
Prime([1, 2, 3])
except PrimeError as e:
# [1, 2, 3] is not a prime number
print(e)
_ = Prime(2)
try:
Prime(2)
except PrimeError as e:
# We have seen 2 before
print(e)
_ = Prime(3)
try:
Prime(4)
except PrimeError as e:
# 4 is not a prime number
print(e)
_ = Prime(5)
_ = Prime(7)
try:
_ = Prime(2)
except PrimeError as e:
# We have seen 2 before
print(e)
_ = Prime(11)
try:
Prime(5)
except PrimeError as e:
# We have seen 5 before
print(e)
Prime.reset()
_ = Prime(2), Prime(3), Prime(5), Prime(7), Prime(11)

The expected output from running the above code is:

1
2
3
4
5
6
7
'1 is not a prime number\n'
'2.0 is not a prime number\n'
'[1, 2, 3] is not a prime number\n'
'We have seen 2 before\n'
'4 is not a prime number\n'
'We have seen 2 before\n'
'We have seen 5 before\n'

My Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# isinstance() is useful.

# DEFINE A CLASS THAT DERIVES FROM EXCEPTION

class PrimeError(Exception):
pass

class Prime:
_seen_primes = set() # Track primes that have been created

def __init__(self, num):
if not isinstance(num, int):
raise PrimeError(f"{num} is not a prime number")
if num < 2 or not self._is_prime(num):
raise PrimeError(f"{num} is not a prime number")
if num in self._seen_primes:
raise PrimeError(f"We have seen {num} before")

# Add the prime to the set if validation passes
self._seen_primes.add(num)
self.value = num

@staticmethod
def _is_prime(num):
"""Helper function to determine if a number is prime."""
if num < 2:
return False
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True

@classmethod
def reset(cls):
"""Clears the record of tracked primes."""
cls._seen_primes.clear()

Standard Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# isinstance() is useful.

class PrimeError(Exception):
pass

class Prime:
primes = set()

def reset():
Prime.primes = set()

def __init__(self, p):
if not isinstance(p, int) or p < 2 \
or any(p % k == 0 for k in range(2, p // 2 + 1)):
raise PrimeError(f'{p} is not a prime number')
if p in Prime.primes:
raise PrimeError(f'We have seen {p} before')
Prime.primes.add(p)

Summary

Here’s a comparison of my version with the standard answer, highlighting the main differences and potential advantages:

  1. Prime Checking Method:

    1. My Version: I use sqrt(num) as the upper limit for prime checking, iterating through for i in range(2, int(num ** 0.5) + 1). This approach is more efficient, especially for larger numbers, as it reduces the number of iterations by only checking up to the square root.
    2. Standard Answer: It checks from 2 to p // 2 for factors, which is slightly less efficient for larger numbers because it performs more division operations.
  2. Code Structure:

    1. My Version: Separates the prime-checking logic into a static method _is_prime, making the code more modular and reusable.
    2. Standard Answer: Integrates the prime-checking logic directly into the constructor. This keeps the code concise but might make it less reusable if the prime-checking logic is needed elsewhere.
  3. Reset Method:

    1. My Version: Uses @classmethod for resetting the _seen_primes set, which is more conventional and allows direct calling as a class method.
    2. Standard Answer: Defines reset as a standalone function without decorators. While it works, it may seem less consistent with typical class conventions.

Exercise 2

Problem Description

In this exercise, we need to implement a class named Modulo that represents numbers in modular arithmetic. The class should:

  1. Validate that the modulus is a prime number.

  2. Ensure that both the number and the modulus are integers.

  3. Represent the number in modular form.

The following Python script is used to test the functionality:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from exercise_8_2 import *

try:
Modulo(4, 1)
except PrimeError as e:
# 1 is not a prime number
print(e)
try:
# 2.0 is not a prime number
Modulo(4, 2.0)
except PrimeError as e:
print(e)
try:
Modulo({0}, 7)
except IntError as e:
# {0} is not an integer
print(e)
x = Modulo(6, 11)
print(repr(x))
print(x)
y = Modulo(11, 7)
print(repr(y))
print(y)
z = Modulo(-100, 29)
print(repr(z))
print(z)

The expected output from running the above code is:

1
2
3
4
5
6
7
8
9
'1 is not a prime number\n'
'2.0 is not a prime number\n'
'{0} is not an integer\n'
'Modulo(6, 11)\n'
'6 (mod 11)\n'
'Modulo(4, 7)\n'
'4 (mod 7)\n'
'Modulo(16, 29)\n'
'16 (mod 29)\n'

Standard Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# isinstance() is useful.

# DEFINE TWO CLASSES THAT DERIVE FROM EXCEPTION

class IntError(Exception):
pass

class PrimeError(Exception):
pass

class Modulo:
def __init__(self, k, p):
if not isinstance(p, int) or p < 2\
or any(p % k == 0 for k in range(2, p // 2 + 1)):
raise PrimeError(f'{p} is not a prime number')
if not isinstance(k, int):
raise IntError(f'{k} is not an integer')
self.modulus = p
self.k = k % p

def __repr__(self):
return f'Modulo({self.k}, {self.modulus})'

def __str__(self):
return f'{self.k} (mod {self.modulus})'

Summary

The Modulo class validates its inputs and provides a representation for modular arithmetic:

  • Exception Handling:

    • PrimeError is used when the modulus is not a valid prime number.

    • IntError is used when the input is not an integer.

  • Prime Check:

    • The constructor checks if the modulus is a prime number using a similar approach to the one used in Exercise 8.1 (so I use standard solution directly). If not, it raises PrimeError.
  • Representation:

    • The class overrides __repr__ and __str__ to provide appropriate representations for instances of Modulo.
  • Modular Arithmetic:

    • The value of k is stored in its modular form by computing k % p, which ensures it always remains within the bounds of 0 to p - 1.

Exercise 3

Problem Description

In this exercise, we extend the Modulo class from Exercise 8.2 to support arithmetic operations, specifically addition and multiplication between two Modulo objects. The class should:

  1. Support addition (+) and multiplication (*) between Modulo objects with the same modulus.

  2. Raise appropriate errors if operations are attempted with incompatible objects.

The following Python script is used to test the functionality:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from exercise_8_3 import *

x = Modulo(6, 11)
try:
x + 20
except ModuloError as e:
# 20 is not a Modulo object
print(e)
try:
y = Modulo(20, 13)
z = x + y
except ModuloError as e:
# 6 (mod 11) and 7 (mod 13) do not have the same modulus
print(e)
print('\nTesting addition\n')
y = Modulo(20, 11)
z = x + y
print(x, y, z, sep='; ')
y = Modulo(20, 11)
x += y
print(x, y, sep='; ')
print('\nTesting mutiplication\n')
y = Modulo(-30, 11)
z = x * y
print(x, y, z, sep='; ')
y = Modulo(-30, 11)
x *= y
print(x, y, sep='; ')

The expected output from running the above code is:

1
2
3
4
5
6
7
8
9
10
11
12
'20 is not a Modulo object\n'
'6 (mod 11) and 7 (mod 13) do not have the same modulus\n'
'\n'
'Testing addition\n'
'\n'
'6 (mod 11); 9 (mod 11); 4 (mod 11)\n'
'4 (mod 11); 9 (mod 11)\n'
'\n'
'Testing multiplication\n'
'\n'
'4 (mod 11); 3 (mod 11); 1 (mod 11)\n'
'1 (mod 11); 3 (mod 11)\n'

My Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# exercise_8_3.py

class PrimeError(Exception):
pass

class IntError(Exception):
pass

class ModuloError(Exception):
"""Exception for invalid Modulo operations."""
pass

class Modulo:
def __init__(self, number, modulus):
if not isinstance(modulus, int):
raise IntError(f"{modulus} is not an integer")
if not self._is_prime(modulus):
raise PrimeError(f"{modulus} is not a prime number")
if not isinstance(number, int):
raise IntError(f"{number} is not an integer")

self.number = number % modulus
self.modulus = modulus

@staticmethod
def _is_prime(num):
if num < 2:
return False
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True

def __repr__(self):
return f"Modulo({self.number}, {self.modulus})"

def __str__(self):
return f"{self.number} (mod {self.modulus})"

def __add__(self, other):
if not isinstance(other, Modulo):
raise ModuloError(f"{other} is not a Modulo object")
if self.modulus != other.modulus:
raise ModuloError(f"{self} and {other} do not have the same modulus")

result_number = (self.number + other.number) % self.modulus
return Modulo(result_number, self.modulus)

def __mul__(self, other):
if not isinstance(other, Modulo):
raise ModuloError("The right operand is not a Modulo object")
if self.modulus != other.modulus:
raise ModuloError(f"{self} and {other} do not have the same modulus")

result_number = (self.number * other.number) % self.modulus
return Modulo(result_number, self.modulus)

Standard Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class IntError(Exception):
pass

class PrimeError(Exception):
pass

class ModuloError(Exception):
pass

class Modulo:
def __init__(self, k, p):
if not isinstance(p, int) or p < 2\
or any(p % k == 0 for k in range(2, p // 2 + 1)):
raise PrimeError(f'{p} is not a prime number')
if not isinstance(k, int):
raise IntError(f'{k} is not an integer')
self.modulus = p
self.k = k % p

def _validate(self, m):
if not isinstance(m, Modulo):
raise ModuloError(f'{m} is not a Modulo object')
if m.modulus != self.modulus:
raise ModuloError(f'{self} and {m} do not have the same modulus')

def __repr__(self):
return f'Modulo({self.k}, {self.modulus})'

def __str__(self):
return f'{self.k} (mod {self.modulus})'

def __add__(self, m):
self._validate(m)
return Modulo(self.k + m.k, self.modulus)

def __iadd__(self, m):
self._validate(m)
self.k = (self.k + m.k) % self.modulus
return self

def __mul__(self, m):
self._validate(m)
return Modulo(self.k * m.k, self.modulus)

def __imul__(self, m):
self._validate(m)
self.k = self.k * m.k % self.modulus
return self

Summary

The differences between the two solutions are as follows:

  1. Validation Method:

    • The standard solution defines a _validate method to handle checks for the operands (Modulo type and modulus equality), improving code reuse.

    • My solution performs these checks directly inside the __add__ and __mul__ methods.

  2. In-Place Operations:

    • The standard solution includes in-place addition (__iadd__) and multiplication (__imul__) methods, which modify the current object directly.

    • My solution does not include these in-place methods, focusing only on returning new Modulo objects.

Exercise 4

Problem Description

In this exercise, we need to create an iterable class Prime that generates prime numbers in a sequence. Each instance of Prime should be able to independently generate the sequence of prime numbers.

The following Python script is used to test the functionality:

1
2
3
4
5
6
7
from exercise_8_4 import *

I = Prime()
print(*(next(I) for _ in range(10)), sep=', ')
print()
J = Prime()
print([next(J) for _ in range(10)])

The expected output from running the above code is:

1
2
3
'2, 3, 5, 7, 11, 13, 17, 19, 23, 29\n'
'\n'
'[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]\n'

My Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Prime:
def __init__(self):
self.current = 2 # Start with the first prime number

def __iter__(self):
return self # The class itself is the iterator

def __next__(self):
# Find the next prime number
while True:
if self._is_prime(self.current):
prime = self.current
self.current += 1
return prime
self.current += 1

@staticmethod
def _is_prime(num):
"""Helper function to determine if num is prime."""
if num < 2:
return False
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True

Standard Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Prime:
def __init__(self):
Prime.p = 2

def __iter__(self):
return self

def __next__(self):
if Prime.p == 2:
Prime.p = 1
return 2
else:
while True:
Prime.p += 2
if all (Prime.p % k for k in range(3, Prime.p // 2 + 1, 2)):
return Prime.p

Exercise 5

Shoelace Formula

The shoelace formula, also known as Gauss’s area formula or the surveyor’s formula, is a mathematical method used to calculate the area of a polygon when given the coordinates of its vertices. This method is especially useful for polygons whose vertices are defined on a Cartesian coordinate system. It is called the “shoelace formula” because of the crisscross pattern formed when calculating the terms.

$ A = \frac{1}{2} \left| \sum_{i=1}^{n} (x_i y_{i+1} - y_i x_{i+1}) \right|$

Example Calculation

Consider a triangle with vertices $(x_1, y_1)=(2,4)$, $(x_2, y_2)=(5,11)$ and $(x_3, y_3)=(12, 8)$:

  1. Write the coordinates as:

    $(2, 4), (5, 11), (12, 8), (2, 4)$

  2. Multiply diagonally from left to right:

    $2 \times 11 + 5 \times 8 + 12 \times 4 = 22 + 40 + 48 = 110$

  3. Multiply diagonally from right to left:

    $4 \times 5 + 11 \times 12 + 8 \times 2 = 20 + 132 + 16 = 168$

  4. Subtract and divide by 2:

    $A = \frac{1}{2} \left| 110 - 168 \right| = \frac{1}{2} \times 58 = 29$

  5. Thus, the area of the triangle is 29.

Problem Description

In this exercise, we need to create a set of classes representing different shapes: Polygon, Rectangle, and Square. Each shape has specific properties and a method to calculate the area. The classes should also provide informative messages when initialized or when calculating the area.

The following Python script is used to test the functionality:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from exercise_8_5 import *

print('Testing a polygon')
print()
# Printed out as a side effect of following statement:
# I am a polygon
x = Polygon(((-3.3, 3.0), (-2.3, 5.1), (0.2, 3.4), (3.2, 5.3),
(5.0, 2.8), (2.8, -1.8), (1.7, 0.7), (-2.6, -4.1),
(-5.7, -2.0), (-0.3, 0.7)))
print(x.nb_of_vertices)
# Printed out as a side effect of following statement:
# Computed using the shoelace formula
print(x.area())
print()
print('Testing a rectangle')
print()
# Printed out as a side effect of following statement:
# I am a polygon
# More precisely, I am a rectangle
y = Rectangle(((5.5, -2.8), (5.5, 4.4), (-3.6, 4.4), (-3.6, -2.8)))
print(y.nb_of_vertices)
# Printed out as a side effect of following statement:
# I could compute it more easily, but well, I leave it to Polygon...
# Computed using the shoelace formula
print(y.area())
print()
print('Testing a square')
print()
# Printed out as a side effect of following statement:
# I am a polygon
# More precisely, I am a rectangle
# Even more precisely, I am a square
z = Square(((-3.5, 3.5), (3.5, 3.5), (3.5, -3.5), (-3.5, -3.5)))
print(z.nb_of_vertices)
# Printed out as a side effect of following statement:
# I compute it myself!
print(z.area())

The expected output from running the above code is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
'Testing a polygon\n'
'\n'
'I am a polygon\n'
'10\n'
'Computed using the shoelace formula\n'
'42.224999999999994\n'
'\n'
'Testing a rectangle\n'
'\n'
'I am a polygon\n'
'More precisely, I am a rectangle\n'
'4\n'
'I could compute it more easily, but well, I leave it to Polygon...\n'
'Computed using the shoelace formula\n'
'65.52000000000001\n'
'\n'
'Testing a square\n'
'\n'
'I am a polygon\n'
'More precisely, I am a rectangle\n'
'Even more precisely, I am a square\n'
'4\n'
'I compute it myself!\n'
'49.0\n'

My Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# exercise_8_5.py

class Polygon:
def __init__(self, vertices):
self.vertices = vertices
self.nb_of_vertices = len(vertices)
print("I am a polygon")

def area(self):
"""In my calculation using the shoelace formula, I initially got an exact value, but since the expected output was a float, I used the method from the standard solution to match the expected output."""
print("Computed using the shoelace formula")
x, y = list(zip(*self.vertices))
return abs(sum(x[i] * y[-self.nb_of_vertices + i + 1]
for i in range(self.nb_of_vertices))
- sum(y[i] * x[-self.nb_of_vertices + i + 1]
for i in range(self.nb_of_vertices))) / 2

class Rectangle(Polygon):
def __init__(self, vertices):
super().__init__(vertices)
print("More precisely, I am a rectangle")

def area(self):
print("I could compute it more easily, but well, I leave it to Polygon...")
return super().area()

class Square(Rectangle):
def __init__(self, vertices):
super().__init__(vertices)
print("Even more precisely, I am a square")

def area(self):
print("I compute it myself!")
side_length = abs(self.vertices[0][0] - self.vertices[1][0])
return side_length ** 2

Standard Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Polygon:
def __init__(self, points):
print('I am a polygon')
self.points = points
self.nb_of_vertices = len(points)

def area(self):
print('Computed using the shoelace formula')
x, y = list(zip(*self.points))
return abs(sum(x[i] * y[-self.nb_of_vertices + i + 1]
for i in range(self.nb_of_vertices))
- sum(y[i] * x[-self.nb_of_vertices + i + 1]
for i in range(self.nb_of_vertices))) / 2

class Rectangle(Polygon):
def __init__(self, points):
super().__init__(points)
print('More precisely, I am a rectangle')

def area(self):
print('I could compute it more easily, but well, I leave it to Polygon...')
return super().area()


class Square(Rectangle):
def __init__(self, points):
super().__init__(points)
print('Even more precisely, I am a square')

def area(self):
print('I compute it myself!')
return max(abs(self.points[0][0] - self.points[1][0]),
abs(self.points[0][1] - self.points[1][1])) ** 2

Exercise 6

Problem Description

In this exercise, we need to implement a class named CircularTuple that represents an immutable sequence of elements. The class should:

  1. Allow circular indexing, meaning any index (positive or negative) will be wrapped around the length of the tuple using modulo arithmetic.

  2. Be immutable, which means any attempt to modify an element should raise an appropriate error.

The following Python script is used to test the functionality:

1
2
3
4
5
6
7
8
9
10
from exercise_8_6 import *

L = CircularTuple([3, 8, 14, 19])
print(len(L))
# Indices modulo the length of L
print(L[-20], L[-11], L[-5], L[-2], L[2], L[5], L[11], L[20], sep=', ')
try:
L[2] = 0
except CircularTupleError as e:
print(e)

The expected output from running the above code is:

1
2
3
'4\n'
'3, 8, 19, 14, 14, 8, 19, 3\n'
"We could make it work, but we shouldn't!\n"

My Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# exercise_8_6.py
from collections.abc import Sequence

class CircularTupleError(Exception):
"""Custom exception for CircularTuple immutability."""
def __init__(self, message="We could make it work, but we shouldn't!"):
super().__init__(message)

class CircularTuple(Sequence):
def __init__(self, data):
self._data = tuple(data) # Store the data as an immutable tuple

def __len__(self):
return len(self._data)

def __getitem__(self, index):
# Implement circular indexing using modulo operation
return self._data[index % len(self._data)]

def __setitem__(self, index, value):
# Prevent assignment and raise the custom error
raise CircularTupleError()

Standard Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from collections.abc import Sequence

class CircularTupleError(Exception):
pass

class CircularTuple(Sequence):
def __init__(self, L):
self.L = L

def __len__(self):
return len(self.L)

def __getitem__(self, i):
return self.L[i % len(self)]

def __setitem__(self, i, x):
raise CircularTupleError("We could make it work, but we shouldn't!")

Summary

  1. Exception Handling:

    • In my solution, CircularTupleError includes a default error message to provide more descriptive information when an assignment is attempted.

    • The standard solution defines CircularTupleError as a simple exception without additional customization.

  2. Both solutions implement circular indexing using modulo arithmetic (index % len(self)), which allows for indexing beyond the bounds of the tuple, wrapping around as needed.

  • Python
  • Answer
  • 9021

扫一扫,分享到微信

微信分享二维码
9021_TUT_9
9021_TUT_5
目录
  1. 1. Exercise 1
    1. 1.0.1. Problem Description
    2. 1.0.2. My Solution
    3. 1.0.3. Standard Solution
    4. 1.0.4. Summary
  • 2. Exercise 2
    1. 2.0.1. Problem Description
    2. 2.0.2. Standard Solution
    3. 2.0.3. Summary
  • 3. Exercise 3
    1. 3.0.1. Problem Description
    2. 3.0.2. My Solution
    3. 3.0.3. Standard Solution
    4. 3.0.4. Summary
  • 4. Exercise 4
    1. 4.0.1. Problem Description
    2. 4.0.2. My Solution
    3. 4.0.3. Standard Solution
  • 5. Exercise 5
    1. 5.0.1. Shoelace Formula
    2. 5.0.2. Example Calculation
    3. 5.0.3. Problem Description
    4. 5.0.4. My Solution
    5. 5.0.5. Standard Solution
  • 6. Exercise 6
    1. 6.0.1. Problem Description
    2. 6.0.2. My Solution
    3. 6.0.3. Standard Solution
    4. 6.0.4. Summary

  • 150 篇 | 131.7k
    次 | 人
    这里自动载入天数这里自动载入时分秒
    2022-2025 loong loong | 新南威尔士龙龙号