Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions exercises/practice/sublist/.approaches/config.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
{
"introduction": {
"authors": ["safwansamsudeen"]
"authors": ["safwansamsudeen"],
"contributors": ["yrahcaz7"]
},
"approaches": [
{
"uuid": "db47397a-4551-49e8-8775-7e7aad79a38b",
"slug": "list-manipulation",
"title": "List manipulation",
"blurb": "Manipulate and check lists to solve the exercise",
"authors": ["safwansamsudeen"]
"authors": ["safwansamsudeen"],
"contributors": ["yrahcaz7"]
},
{
"uuid": "61366160-c859-4d16-9085-171428209b8d",
"slug": "using-strings",
"title": "Using strings",
"blurb": "Convert the lists to string and use string manipulation to solve the exercise",
"authors": ["safwansamsudeen"]
"authors": ["safwansamsudeen"],
"contributors": ["yrahcaz7"]
},
{
"uuid": "b2695c39-c1c7-47f0-bfcd-5e9703674bea",
"slug": "manual-loop",
"title": "Manual looping",
"blurb": "Manually track indexes while looping through the lists to solve the exercise",
"authors": ["yrahcaz7"]
},
{
"uuid": "a1eeaf9b-a9b3-421e-bfad-44f7e1575450",
"slug": "sort-lists",
"title": "Sorting lists",
"blurb": "Sort the lists to determine the shorter and longer ones to solve the exercise",
"authors": ["yrahcaz7"]
}
]
}
102 changes: 81 additions & 21 deletions exercises/practice/sublist/.approaches/introduction.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# Introduction
There are two broad ways to solve Sublist.

There are four broad ways to solve Sublist, though one of them ("using strings") is not recommended.

## General guidance
To write the code, you need to branch out (probably with `if`) into the four different possible conditions, and return the appropriate name of the category.

## Approach: list manipulation
To write the code, you need to branch out (probably with `if`) into the four different possible conditions, and return the appropriate category (`SUBLIST`, `SUPERLIST`, `EQUAL`, or `UNEQUAL`).

Note that you shouldn't return the category's value directly, as that would introduce [magic values][magic-values] into your code.

## Approach: List manipulation

The direct approach would be to manipulate and check the given lists to solve this.
This solution uses a helper function, which simplifies things, but the approach can be implemented without it.

```python
SUBLIST = 1
SUPERLIST = 2
EQUAL = 3
UNEQUAL = 4

def check_sub_sequences(list_one, list_two):
n1 = len(list_one)
n2 = len(list_two)
return any(list_two[i:i+n1] == list_one for i in range(n2 - n1 + 1))

def sublist(list_one, list_two):
if list_one == list_two:
return EQUAL
Expand All @@ -31,29 +31,89 @@ def sublist(list_one, list_two):

Read more on the [detail of this approach][approach-list-manipulation].

## Approach: using strings
Another seemingly clever approach is to convert the lists to strings and then
use the `in` operator to check for sub-sequences.
**However, this does not work.**
## Approach: Manual looping

This approach uses a helper function that manually loops through the lists to determine if the first list is a sublist of the second one.
This approach is the longest one by far, though it may be more comprehensible to some.

```python
def check_sub_sequences(list_one, list_two):
index_one = 0
index_two = 0
next_index_two = 1

while index_one < len(list_one) and index_two < len(list_two):
if list_one[index_one] == list_two[index_two]:
index_one += 1
else:
index_one = 0
index_two = next_index_two
next_index_two += 1
index_two += 1

if index_one == len(list_one):
if len(list_one) == len(list_two):
return EQUAL
return SUBLIST
return UNEQUAL

def sublist(list_one, list_two):
result = check_sub_sequences(list_one, list_two)
if result == UNEQUAL and check_sub_sequences(list_two, list_one) == SUBLIST:
result = SUPERLIST
return result
```

Learn more about the [details of this approach here][approach-manual-loop].

## Approach: Sorting lists

This approach uses the `sorted()` function to determine which list is shorter and which is longer.
Knowing this information, one can implement a simplified version of the list manipulation approach.

```python
SUBLIST = 1
SUPERLIST = 2
EQUAL = 3
UNEQUAL = 4
def sublist(list_one, list_two):
if list_one == list_two:
return EQUAL
if not list_one:
return SUBLIST
if not list_two:
return SUPERLIST

shorter, longer = sorted((list_one, list_two), key=len)

for index in range(len(longer) - len(shorter) + 1):
if longer[index : index + len(shorter)] == shorter:
return SUPERLIST if longer is list_one else SUBLIST

return UNEQUAL
```

Read more on the [detail of this approach][approach-sort-lists].

## Approach: Using strings

Another seemingly clever approach is to convert the lists to strings and then use the `in` operator to check for sub-sequences.
**However, this does not work.**

```python
def sublist(list_one, list_two):
list_one_check = (str(list_one).strip("[]") + ",")
list_two_check = (str(list_two).strip("[]") + ",")
list_one_check = str(list_one).strip("[]") + ","
list_two_check = str(list_two).strip("[]") + ","

