1

This is my first post and I'm quite new at programming, so I might not be able to convey my question appropriately, but I'll do my best!

tries_dict = {1:'first', 2:'second', 3:'third', 4:'fourth', ub_tries:'last'}

ub_tries = user input

tries = 1

input ('\nCome on make your ' + tries_dict.get(tries) + guess: ')

These 3 elements are part of a number guess game I created, and I included them in a while loop where tries += 1 after each wrong answer.

As you can see, in my dictionary there are custom values for the first 4 answers and the last possible chance before the game is over, so here is what I tried to do:

I wanted to find a way to have the 'NEXT' value for every answer/key between 'fourth' and 'last'.

As in:

tries = 5

Come on make your next guess

tries = 6

Come on make your next guess

and so on

I did find a way with some complex looping, but being the curious type I wanted to know of more efficient/practical ways to accomplish this.

Here are some options i thought about but couldn't get to work:

  1. Using a range as a key
  2. Finding a way to generate a list with values between 4 and ub_tries and using that list as a key

So generally speaking: how can one create a way to have this general answer (next or whatever) for keys that aren't specified in a dictionary?

Any feedback would be greatly appreciated, feel free to ask for clarifications since I can tell myself my question is kind of messy.

I hope I get more crafty both at programming and asking related questions, so far my programming is nearly as messy as my summary skills, sigh!

Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160
Newbie
  • 87
  • 1
  • 2
  • 9
  • Are you just looking for: `tries_dict.get(tries, 'next')`? – abarnert Nov 19 '12 at 23:25
  • PS, you _can_ use a `range` as a key (that's in Python 3; in Python 2, it's an `xrange`), but I don't see how that helps you here. You'd then have to use a `range` to do the later lookups, and where would this come from? – abarnert Nov 19 '12 at 23:39
  • @abarnert i don't really get your point about dict.get, there is no key for 'next' so how could i get it? well i did think about a range as in range (5,ub_tries), this range working as keys for the value 'Next' – Newbie Nov 20 '12 at 23:35
  • Did you read my answer? The idea is that `'next'` is the default value that you get back for any key that you haven't defined a value for. So, you get it by calling `get` with any value except 1, 2, 3, 4, or `ub_tries`. Which is exactly what you want, right? – abarnert Nov 20 '12 at 23:41
  • Meanwhile, if you have `range(5, ub_tries)` as a key, that's fine, but how will it help? For example, `tries_dict[range(5, ub_tries)] = 'next'` is fine, but then `tries_dict[6]` is still a `KeyError`, because `6` isn't the same key as `range(5, ub_tries)`. – abarnert Nov 20 '12 at 23:43

4 Answers4

5

I'm not sure whether this is what you want, but dict.get may be the answer:

>>> ub_tries = 20
>>> tries_dict = {1:'first', 2:'second', 3:'third', 4:'fourth', ub_tries:'last'}
>>> tries_dict.get(1, 'next')
'first'
>>> tries_dict.get(4, 'next')
'fourth'
>>> tries_dict.get(5, 'next')
'next'
>>> tries_dict.get(20, 'next')
'last'
>>> tries_dict.get(21, 'next')
'next'

Of course you could wrap this up in a function, in various different ways. For example:

def name_try(try_number, ub_tries):
    tries_dict = {1:'first', 2:'second', 3:'third', 4:'fourth', ub_tries:'last'}
    return tries_dict.get(try_number, 'next')

At any rate, dict.get(key, default=None) is like dict[key], except that if key is not a member, instead of raising a KeyError, it returns default.

As for your suggestions:

using a range as a key??

Sure, you can do that (if you're in Python 2 instead of 3, use xrange for range), but how would it help?

d = { range(1, 5): '???', 
      range(5, ub_tries): 'next', 
      range(ub_tries, ub_tries + 1): 'last' }

That's perfectly legal—but d[6] is going to raise a KeyError, because 6 isn't the same thing as range(5, ub_tries).

If you want this to work, you could build a RangeDictionary like this:

class RangeDictionary(dict):
    def __getitem__(self, key):
        for r in self.keys():
            if key in r:
                return super().__getitem__(r)
        return super().__getitem__(key)

But that's well beyond "beginners' Python", even for this horribly inefficient, incomplete, and non-robust implementation, so I wouldn't suggest it.

finding a way to generate a list with values between 4 and ub_tries and using such list as a key

You mean like this?

>>> ub_tries = 8
>>> tries_dict = {1:'first', 2:'second', 3:'third', 4:'fourth', ub_tries:'last'}
>>> tries_dict.update({i: 'next' for i in range(5, ub_tries)})
>>> tries_dict
{1: 'first', 2: 'second', 3: 'third', 4: 'fourth', 5: 'next', 6: 'next', 7: 'next', 8: 'last'}
>>> tries_dict[6]
'next'

That works, but it's probably not as good a solution.

Finally, you could use defaultdict, which lets you bake the default value into the dictionary, instead of passing it as part of each call:

>>> from collections import defaultdict
>>> tries_dict = defaultdict(lambda: 'next', 
...                          {1:'first', 2:'second', 3:'third', 4:'fourth', ub_tries:'last'})
>>> tries_dict
defaultdict(<function <lambda> at 0x10272fef0>, {8: 'last', 1: 'first', 2: 'second', 3: 'third', 4: 'fourth'})
>>> tries_dict[5]
'next'
>>> tries_dict
defaultdict(<function <lambda> at 0x10272fef0>, {1: 'first', 2: 'second', 3: 'third', 4: 'fourth', 5: 'next', 8: 'last'})

However, note that this permanently creates each element the first time you ask for it—and you have to create a function that returns the default value. This makes it more useful for cases where you're going to be updating values, and just want a default as a starting point.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • def name_try(try_number, ub_tries): ** why are we defining name_try? – Newbie Nov 21 '12 at 00:32
  • return tries_dict.get(try_number, 'next') **means 'retrieve' value for key 'try_number' and if try number is not a member "retrieve" default which in this case is defined as 'next', right? can i include this inside an input statement? as in: input ('\nCome on make your ' + tries_dict.get(tries_number, 'next') + guess: ') – Newbie Nov 21 '12 at 00:38
  • @Newbie: Sometimes it's nice to wrap up common functionality in a function. For example, if you need to get the name from 30 places in your code, and you want to play with different implementations, having a function means you only need to change 1 line instead of 30. Also, in some cases, a good name can help the code be self-documenting. – abarnert Nov 21 '12 at 00:38
  • @Newbie: Yes, that's what `get` means. And yes, a call to the `get` method, like any other function call, is an expression, so it can be used anywhere an expression can be used, including in the middle of an expression adding strings together into a parameter for a call to another function like `input`. – abarnert Nov 21 '12 at 00:40
  • i meant using a range only for next range(5,last_trie) and explicitly define keys and values for 1 to 4 and last_ trie. or do you mean that they keys have to be ALL ranges? which could be done as well i guess, not too practical though, right? – Newbie Nov 21 '12 at 00:40
  • sorry i just noticed the comment on top was from the post all the way at the bottom, anyhow thanks a lot, i like the .update thing as well. as for the last default dictionary import sounds a little over the top for my level now. anyhow let me put my hands on the stuff you already gave and i'll get back to you. Cheers :-) – Newbie Nov 21 '12 at 00:50
  • @Newbie: Actually, `import` is really simple, and something you should learn pretty early. However, `defaultdict` is _not_ that simple—you probably shouldn't be learning `lambda` yet, for example. So I agree, it's not the best solution for you. – abarnert Nov 21 '12 at 00:55
  • Thanks a lot for the feedback, it's all very interesting, i'll put all of this to practice. Cheers. – Newbie Nov 21 '12 at 21:29
2

You can use a dictionary with range as a key:

def switch(x):
    return { 1<x<4: 'several', 5<x<40: 'lots'}[1]


x = input("enter no. of monsters ")
print switch(x)

Hope this helps. :)

1

Have I captured your intent for the game here?

max_tries = 8

tries_verbage = {
    1: 'first',
    2: 'second',
    3: 'third',
    4: 'fourth',
    max_tries: 'last'
    }

for i in xrange(1, max_tries + 1):
    raw_input('Make your %s guess:' % tries_verbage.get(i, 'next'))

returns

Make your first guess:1
Make your second guess:2
Make your third guess:3
Make your fourth guess:4
Make your next guess:5
Make your next guess:6
Make your next guess:7
Make your last guess:8
hexparrot
  • 3,399
  • 1
  • 24
  • 33
  • WOW so much new stuff to learn!! thanks i did a little homework to understand better your suggestion so... raw_input = input in python 3.x?? should i use xrange or range in my case and why? i read is about some memory stuff, but it got a little too technical for a newbie like myself. %s is basically some string formatting right? in your example it can be translated as: for the given range "insert" in the string, at %s, all the values included in the dict. "plus" 'next' as a value for the rest. Other than that your suggestion helped me a a lot!! thanx – Newbie Nov 21 '12 at 00:46
  • @Newbie: Yes, Python 3's `input` is called `raw_input` in Python 2, and Python 3's `range` is called `xrange` in Python 2. If you're learning Python 3, you don't have to worry about that right now—except, of course, that you have to read answers that people have written for Python 2. (Adding the `python3` tag and/or mentioning the Python version in your question will help with that.) – abarnert Nov 21 '12 at 00:48
1

Why don't you just use a list?

MAX_TRIES = 10
tries_list = ["first", "second", "third", "fourth"]

for word in (tries_list[:MAX_TRIES-1] + 
             (["next"] * (MAX_TRIES - len(tries_list) - 1)) + ["last"]):
    result = raw_input("Come on, make your {} guess:".format(word))

Note this code won't work as planned for MAX_TRIES = 0, but I don't think that will be a problem.

Joel Cornett
  • 24,192
  • 9
  • 66
  • 88
  • Thanks, could you please elaborate more on the for statement? max_tries would be user defined previously, like user input, right? tries = 0 = first?? tries = 1 = second? (["next"] * (max_tries - len(tries_list)) ** this i didn't get at all!, hehe** Could you provide me with a "english" step by step explanation of what's happening there? :-p – Newbie Nov 21 '12 at 00:20
  • `["next"] * (max_tries - len(tries_list))` is a list of `max_tries - len(tries_list)` copies of the string `"next"`. So, if `max_tries=20` and `tries_list` is that 9-element list shown in the answer, it will have 11 copies of the string `"next"`. When you add that to the 9-element `tries_list`, you get a 20-element list, where the first 9 come from `tries_list` and the next 11 are all `"next"`. To get _exactly_ what you want, you'd want `max_tries - len(tries_list) - 1`, and then add `["last"]` to the end. – abarnert Nov 21 '12 at 00:44
  • Also, this loop doesn't get you the number of the current try, just the string. But if you want that, look at the built-in `enumerate` function. You can do `for i, name in enumerate(tries_list... + ["last"])`, and then `i+1` is the number of the current try. – abarnert Nov 21 '12 at 00:45
  • 1
    Finally, this suggestion won't actually work as written, because `try` is a reserved word. So you'll need to change the name of the variable (and possibly other things, since this obviously wasn't actually tested, or the poster would have realized that it doesn't work). – abarnert Nov 21 '12 at 00:46
  • @abarnert : Sorry for the poor answer quality. Fixed now. – Joel Cornett Nov 21 '12 at 09:25
  • Thanks, List is definitely a different approach which is always welcome! – Newbie Nov 21 '12 at 21:28