How to retrieve partial matches from a list of strings?
Instead of returning the result of the any()
function, you can use a for-loop to look for the string instead:
def find_match(string_list, wanted):
for string in string_list:
if string.startswith(wanted):
return string
return None
>>> find_match(['ones', 'twos', 'threes'], "three")
'threes'
startswith
andin
, return a Boolean.- The
in
operator is a test of membership. - This can be performed with a
list-comprehension
orfilter
. - Using a
list-comprehension
, within
, is the fastest implementation tested. - If case is not an issue, consider mapping all the words to lowercase.
l = list(map(str.lower, l))
.
- Tested with python 3.10.0
filter
:
- Using
filter
creates afilter
object, solist()
is used to show all the matching values in alist
.
l = ['ones', 'twos', 'threes']
wanted = 'three'
# using startswith
result = list(filter(lambda x: x.startswith(wanted), l))
# using in
result = list(filter(lambda x: wanted in x, l))
print(result)
[out]:
['threes']
list-comprehension
l = ['ones', 'twos', 'threes']
wanted = 'three'
# using startswith
result = [v for v in l if v.startswith(wanted)]
# using in
result = [v for v in l if wanted in v]
print(result)
[out]:
['threes']
Which implementation is faster?
- Tested in Jupyter Lab using the
words
corpus fromnltk v3.6.5
, which has 236736 words - Words with
'three'
['three', 'threefold', 'threefolded', 'threefoldedness', 'threefoldly', 'threefoldness', 'threeling', 'threeness', 'threepence', 'threepenny', 'threepennyworth', 'threescore', 'threesome']
from nltk.corpus import words
%timeit list(filter(lambda x: x.startswith(wanted), words.words()))
[out]:
64.8 ms ± 856 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit list(filter(lambda x: wanted in x, words.words()))
[out]:
54.8 ms ± 528 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit [v for v in words.words() if v.startswith(wanted)]
[out]:
57.5 ms ± 634 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit [v for v in words.words() if wanted in v]
[out]:
50.2 ms ± 791 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
A simple, direct answer:
test_list = ['one', 'two','threefour']
r = [s for s in test_list if s.startswith('three')]
print(r[0] if r else 'nomatch')
Result:
threefour
Not sure what you want to do in the non-matching case. r[0]
is exactly what you asked for if there is a match, but it's undefined if there is no match. The print
deals with this, but you may want to do so differently.
I'd say the most closely related solution would be to use next
instead of any
:
>>> next((s for s in l if s.startswith(wanted)), 'mydefault')
'threes'
>>> next((s for s in l if s.startswith('blarg')), 'mydefault')
'mydefault'
Just like any
, it stops the search as soon as it found a match, and only takes O(1) space. Unlike the list comprehension solutions, which always process the whole list and take O(n) space.
Ooh, alternatively just use any
as is but remember the last checked element:
>>> if any((match := s).startswith(wanted) for s in l):
print(match)
threes
>>> if any((match := s).startswith('blarg') for s in l):
print(match)
>>>
Another variation, only assign the matching element:
>>> if any(s.startswith(wanted) and (match := s) for s in l):
print(match)
threes
(Might want to include something like or True
if a matching s
could be the empty string.)