if list_one_check == list_two_check:
return EQUAL
elif list_one_check in list_two_check:
if list_one_check in list_two_check:
return SUBLIST
elif list_two_check in list_one_check:
if list_two_check in list_one_check:
return SUPERLIST
return UNEQUAL
```

To understand more about this approach and **why it fails**, [read here][approach-using-strings].

[magic-values]: https://stackoverflow.com/questions/47882/what-is-a-magic-number-and-why-is-it-bad
[approach-list-manipulation]: https://exercism.org/tracks/python/exercises/sublist/approaches/list-manipulation
[approach-manual-loop]: https://exercism.org/tracks/python/exercises/sublist/approaches/manual-loop
[approach-sort-lists]: https://exercism.org/tracks/python/exercises/sublist/approaches/sort-lists
[approach-using-strings]: https://exercism.org/tracks/python/exercises/sublist/approaches/using-strings
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# List manipulation

The direct approach would be to manipulate and check the given lists to solve this.
This solution uses a helper function, which simplifies things, but the approach can be implemented without it.

Expand All @@ -12,7 +13,7 @@ def check_sub_sequences(list_one, list_two):
n1 = len(list_one)
n2 = len(list_two)
return any(list_two[i:i+n1] == list_one for i in range(n2 - n1 + 1))

def sublist(list_one, list_two):
if list_one == list_two:
return EQUAL
Expand All @@ -23,16 +24,13 @@ def sublist(list_one, list_two):
return UNEQUAL
```

We first check for equality using the `==` operator, if so, then we return `EQUAL`.
A common way to do this differently would be to return `1` directly, but this is better practice as we [remove magic values][magic values].
We first check for equality using the `==` operator, and if the lists are equal, then we return `EQUAL`.
After that, we call `check_sub_sequences()`, passing in `list_one` and `list_two`.

After that we call `check_sub_sequences` passing in `list_one` and `list_two`.
In the helper function, we check if `any` of the possible sub-sequences in `list_two` of length `n1` (the length of the first list) are equal to the first list.
If so, then we conclude that `list_one` is a `SUBLIST` of `list_two`.
If so, then we conclude that `list_one` is a `SUBLIST` of `list_two`.

To find whether `list_one` is a `SUPERLIST` of `list_two`, we just reverse this process - pass in the lists in the opposite order.
Thus, we check if `any` of the possible sub-sequences in `list_one` of length `n2` (the length of the second list) are equal to the second list.

If none of the above conditions are true, we conclude that the two lists are unequal.

[magic values]: https://stackoverflow.com/questions/47882/what-is-a-magic-number-and-why-is-it-bad
48 changes: 48 additions & 0 deletions exercises/practice/sublist/.approaches/manual-loop/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Manual looping

This approach uses a helper function that manually loops through the lists to determine if the first list is a sublist of the second one.
This approach is the longest one by far, though it may be more comprehensible to some.

```python
SUBLIST = 1
SUPERLIST = 2
EQUAL = 3
UNEQUAL = 4

def check_sub_sequences(list_one, list_two):
index_one = 0
index_two = 0
next_index_two = 1

while index_one < len(list_one) and index_two < len(list_two):
if list_one[index_one] == list_two[index_two]:
index_one += 1
else:
index_one = 0
index_two = next_index_two
next_index_two += 1
index_two += 1

if index_one == len(list_one):
if len(list_one) == len(list_two):
return EQUAL
return SUBLIST
return UNEQUAL

def sublist(list_one, list_two):
result = check_sub_sequences(list_one, list_two)
if result == UNEQUAL and check_sub_sequences(list_two, list_one) == SUBLIST:
result = SUPERLIST
return result
```

In this approach, the first thing `sublist()` does is call the helper function.
That function then loops through the lists, keeping track of an index for both lists so it can test all necessary combinations to determine if `list_one` is a sublist of `list_two`.

However, the helper function only determines if `list_one` is equal to or a sublist of `list_two`, not if `list_one` is a superlist of `list_two`.
That is why if the helper function returns `UNEQUAL`, `sublist()` needs to make sure that it isn't acutally a superlist.

`sublist()` does this by calling the helper function with its arguments reversed: `check_sub_sequences(list_two, list_one)`.
If the result is `SUBLIST`, that means `list_two` is a sublist of `list_one`, thus `list_one` must be a superlist of `list_two`.

Thus all possibilities are covered, and `sublist()` returns the result.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
while index_one < len(list_one) and index_two < len(list_two):
if list_one[index_one] == list_two[index_two]:
index_one += 1
else:
index_one = 0
index_two = next_index_two
next_index_two += 1
index_two += 1
44 changes: 44 additions & 0 deletions exercises/practice/sublist/.approaches/sort-lists/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Sorting lists

This approach uses the `sorted()` function to determine which list is shorter and which is longer.
Knowing this information, one can implement a simplified version of the [list manipulation approach][approach-list-manipulation].

```python
SUBLIST = 1
SUPERLIST = 2
EQUAL = 3
UNEQUAL = 4

def sublist(list_one, list_two):
if list_one == list_two:
return EQUAL
if not list_one:
return SUBLIST
if not list_two:
return SUPERLIST

shorter, longer = sorted((list_one, list_two), key=len)

for index in range(len(longer) - len(shorter) + 1):
if longer[index : index + len(shorter)] == shorter:
return SUPERLIST if longer is list_one else SUBLIST

return UNEQUAL
```

