2

I am looping over an itertools.permutation object and for efficiency, the loop breaks once the item is found. I understand that because I used the for loop I am unable to catch the StopIteration error when the generator is exhausted and the item is not found.

So far I have implemented a found flag, but it seems kinda hacky.

from itertools import permutations

def verify_string(name, compare):
    name = name.lower().split()
    compare = compare.lower().split()
    p = permutations(compare, len(compare))
    found = 0

    for i in p:
        if list(i) == name:
            print(f'Found: {compare} in {name}')
            found = 1
            break

    if not found:
        print('Not Found')

name = 'guido van rossum'
compare = 'van guido rossum'

verify_string(name, compare)
>>Found: ['van', 'guido', 'rossum'] in ['guido', 'van', 'rossum']

I also thought of checking if not next(p, '') to see if it is exhausted but the item might be found in the generator's last item and will return True anyways.

From a Pythonic view, is there a way to manage looping over a generator that stops and return when an item is found and returns a different value only when the generator is exhausted.

1
  • As a side comment, it is better to cast name as a tuple and test for i == name. This way, you can avoid creating a redundant copy of each permutation. Commented Dec 2, 2018 at 5:58

3 Answers 3

6

The Pythonic way is to use a for-else loop.

from itertools import permutations

def verify_string(name, compare):
    name = name.lower().split()
    compare = compare.lower().split()    
    for i in permutations(compare, len(compare)):
        if list(i) == name:
            print(f'Found: {compare} in {name}')
            break
    else:  # Raymond Hettinger calls this "if no break" condition
        # If we did not break out of the "for loop", execute this.
        print('Not Found')

name = 'guido van rossum'
compare = 'van guido rossum'

verify_string(name, compare)
>>> Found: ['van', 'guido', 'rossum'] in ['guido', 'van', 'rossum']

Edit

My initial reply was to how to avoid using the found flag and I wasn't paying attention to what you were actually trying to do. The for-else construct is also a very useful and often neglected language construct that I wanted to highlight.

However, if you just want to check if the set of string is a permutation of another, then why not just

match = sorted(name.lower().split()) == sorted(compare.lower().split())

This avoids the need to go through all possible permutation of the words in the string.

Sign up to request clarification or add additional context in comments.

9 Comments

Lol that is so true, that was my earlier code without the break and it kept printing because of that. Thanks anyways.
This is nice but I would prefer checking if tuple(name) in permutations(compare, len(compare)) as opposed to the for loop.
Thanks, I was not paying attention to the string permutation problem too. Rather just trying to understand the best way to loop over a generator finding for values, you answered the question clearly with the for-else construct which I misunderstood earlier.
@BernardL that is my take too. Also, we can consider replacing sorted with set or Counter for better time complexity, at least on paper.
@YakymPirozhenko go ahead and post your updated answer with that and I will gladly +1 for a workable solution.
|
1

From a Pythonic view, is there a way to manage looping over a generator that stops and return when an item is found and returns a different value only when the generator is exhausted.

As you don't actually return anything - if you fix that part of your function, you'll return None if there are no matches:

from itertools import permutations

def verify_string(name, compare):
    name = name.lower().split()
    compare = compare.lower().split()    
    for i in permutations(compare, len(compare)):
        if list(i) == name:
            return True

name = 'guido van rossum'
compare = 'van guido rossum'

if verify_string(name, compare):
  print(f'Found: {compare} in {name}')
else:
  print('Not found')

1 Comment

That's true, I was just juggling with the thoughts of whether to manage it in the function or not.
1

There are two approaches. One is to test a membership of an iterator:

from itertools import permutations
from collections import Counter


def verify_string(name, compare):
    if tuple(compare.lower().split()) in permutations(name.lower().split()):
        print(f"Found: {compare} in {name}")
    else:
        print("No match found")

We can also avoid the function altogether with a ternary operator:

val if val in itr else other_val

If the iterator at hand happens to be the permutations, then we can get away without considering every permutation and count words instead.

def verify_string_fast(name, compare):
    if not Counter(compare.lower.split()) - Counter(name.lower.split()):
        print(f"Found: {compare} in {name}")
    else:
        print("No match found")

More generally, if we have a test condition other than membership, we can do:

def verify_general(val, itr):
    if any(compare(val, x) for x in itr):
        print("Success")
    else:
        print("Failure")

Note on membership in an iterator. According to the official documentation,

For user-defined classes which do not define contains() but do define iter(), x in y is True if some value z with x == z is produced while iterating over y. If an exception is raised during the iteration, it is as if in raised that exception.

Since permutations has an __iter__ method, testing for membership is possible.

For example,

assert (3, 2, 1) in permutations([1, 2, 3])

12 Comments

The results do not really matter for this question, as long as the generator stops when the criteria are matched. And how would I use itertools.chain for my problem statement?
I think that I was focusing on the details of the example too much. See if the first snipped added in the last edit does the job.
Do you mean to convert, list(permutations(name.lower.split()))? Tested and membership testing with generators does not work. For the Counter method, why are you doing an equality test? And where did you define the function compare in verify_general.
@BernardL there was a typo in my code; permutations(name.lower.split() should be permutations(name.lower().split(). For the details of how testing iterator membership works, see docs.python.org/3/reference/… I did not define compare, it's meant to be pseudocode for a boolean binary predicate.
The inequality check for Counter comparison comes into play if compare has fewer words than name, but every word in compare is a word in name.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.