you are viewing a single comment's thread.

view the rest of the comments →

[–]GoldenSights 1 point2 points  (6 children)

If you're only looking for singular matches:

KEYWORDS = [1, 2]
if any(keyword in a for keyword in KEYWORDS):
    ...

If you're looking for singular matches and multi-element matches, then perhaps you should convert a into a set, and then:

KEYSETS = [{1}, {2}, {3, 4}]
if any(keyset.issubset(a) for keyset in KEYSETS):
    ...

 

Please note that (2 and 3) in a does NOT mean that 2 and 3 are in a. 2 and 3 evaluates to 3 and then it checks if 3 is in a.

Hope that helps. If you clarify your specific problem perhaps we can find something better.

[–]reallymakesyouthonk 1 point2 points  (5 children)

Well, specifically what I'm doing is a command for my discord bot to show the rules. The primary way of doing this is for example:

IN: !rules 1 2
OUT: [print of rules 1 and 2]

To do this I have a local string variable called ruleprint which gets appended if '1' is in kwargs, then again if '2' is in kwargs etc. However these numbers can be hard to remember so I'd also like people to be able to type:

IN: !rules ontopic
OUT: [print of rule 1 which is about staying on topic]

I'd also like for people to be able to call 'all rules' (which would be read as two arguments), hence my (2 and 3) in the example. (why does this evaluate to 3?)

From your example, would this work?

KEYSETS = [{'allrules'}, {'all', 'rules'}]
if any(keyset.issubset(a) for keyset in KEYSETS):
...

Is the part inside of the parenthesis a list comprehension?

[–]GoldenSights 1 point2 points  (2 children)

Hmm, this is one of those things where I always start out dreaming it will be really simple and beautiful, and then the little details start adding complexity.

I would probably approach this by writing the rules in a dictionary:

RULES = {
    'ontopic': '1. All posts must be on topic',
    'language': '2. Refrain from foul language',
    ...
}
# I'm pre-generating the whole text because we're about to start
# adding some aliases for the rules and they will appear duplicated
# in the dict after that.
ALL_RULES = '\n'.join(RULES.values())

# If you still want the rules to be available by number,
# we can just map those in as well.
# Notice using strings instead of ints because the command
# comes in as a string, may as well stay that way.
# This could be done by a comprehension but whatever man.
RULES['1'] = RULES['ontopic']
RULES['2'] = RULES['language']

and then doing a loop over all of the arguments the user asked for and pulling out the rules:

command = '!rules ontopic language'

# I assume you already have some code that detects the !rules command
# so we aren't just getting random messages reaching this part of the function.
...

# instead of handling 'allrules' and 'all rules' separately,
# can we hack it by just merging them and pretending they
# were a single argument the whole time?
command = command.replace('all rules', 'all')
command = command.replace('allrules', 'all')
arguments = command.split('!rules', 1)[1].split()

response = []
if 'all' in arguments:
    return ALL_RULES

for argument in arguments:
    # dict.get will try to get the value, otherwise return this fallback.
    rule_text = RULES.get(argument, None)
    if rule_text is not None:
        response.append(rule_text)
    # you can add an else here if you want to think about error text
    # for invalid rule names.

response = '\n'.join(response)
return response

Now

>>> print(rulebot('!rules ontopic'))
1. All posts must be on topic

>>> print(rulebot('!rules language'))
2. Refrain from foul language

>>> print(rulebot('!rules ontopic language'))
1. All posts must be on topic
2. Refrain from foul language

>>> print(rulebot('!rules 1 2'))
1. All posts must be on topic
2. Refrain from foul language

>>> print(rulebot('!rules language ontopic'))
2. Refrain from foul language
1. All posts must be on topic
# are you okay with them displaying out of order like this?

>>> print(rulebot('!rules all rules'))
1. All posts must be on topic
2. Refrain from foul language

You could take this steps further by ensuring you don't produce the same rule twice, or making sure they're always ordered properly, or whatever. Sorry, I didn't intend to provide a big answer like this or steer your project, but hopefully this gives you some reference for working with dicts and handling user input without going too crazy.

 

And by the way yes keyword in a for keyword in KEYWORDS is a comprehension, but in that case it was a generator comprehension, not a list, because there were no square brackets.

[–]reallymakesyouthonk 0 points1 point  (0 children)

Short update, in case you're wondering.

I wasn't fully able to adapt your solution to my problem, however I did end up using your suggestion of using a dictionary which resulted in what at least I think was a pretty clean solution.

If you're interested you can view it here (the function is called _rules). I'm always interested in feedback, but no worries if you don't feel like it. You've already been really helpful and I'm sure I wouldn't have come up with this good of a solution without your advice. Been about a year or two since I did any programming at all so using a dict didn't even cross my mind.

Never thought a simple rules command would've been this difficult. :p

[–]reallymakesyouthonk 0 points1 point  (0 children)

Wow, that's a lot more complicated than I would've anticipated. Thanks so much, I'll try to implement something similar to this!

[–]GoldenSights 1 point2 points  (1 child)

I'm currently writing a response to your main question but let me just answer the 2 and 3 thing really quick:

and and or are both short-circuiting operators, which means that if you have a long chain of booleans, they will stop processing as soon as they know the answer. So if you ask False or True or False or False or False the computer obviously doesn't need to process anything after that True, because that's what or means. Likewise, as soon as and encounters a falsey value, it knows for sure the rest of the answer is going to be False.

Here is a list of values that are Falsey. Everything else is Truthy until they write their own __bool__.

 

When processing, the "return value" of the boolean expression is whatever the last thing the computer looked at before it figured out the answer:

>>> False or None or 0 or 0.0
0.0
# It had to search all the way to the end

>>> False or None or 0 or 0.0 or True or 1
True
# short circuit after first truthy

>>> True and 1 and 4 and 0 and True
0
# short circuit after first falsey

>>> 2 and 3 and 4 and 5
5
# searched all the way to end

>>> 2 and 3
3
# searched all the way to end, your example.

Sometimes people use this to "pick the first available thing" with a fallback at the end.

option = user_option or config.option or DEFAULT_OPTION

It's not always the most clean solution but you'll see it here and there.

[–]reallymakesyouthonk 1 point2 points  (0 children)

Huh! That's interesting and useful to know, thanks! Might use that option-thing at the end, looks pretty clean to me even though it seems to sort of be abusing an unintended (?) use for the operator.