Here, the case of the lists being equal is checked first.
Then the special cases of empty lists are handled, returning `SUBLIST` or `SUPERLIST` as necessary.

Once those simple cases are out of the way, the `sorted()` function is used with the keyword argument `key` set to the `len()` function.
This makes `sorted()` sort the items according to their length.

Once `sorted()` does its work, we use multiple assignment to unpack the results into the `shorter` and `longer` variables.
Then, for each slice of length `len(shorter)` in `longer`, we test if that slice is equal to `shorter`.

If we find such a slice, that means `shorter` is a sublist of `longer`.
Then we use a [conditional expression][conditional-expression] along with the `is` operator to return `SUBLIST` or `SUPERLIST` depending on which of the original lists is `longer`.

If we do not find such a slice, we can eliminate `SUBLIST` and `SUPERLIST` from the possible categories, thus the two lists must be `UNEQUAL`.

[approach-list-manipulation]: https://exercism.org/tracks/python/exercises/sublist/approaches/list-manipulation
[conditional-expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions
8 changes: 8 additions & 0 deletions exercises/practice/sublist/.approaches/sort-lists/snippet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
def sublist(list_one, list_two):
...
shorter, longer = sorted((list_one, list_two), key=len)

for index in range(len(longer) - len(shorter) + 1):
if longer[index : index + len(shorter)] == shorter:
return SUPERLIST if longer is list_one else SUBLIST
return UNEQUAL
43 changes: 25 additions & 18 deletions exercises/practice/sublist/.approaches/using-strings/content.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# Using strings

~~~~exercism/caution
**This approach does not work, and this document exists to explain that.**
**This approach does not work, and this document exists to explain that.**
Please do not use it in your code.
~~~~

Another seemingly clever solution is to convert the lists to strings and then
use the `in` operator to check for sub-sequences.
Note that this approach, even if it worked, is not as performant as the
previous one.
Another seemingly clever solution is to convert the lists to strings and then use the `in` operator to check for sub-sequences.

```python
SUBLIST = 1
SUPERLIST = 2
Expand All @@ -20,28 +19,36 @@ def sublist(list_one, list_two):

if list_one_check == list_two_check:
return EQUAL
elif list_one_check in list_two_check:
if list_one_check in list_two_check:
return SUBLIST
elif list_two_check in list_one_check:
if list_two_check in list_one_check:
return SUPERLIST
return UNEQUAL
```

Let's parse the code to see what it does.
In this approach, we convert the lists to strings, so `[1, 2, 3]` becomes `"[1, 2, 3]"`, remove the brackets `"1, 2, 3"`, and add a comma `"1, 2, 3,"`.
In this approach, we convert the lists to strings, so `[1, 2, 3]` becomes `"[1, 2, 3]"`, remove the brackets `"1, 2, 3"`, and add a comma `"1, 2, 3,"`.
We check equality and then use the `in` operator to check for `SUBLIST` or `SUPERLIST`, and finally return `UNEQUAL`.

We add a comma because, say, we call `sublist` with `[1, 2]` and `[1, 22]`. `"1, 2" in "1, 22"` evaluates to `True`, so
the **function would wrongly mark it as `SUBLIST`**.
We add a comma because, say, we call `sublist` with `[1, 2]` and `[1, 22]`. `"1, 2" in "1, 22"` evaluates to `True`, so the **function would wrongly mark it as `SUBLIST`**.

This case can be handled by changing the code like this:

```python
list_one_check = str(list_one).strip("[]") + ","
list_two_check = str(list_two).strip("[]") + ","
```

Yet, even though this code would pass all of the tests in the Exercism test suite, it would still fail in some cases.
For example, if we call `sublist` with `[1, 2]` and `[5, "1, 2,", 7]`, the function would return `SUBLIST` when it should actually return `UNEQUAL`.

This could be avoided by changing the code to use a separator that isn't the default one:

This test can be overridden by changing the code like this:
```python
list_one_check = str(list_one).strip("[]") + ','
list_two_check = str(list_two).strip("[]") + ','
list_one_check = "|".join(str(item) for item in list_one) + "|"
list_two_check = "|".join(str(item) for item in list_two) + "|"
```
Yet, the test case (which doesn't exist in the Exercism test suite) `["1", "2"]` and `["5", "'1', '2',", "7"]` would
fail.

Students can add any arbitrary string into the representation to try to "defeat" this test - `list_one_check = str
(list_one) + TOKEN`. The test suite currently test `TOKEN = ''`, but not others.
However, this only avoids the (theoretical) test and does not fix the solution. For example, a test with the inputs `[1, 2]` and `[5, "1|2|", 7]` would now fail.

[gen-exp]: https://www.programiz.com/python-programming/generator
No matter what separator is chosen, there will always be at least one input for which the function will return the wrong result. **This is why no approach that converts the lists to strings can ever be correct for all possible inputs.**
Loading
Loading