Given an input string s, find example of the longest substring containing no more than some prescribed maximum number of unique characters.
There are two different versions of the program:
substrings_set_and_list.pysubstrings_list_only.py(Note: passespy.testtests, but I think there may be something wrong, 20131023. Pending.)
With input string s and optional prescribed maximum number of unique characters max_uniq, the function:
find_substring(s, max_uniq = 2)
returns the longest substring (or the first-found longest substring, if there is more than one of this length) with two unique characters (or some other prescribed maximum number).
There is also a function
test_random(length = 100, max_uniq = 2)
that generates a random string of prescribed length and then asks whether more than the prescribed number of unique characters appears in the longest substring returned by find_substring(). If not, the longest substring is returned, alone; but if so, the number of unique characters actually found is also reported.
- Input:
1.
s: the string 1.max_uniq: (optional) the prescribed maximum number of unique characters. - Keep track of the longest acceptable running substring, and also the longest of any substring found. When the running substring exceeds the longest, replace the longest with it.
- Substrings are represented merely as their starting and ending indices in the original input string
s, rather than as string datatypes proper, which are slower to manipulate. The start- and end-indices of the running and longest substrings found are stored in. 1. start_i_longest 2. end_i_longest 3. start_i_running 4. end_i_running - Traverse the characters input string
sonce. - Each newly encountered character is added to a list
chars_found, so that there is a running list of these characters. - If a character is not new, it is added directly to the running substring and traversal of the input string continues. If a character is new, however, we must find out whether
max_uniqhas been exceeded or not. Keeping track of the number of unique characters in the running substring is done in two different ways in the two different versions of the program. 1. Insubstrings_set_and_list.py, newly encountered characters are also added to a setunique_running. 1. Insubstrings_list_only.py, the listchars_foundis used for this purpose. - Sets can be searched on average in constant time to determine whether a character is "newly encountered" or not; lists are searched on average in linear time. Since this search takes place once for each character traverse in the input string, potentially it can lead to the program running in quadratic time. Now, unless the input string and
max_uniqare both relatively large, the linear searching time does not cause problems. Timing results are reported later in this document. - When the number of unique characters in the current running substring exceeds
max_uniq, the current substring is abandoned and a new one is begun. This is done in slightly different ways in the two versions of the program. 1. Insubstrings_set_and_list.py, we remove fromunique_runningthat element added longest ago. But before proceeding we need to check whether that character reappears later in the next running substring. One way to do this would be to search the running substring itself for the character in question, but that is a linear-time operation that, within the original linear-time traversal of the input string, would give us quadratic time. Instead, we turn tochars_found, using a cursor (cursor) to step through it index by index, untilcursorpoints to an index not more thanmax_uniqfrom the current end of the list. Although stepping through the list is linear, it is done in parallel to stepping through the input string, = 𝓞(2n) ∈ 𝓞(n). 1. Insubstrings_list_only.py,cursoralone can be used withchars_found, and there is no need to remove any characters from any containers. - At the end of the traversal of
s, the longest substring found is reported.
Thirteen tests are supplied for each program. Change to directory test and run py.test.
Based on timings using sufficiently large input strings, strings_set_and_list.py appears to run in linear time regardless of the values of s and max_uniq, while substrings_list_only.py runs in considerably more than linear time with sufficiently large values of s and max_uniq.
Random string's length and time to complete:
length of s |
List only: time of loop | List only: Δs/ Δ time |
Set & list: time of loop | Set & list: Δs/ Δ time |
|
|---|---|---|---|---|---|
| 100 | 207 usec | — | 218 usec | — | |
| 1000 | 1.83 msec | 0.88 | 2.11 msec | 0.97 | |
| 10000 | 18.5 msec | 1.01 | 21.8 msec | 0.98 | |
| 100000 | 187 msec | 1.01 | 222 msec | 1.02 | |
| 1000000 | 1.92 sec | 1.03 | 2.18 sec | 0.98 |
length of s |
List only: time of loop | List only: Δs/ Δ time |
Set & list: time of loop | Set & list: Δs/ Δ time |
|
|---|---|---|---|---|---|
| 100 | 222 usec | — | 220 usec | — | |
| 1000 | 1.9 msec | 0.86 | 2.08 msec | 0.94 | |
| 10000 | 18.8 msec | 0.99 | 21.6 msec | 1.04 | |
| 100000 | 188 msec | 1.00 | 225 msec | 1.04 | |
| 1000000 | 1.93 sec | 1.03 | 2.3 sec | 1.02 |
length of s |
List only: time of loop | List only: Δs/ Δ time |
Set & list: time of loop | Set & list: Δs/ Δ time |
|
|---|---|---|---|---|---|
| 1000 | 3.28 msec | — | 1.81 msec | — | |
| 10000 | 126 msec | 3.84 | 17.9 msec | 0.99 | |
| 100000 | 11.5 sec | 9.13 | 180 msec | 1.01 | |
| 1000000 | 1.82e+03 sec | 158.27 | 1.81 sec | 1.00 |
length of s |
List only: time of loop | List only: Δs/ Δ time |
Set & list: time of loop | Set & list: Δs/ Δ time |
|
|---|---|---|---|---|---|
| 10000 | 129 msec | _ | 16.5 msec | _ | |
| 100000 | 11.9 sec | 9.22 | 172 msec | 1.04 |
Using a list only is slightly faster than using a list and a set until the values of s and max_uniq become relatively large.
python -m timeit -s '''
import substrings
import random
import string
max_uniq = 100''' '''
s = "".join([random.choice(string.ascii_uppercase) for i in range(10)])
result = substrings.find_substring(s, max_uniq)'''
[end]