Python - If element is in list, return another unique element

(This is Python 2.7.10)

I am traversing through a list of sublists that like this:

for x in words:
    for subs in bigLsts:
        if x in subs:
            print() # Here, I want to print a different word in subs that is not the same word

bigLsts is a list of word lists formatted like this:

bigLsts = [
   ["herro", "hewwo", "holas"],
   ["woah", "woahwa", "whatda"]
]

If x is in the subs, how can I print another word in the sublist that is not the same word as x? So, if x == "hewwo" how can I print either "herro" or "holas" but not "hewwo"

I have some solutions like generating a random number that does not include the index of that element, but solutions like that feel a bit clunky to me. Is there any cleaner solution?

2 answers

  • answered 2022-05-03 00:59 oda

    In your inner loop, you can consider using the following try statement (to replace the if x in subs):

    from random import randrange
    try:
         idx = subs.index(x)
         while True:
              a = randrange(0, len(subs))
              if a != idx:
                   break
         print(subs[a])
    except ValueError:
         pass
    

    The try suite tries to find the index in subs with corresponding element equal to the word x.

    • In the event that x is not found, a ValueError is raised and we quietly ignore it.
    • In the event that x is found, idx is assigned the corresponding index. Then, we assign a random integer in [0, len(subs) - 1] (i.e. the set of valid indices for subs) to a until a is not equal to idx. Once a is not equal to idx, we break out of the loop and print subs[a].

    Drawing inspiration from @Mad Physicist, it would be better if the try suite were replaced with

    a = randrange(0, len(subs) - 1)
    a += a >= subs.index(x)
    

    which assigns a to a random integer in [0, len(subs) - 1) and then increments a by 1 if a >= idx is true; otherwise (i.e. if a < idx is true), a is unchanged. If this change is made, the try statement becomes

    try:
         a = randrange(0, len(subs) - 1)
         a += a >= subs.index(x)
         print(subs[a])
    except ValueError:
         pass
    

    A slight optimization would be to simply catch all exceptions with except: (replacing except ValueError:). See, for example, this answer. The gist of it is that if an except clause has an expression (here, ValueError), it needs to be evaluated and tested against the raised exception from the try suite. An expression-less except clause (as in except:) does not have to do this additional step. I would not recommend making catching all exceptions a habit, however -- it is context-specific.

    Example Session

    Suppose that

    words = ["green", "eggs", "and", "ham", "hewwo", "woahwa"]
    bigLsts = [['herro', 'hewwo', 'holas'], ['woah', 'woahwa', 'whatda']]
    

    A session might yield output

    herro
    whatda
    

  • answered 2022-05-03 01:10 Mad Physicist

    Generating a random number that is not the index of the element does not have to be clunky. One simple way to do it is to generate a number that is in the range [0, len(subs) - 2] and add one if the number is greater than or equal to the index you want to avoid. You can use the fact that python booleans are a subtype of integers to make the computation very simple:

    ind = random.randrange(0, len(subs) - 1)
    ind += ind >= subs.index(x)
    print(subs[ind])
    

    That being said, you can use an even simpler formulation, courtesy of this unrelated answer:

    min((i for i in subs if i != x), key=lambda x: random.random())
    

    The idea is to take the element with the minimum uniformly randomly generated key. The generator automatically handles skipping the element you want to skip without doing an index lookup or ever mentioning indices at all.

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum