0

I am trying to find a way to get the next key of a Python 3.6+ (which are ordered)

For example:

dict = {'one':'value 1','two':'value 2','three':'value 3'}

What I am trying to achieve is a function to return the next key. something like:

next_key(dict, current_key='two')   # -> should return 'three' 

This is what I have so far:

def next_key(dict,key):
    key_iter = iter(dict)  # create iterator with keys
    while k := next(key_iter):    #(not sure if this is a valid way to iterate over an iterator)
        if k == key:   
            #key found! return next key
            try:    #added this to handle when key is the last key of the list
                return(next(key_iter))
            except:
                return False
    return False

well, that is the basic idea, I think I am close, but this code gives a StopIteration error. Please help.

Thank you!

F. A
  • 91
  • 9
  • Why not just use `iteritems()`? What do you mean by `next`? If you iterate over all keys, use built-in `keys()`, why isn't it enough? – Aaron_ab Apr 06 '20 at 11:28
  • You *can* do this, but it’s really awkward (not a fast operation on dicts, dicts preserve order but aren’t ordered per se – less than `OrderedDict`s, anyway). Is there a problem one step removed that might have a better solution you could ask about? – Ry- Apr 06 '20 at 11:29
  • 2
    Does this answer your question? [How to get the "next" item in an OrderedDict?](https://stackoverflow.com/questions/12328184/how-to-get-the-next-item-in-an-ordereddict) – Mayank Porwal Apr 06 '20 at 11:30
  • @F.A If you have already found the answer, I would suggest to take this question down. No point in having multiple same questions. – Mayank Porwal Apr 06 '20 at 11:37
  • With `next_key(dict, key='two')` your code returns `thee` for me on Python 3.8.1. Only change I did was replace current_key with key in the function call. Also you shouldn't use variable names that conflict with builtin functions (i.e. names such as `dict, list, . ..`). – DarrylG Apr 06 '20 at 11:40
  • @Mayank Porwal after looking at the question you mentioned, it is similar than the one I posted but it refers to a OrderedDict. The one I posted is using a regular dictionary – F. A Apr 06 '20 at 11:52
  • @F.A The first line of your question says `(Python 3.6+, ordered dictionary)`. I went with this. And if you are talking about regular dict, then there's no concept of particular next as regular dict is an unordered object. – Mayank Porwal Apr 06 '20 at 11:57
  • 1
    @MayankPorwal When are people going to stop claiming dicts are still unordered? – Kelly Bundy Apr 06 '20 at 12:59

4 Answers4

4

An iterator way...

def next_key(dict, key):
    keys = iter(dict)
    key in keys
    return next(keys, False)

Demo:

>>> next_key(dict, 'two')
'three'
>>> next_key(dict, 'three')
False
>>> next_key(dict, 'four')
False
Kelly Bundy
  • 5,629
  • 1
  • 5
  • 31
  • 1
    Wow. That’s terrible, but also beautiful. – Ry- Apr 06 '20 at 11:38
  • @Ry- Do you mean terrible because of the complexity? (I doubt it can be improved if the dict is all we have.) – Kelly Bundy Apr 06 '20 at 11:41
  • No, the use of `in` for side effects :D – Ry- Apr 06 '20 at 11:41
  • 1
    @Ry- Ah. Heh :-). I like doing that. [Here's](https://stackoverflow.com/a/60458724/12671057) another recent one. – Kelly Bundy Apr 06 '20 at 11:44
  • @heapoverflow, Amazing very short solution! Although I do not understand how it works. What happens exactly in the line "key in keys"? – F. A Apr 06 '20 at 12:23
  • 1
    @F.A It's not explicitly covered [where it should be](https://docs.python.org/3/reference/expressions.html#membership-test-operations), but I believe it falls in the same category as *"user-defined classes which do not define `__contains__()` but do define `__iter__()`"*, so it consumes the iterator until it finds the value (or until the end, if it's not in there). – Kelly Bundy Apr 06 '20 at 12:51
4

Looping while k := next(key_iter) doesn’t stop correctly. Iterating manually with iter is done either by catching StopIteration:

iterator = iter(some_iterable)

while True:
    try:
        value = next(iterator)
    except StopIteration:
        # no more items

or by passing a default value to next and letting it catch StopIteration for you, then checking for that default value (but you need to pick a default value that won’t appear in your iterable!):

iterator = iter(some_iterable)

while (value := next(iterator, None)) is not None:
    # …

# no more items

but iterators are, themselves, iterable, so you can skip all that and use a plain ol’ for loop:

iterator = iter(some_iterable)

for value in iterator:
    # …

# no more items

which translates into your example as:

def next_key(d, key):
    key_iter = iter(d)

    for k in key_iter:
        if k == key:
            return next(key_iter, None)

    return None
Ry-
  • 199,309
  • 51
  • 404
  • 420
  • Thank you very mutch for the answer! I have lerned a lot from reading it. Would you say your proposed solution is better than the one proposed by @heapoverflow ? you mentioned something about side effects of using "in", I am not sure what you mean. I like his solution as it avoids the for loop so it is shorter – F. A Apr 06 '20 at 12:00
  • 1
    @F.A: I would say the best solution is probably one that never involves this function at all :] Like I commented, it’s a weird thing to do with a `dict`, so I think you should take a look at the problem you’re trying to solve by doing it (and feel free to ask about that problem here! See also: https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). As for `in`: most people will be surprised to see that, but it’s not too bad when its intent is clear the way it is in a small function. – Ry- Apr 06 '20 at 12:03
1

You can get the keys of the dictionary as list and use index() to get the next key. You can also check for IndexError with try/except block:

my_dict = {'one':'value 1','two':'value 2','three':'value 3'}

def next_key(d, key):
  dict_keys = list(d.keys())
  try:
    return dict_keys[dict_keys.index(key) + 1]
  except IndexError:
    print('Item index does not exist')
    return -1

nk = next_key(my_dict, key="two")
print(nk)

And you better not use dict, list etc as variable names.

Harun Yilmaz
  • 6,857
  • 3
  • 21
  • 31
0
# Python3 code to demonstrate working of 
# Getting next key in dictionary Using list() + index()

# initializing dictionary 
test_dict = {'one':'value 1','two':'value 2','three':'value 3'}

def get_next_key(dic, current_key):
    """ get the next key of a dictionary.

    Parameters
    ----------
    dic: dict
    current_key: string

    Return
    ------
    next_key: string, represent the next key in dictionary.
    or
    False If the value passed in current_key can not be found in the dictionary keys,
    or it is last key in the dictionary
    """

    l=list(dic) # convert the dict keys to a list

    try:
        next_key=l[l.index(current_key) + 1] # using index method to get next key
    except (ValueError, IndexError):
        return False
    return next_key

get_next_key(test_dict, 'two')

'three'

get_next_key(test_dict, 'three')

False

get_next_key(test_dict, 'one')

'two'

get_next_key(test_dict, 'NOT EXISTS')

False

Sameh Farouk
  • 339
  • 2
  • 7