Link
Link

Reputation: 61

List Comprehension nested in Dict Comprehension

I want to create a dict with lists as values, where the content on the lists depends on whether or not the key (numbers 1 to 100) is dividable by 3,5 and/or 7

The output would be like this:

{
    1: ['nodiv3', 'nodiv5', 'nodiv7'],
    3: ['div3', 'nodiv5', 'nodiv7'],
    15: ['div3', 'div5', 'nodiv7'],
}

Similar questions where about filtering the list/values, not creating them.

dict_divider = {}
for x in range(0,101):
    div_list= []
    if x % 3 == 0:
        div_list.append('div3')
    else:
        div_list.append('nodiv3')
    if x % 5 == 0:
        div_list.append('div5')
    else:
        div_list.append('nodiv5')
    if x % 7 == 0:
        div_list.append('div7')
    else:
        div_list.append('nodiv7')
    dict_divider[x] = div_list

This works just fine, but is there a way to do this with a pythonic one-/twoliner?

Something along like this: d = dict((val, range(int(val), int(val) + 2)) for val in ['1', '2', '3'])

Upvotes: 0

Views: 148

Answers (4)

Hadrian
Hadrian

Reputation: 927

you could write a second loop so that you only have to write if...else only once

dict_divider = {}
div_check_lst = [3, 5, 7]
for x in range(0,101):
    div_list= []
    for div_check in div_check_lst:
        if x % div_check == 0:
            div_list.append(f'div{str(div_check)}')
        else:
            div_list.append(f'nodiv{str(div_check)}')
    dict_divider[x] = div_list

or

dict_divider = {x:[f'{'no' * x % div_check != 0}div{str(div_check)}' for x in range(0,101) for div_check in div_check_lst]}

Upvotes: 0

wim
wim

Reputation: 362507

You don't actually need to do all these brute-force divisions. Every third number is divisible by three, every seventh number is divisible by seven, etc:

0 1 2 3 4 5 6 7 8 9 ...  <-- range(10)
0 1 2 0 1 2 0 1 2 0 ...  <-- mod 3

0 1 2 3 4 5 6 7 8 9 ...  <-- range(10)
0 1 2 3 4 5 6 0 1 2 ...  <-- mod 7

So the best approach should take advantage of that fact, using the repeating patterns of modulo. Then, we can just zip the range with however many iterators you want to use.

import itertools

def divs(n):
    L = [f"div{n}"] + [f"nodiv{n}"] * (n - 1)
    return itertools.cycle(L)

repeaters = [divs(n) for n in (3, 5, 7)]
d = {x: s for x, *s in zip(range(101), *repeaters)}

Upvotes: 1

Alexander Riedel
Alexander Riedel

Reputation: 1359

There is actually a one liner that isnt even that complicated :)

my_dict = {}
for i in range(100):
    my_dict[i] = ['div' + str(n) if i % n == 0 else 'nodiv' + str(n) for n in [3,5,7]]

Upvotes: 0

Dani Mesejo
Dani Mesejo

Reputation: 61910

Pythonic is not about one or two liners. In my opinion is (mainly) about readability, perhaps this could be considered more pythonic:

def label(n, divisor):
    return f"{'' if n % divisor == 0 else 'no'}div{divisor}"


def find_divisors(n, divisors=[3, 5, 7]):
    return [label(n, divisor) for divisor in divisors]


dict_divider = {x: find_divisors(x) for x in range(1, 101)}

print(dict_divider) 

Upvotes: 1

Related Questions