3
\$\begingroup\$

Recently I built a Usenet newsreader in Python and for that I built a keyword search which supports AND and OR functionalities (e.g., python AND django should bring up articles containing both. I defined a find function for this which takes a keyword query and a string and returns a bool signifying whether the string qualifies (i.e., for the above example, the string qualifies if it contains both 'python' and 'django').

Here's the snippet:

search = 'python AND django OR ruby AND rails'
query = search.split(' OR ')
query = [phrase.split(' AND ') for phrase in query]

def find(query, string):
    is_there = False
    for chunk in query: #OR
        chunk_qualified = False
        for word in chunk: #AND
            if word not in string:
                break
        else:
            chunk_qualified = True

        if chunk_qualified:
            is_there = True
            break

    return is_there

It works, but can I do it in a better manner (less code, more efficient)?

\$\endgroup\$
3
\$\begingroup\$

You can use the builtin function all() here to check phrases in a subquery are part of the string passed.

def get_sub_queries(query):
    for subquery in query.split(' OR '):
        yield subquery


def get_phrases(subquery):
    for phrase in subquery.split(' AND '):
        yield phrase


def find(query, string):
    for subquery in get_sub_queries(query):
        if all(phrase in string for phrase in get_phrases(subquery)):
            return True
    return False

Try to use better variable names(I know it's hard though :-)).

Apart from these I have broken your code into 3 functions now.

  1. Contains logic to split queries to subqueries
  2. Contains logic to split subquery to get phrases
  3. Actual find() functionality

The reason behind this is that if the logic related to anyone of them changes later on then it won't complicate your search functionality and you can write separate tests for each of the three functionalities.


If you're on Python 3.3+ then use could also use yield from.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for simplifying! But I feel that all() needs all the boolean values you create for each phase in subquery. Isn't it better to just find a single False and break out, since even one False means our AND check is unsatisfied? Or maybe you've used generators for exactly that? \$\endgroup\$
    – Sam Chats
    Jun 27 '17 at 7:59
  • 1
    \$\begingroup\$ Not really, all() short-circuits at first falsy value. \$\endgroup\$ Jun 27 '17 at 8:01
  • \$\begingroup\$ Cool. Also, I think your code is more memory-friendly because of your use of generators. Awesome! \$\endgroup\$
    – Sam Chats
    Jun 27 '17 at 8:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.