Skip to content

Commit

Permalink
Additional grammar and spelling edits
Browse files Browse the repository at this point in the history
  • Loading branch information
BethanyG committed Aug 1, 2024
1 parent 7d1f5bd commit d42bbf5
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ Recursion is not highly optimised in Python and there is no tail call optimizati
## Which approach to use

For short, well-defined input strings such as those currently in the test file, repeated-substitution allows a passing solution in very few lines of code.
But as input grows, this method becomes could become less and less performant, due to the multiple passes and changes needed to determine matches.
But as input grows, this method could become less and less performant, due to the multiple passes and changes needed to determine matches.

The single-pass strategy of the stack-match approach allows for stream processing, scales linearly (_`O(n)` time complexity_) with text length, and will remain performant for very large inputs.

Examining the community solutions published for this exercise, it is clear that many programmers prefer the stack-match method, avoiding the repeated string copying of the substitution approach.
Examining the community solutions published for this exercise, it is clear that many programmers prefer the stack-match method which avoids the repeated string copying of the substitution approach.

Thus it is interesting, and perhaps humbling, to note that repeated-substitution is *at least* as fast in benchmarking, even with large (>30 kB) input strings!
Thus it is interesting and perhaps humbling to note that repeated-substitution is **_at least_** as fast in benchmarking, even with large (>30 kB) input strings!

See the [performance article][article-performance] for more details.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def is_paired(text):

In this approach, the steps are:

1. Remove all non-bracket characters from the input string.
1. Remove all non-bracket characters from the input string (_as done through the filter clause in the list-comprehension above_).
2. Iteratively remove all remaining bracket pairs: this reduces nesting in the string from the inside outwards.
3. Test for a now empty string, meaning all brackets have been paired.

Expand All @@ -30,27 +30,28 @@ def is_paired(input_string):
return not symbols
```

The second solution above does essentially the same thing as the initial approach, but uses a generator expression assigned with a [walrus operator][walrus] `:=` (_introduced in Python 3.8_).
The second solution above does essentially the same thing as the initial approach, but uses a generator expression assigned with a [walrus operator][walrus] `:=` (_introduced in Python 3.8_) in the `while-loop` test.


## Variation 2: Regex
## Variation 2: Regex Substitution in a While Loop

Regex enthusiasts can modify the previous approach, using `re.sub()` instead of `string.replace()`:
Regex enthusiasts can modify the previous approach, using `re.sub()` instead of `string.replace()` in the `while-loop` test:

```python
import re

def is_paired(str_: str) -> bool:
str_ = re.sub(r'[^{}\[\]()]', '', str_)
while str_ != (str_ := re.sub(r'{\}|\[]|\(\)', '', str_)):
pass
return not bool(str_)
def is_paired(text: str) -> bool:
text = re.sub(r'[^{}\[\]()]', '', text)
while text != (text := re.sub(r'{\}|\[]|\(\)', '', text)):
continue
return not bool(text)
```

## Variation 3: Regex and Recursion

## Variation 3: Regex Substitution and Recursion

It is possible to combine regexes & recursion in the same solution, though not everyone would view this as idiomatic Python:

It is possible to combine `re.sub()` and recursion in the same solution, though not everyone would view this as idiomatic Python:


```python
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
def is_paired(text):
text = "".join([element for element in text if element in "()[]{}"])
text = "".join(element for element in text if element in "()[]{}")
while "()" in text or "[]" in text or "{}" in text:
text = text.replace("()","").replace("[]", "").replace("{}","")
return not text
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ def is_paired(input_string):
return not stack
```

The point of this approach is to maintain a context of which bracket sets are currently open:
The point of this approach is to maintain a context of which bracket sets are currently "open":

- If a left bracket is found, push it onto the stack.
- If a right bracket is found, and it pairs with the top item on the stack, pop the bracket off the stack and continue.
- If there is a mismatch, for example `'['` with `'}'` or no left bracket on the stack, the code can immediately terminate and return `False`.
- If a left bracket is found, push it onto the stack (_append it to the `list`_).
- If a right bracket is found, **and** it pairs with the last item placed on the stack, pop the bracket off the stack and continue.
- If there is a mismatch, for example `'['` with `'}'` or there is no left bracket on the stack, the code can immediately terminate and return `False`.
- When all the input text is processed, determine if the stack is empty, meaning all left brackets were matched.

In Python, a `list` is a good implementation of a stack: it has `list.append()` (equivalent to a "push") and `lsit.pop()` methods built in.
In Python, a [`list`][concept:python/lists]() is a good implementation of a stack: it has [`list.append()`][list-append] (_equivalent to a "push"_) and [`lsit.pop()`][list-pop] methods built in.

Some solutions use `collections.deque()` as an alternative implementation, though this has no clear advantage (_since the code only uses appends to the right-hand side_) and near-identical runtime performance.
Some solutions use [`collections.deque()`][collections-deque] as an alternative implementation, though this has no clear advantage (_since the code only uses appends to the right-hand side_) and near-identical runtime performance.

The code above searches `bracket_map` for left brackets, meaning the _keys_ of the dictionary, and the line `bracket_map.values()` is used to search for the right brackets.
The default iteration for a dictionary is over the _keys_, so the code above uses a plain `bracket_map` to search for left brackets, while `bracket_map.values()` is used to search for right brackets.

Other solutions created two sets of left and right brackets explicitly, or searched a string representation:

Expand All @@ -43,3 +43,7 @@ To be more explicit, we could alternatively use an equality:
```python
return stack == []
```

[list-append]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists
[list-pop]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists
[collections-deque]: https://docs.python.org/3/library/collections.html#collections.deque

0 comments on commit d42bbf5

Please sign in to comment.