2

Suppose I am having a Dictionary in the following format.

new_dict = {'worry': [23, 667, 1692, 1940], 'win': [96, 200, 286], 'work': [833, 1458]}

I want to print only the key only keys based on the values between 0 to 100.

Output in this case:

{'worry', 'win'}

I have tried:

dict0100 = {k:v for (k,v) in new_dict.items() if new_dict.values(range(100))}

I am getting error like values take only 1 argument.

Note: The values range between 0 to 5000 in my case. I want only the key values between 0 to 100 only.

Any help is appreciated.

sentence
  • 5,556
  • 4
  • 20
  • 33
M S
  • 836
  • 1
  • 8
  • 28

5 Answers5

2

Try this:

dict0100 = {k: v for (k, v) in new_dict.items() if any(i <= 100 for i in v)}

If you want the list to be filtered too:

dict0100 = {k: l for k, l in [(k, [i for i in v if i <= 100]) for k, v in new_dict.items()] if l}

Note about < or range

About using the comparison operator < or the in range() expression, I would personnaly go for the x < 100 since here all of the lists items are said to be positive integers, and comparison is more performant:

import timeit

t1 = timeit.timeit(lambda: 3 in range(100))
print(t1) # 0.37976

t2 = timeit.timeit(lambda: 3 <= 100)
print(t2) # 0.08725
olinox14
  • 5,087
  • 1
  • 14
  • 34
  • 3
    Values are said to be between 0 and 5000 – olinox14 Jun 27 '19 at 14:57
  • "comparison is more performant" sure, performance matters but this is an unnecessary microoptimization that sacrifices some degree of readability. I wouldn't bother. – cs95 Jun 27 '19 at 16:15
  • Ok then, but I can't 100% agree on `x < 100` being less readable than `in range()`... Whatever, range is fine here. – olinox14 Jun 27 '19 at 16:18
2

I'd use a range check with any. Here's a neat reusable generator function:

def get_keys_in_range(dct, lo=0, hi=100):
    rng = range(lo, hi) 
    for key, lst in dct.items():
        if any(l in rng for l in lst):
            yield key

[*get_keys_in_range(new_dict, lo=0, hi=100)]
# ['worry', 'win']

It's worth mentioning that membership checks on range objects are constant time (O(1)) in python-3.x (but O(N) in python-2.x, so you may want to consider the other solutions using pure comparisons).

Note that the range is closed on the right interval, meaning you'd want to do range(lo, hi+1) if you want the upper bound to be inclusive.


This can be shortened to

lo, hi = 0, 100
[key for key, lst in new_dict.items() if any(l in range(lo, hi) for l in lst)]
# ['worry', 'win']

You can pre-cache range for a little more performance (as done inside the function).

rng = range(lo, hi)
[key for key, lst in new_dict.items() if any(l in rng for l in lst)]
# ['worry', 'win']
cs95
  • 274,032
  • 76
  • 480
  • 537
  • 1
    @Chris_Rands yay was just adding that disclaimer as you posted :D – cs95 Jun 27 '19 at 15:00
  • From a 'keep it simple' point of view, I can't see the upside of using `range` here... It can be O(1), it's still more greedy that a simple ` – olinox14 Jun 27 '19 at 15:32
  • @olinox14 it's just an option, and IMO more concise than using `if` on python3 where it makes sense to use it (since it is a constant time operation). `number in range(lo, hi)` is also very readable. I'm not sure what you mean by "greedy" either. And "lists are said to hold only positive integers" seems like a non-sequitur. – cs95 Jun 27 '19 at 15:40
  • About lists, I was just quoting OP: `The values range between 0 to 5000 in my case.`. By greedy, I meant that even if it's a constant time operation, it's more time-consuming than a simple comparison, don't you agree? – olinox14 Jun 27 '19 at 16:06
  • @olinox14 I don't see how the range of values matters here. I agree on range being more time consuming, but unless the sublists are extremely large with very little chance of finding positives for most of them, the difference isn't something I'd really care about. – cs95 Jun 27 '19 at 16:13
1

Try this:

dict0100 = {k:v for (k,v) in new_dict.items() if [x for x in v if all([x<100,x>0])]}

This will save both the key and the value, for key only remove the ":v"

mik1904
  • 1,201
  • 7
  • 18
1

your dict has keys of type str and values of type list (of ints)

your expected output is a set object (and not a dict).

try this:

new_dict = {'worry': [23, 667, 1692, 1940], 'win': [96, 200, 286], 'work': [833, 1458]}

set0100 = {k for k, arr in new_dict.items() if any(v in range(100) for v in arr)}

print(set0100)

this uses the nice fact that range objects implement the __contains__ method, which allows us to use in on them.

Adam.Er8
  • 10,606
  • 3
  • 17
  • 32
  • 1
    `v in range(100)` is unecessary greedy, since `v <= 100` is enough here... – olinox14 Jun 27 '19 at 14:56
  • range objects `__contains__` function is actually very efficient... it uses a simple arithmetic computation. – Adam.Er8 Jun 27 '19 at 14:59
  • 1
    From a 'keep it simple' point of view, I can't see the upside of using `range` here... It can be O(1), it's still more greedy that a simple ` – olinox14 Jun 27 '19 at 15:31
0

dict0100 = {k:v for (k,v) in new_dict.items() if all([v_e < 5000 for v_e in v])}

Memristor
  • 75
  • 2
  • 4