bmay2
bmay2

Reputation: 382

Refactoring code/consolidating functions (e.g. nested for-loop order)

Just a little background: I'm making a program where a user inputs a skeleton text, two numbers (lower and upper limit), and a list of words. The outputs are a series of modifications on the skeleton text.

Sample inputs:

text = "Player # likes @." (replace # with inputted integers and @ with words in list)
lower = 1
upper = 3
list = "apples, bananas, oranges"

The user can choose to iterate over numbers first:

Player 1 likes apples.
Player 2 likes apples.
Player 3 likes apples.

Or words first:

Player 1 likes apples.
Player 1 likes bananas.
Player 1 likes oranges.

I chose to split these two methods of outputs by creating a different type of dictionary based on either number keys (integers inputted by the user) or word keys (from words in the inputted list) and then later iterating over the values in the dictionary.

Here are the two types of dictionary creation:

def numkey(dict): # {1: ['Player 1 likes apples', 'Player 1 likes...' ] }

    text, lower, upper, list = input_sort(dict)
    d = {}

    for num in range(lower,upper+1):
        l = []
        for i in list:
            l.append(text.replace('#', str(num)).replace('@', i))
        d[num] = l
    return d

def wordkey(dict): # {'apples': ['Player 1 likes apples', 'Player 2 likes apples'..] }

    text, lower, upper, list = input_sort(dict)
    d = {}

    for i in list:
        l = []
        for num in range(lower,upper+1):
            l.append(text.replace('#', str(num)).replace('@', i))
        d[i] = l
    return d

It's fine that I have two separate functions for creating different types of dictionaries but I see a lot of repetition between the two. Is there any way I could make one dictionary function and pass in different values to it that would change the order of the nested for loops to create the specific {key : value} pairs I'm looking for?

I'm not sure how this would be done. Is there anything related to functional programming or other paradigms that might help with this? The question is a little abstract and more stylistic/design-oriented than anything.

Upvotes: 2

Views: 674

Answers (3)

jfs
jfs

Reputation: 414795

You don't need a dictionary to generate the output. You could use something like:

import itertools

numbers = range(lower, upper + 1)
words = "a, b, c".split(", ")

data = (numbers, words) if numbers_first else (words, numbers)
for n, w in itertools.product(*data):
    if not numbers_first: n, w = w, n
    print("Player %d likes %s." % (n, w))

To avoid the if inside the loop you could generate the format string dynamically e.g.:

template = "Player # likes @."
subs = ("{n}", "{w}") if numbers_first else ("{w}", "{n}")
format = make_format(template, subs) # escape {}, replace # and @

# ...
for n, w in product(*data):
    print(format.format(n=n, w=w))

Upvotes: 2

georg
georg

Reputation: 215029

Here's a more generic way:

from itertools import product

def generate(skeleton, replacements):
    values = (product([k], v) for k, v in replacements.items())
    for p in product(*values):
        s = skeleton
        for a, b in p:
            s = s.replace(a, str(b))
        yield s

replacements is expected to be a dictionary {placeholder: list of values}, for example:

gen = generate("Player # likes @.", {'#': range(1,3), '@': ['apples', 'bananas']})    
for phrase in gen:
    print phrase

prints

Player 1 likes apples.
Player 2 likes apples.
Player 1 likes bananas.
Player 2 likes bananas.

If you need one of the values to be "static", just provide a single-element list:

gen = generate("Player # likes @.", {'#': [1], '@': ['apples', 'bananas']})    

Note that this works with arbitrary number of placeholders:

gen = generate("Player # likes @ and can $", {
    '#': range(1,3), 
    '@': ['apples', 'bananas'],
    '$': ['swim', 'run', 'jump']
})    

Upvotes: 1

Karmel
Karmel

Reputation: 3472

I think in this case, rather than forcing the two processes into one function, just make each function much shorter:

def numkey(dict):
    text, lower, upper, list = input_sort(dict)
    d = {x: [text.replace('#',str(x)).replace('@',item) 
            for item in list.split(', ')] for x in xrange(lower,upper+1)}
    return d

def wordkey(dict):
    text, lower, upper, list = input_sort(dict)
    d = {item: [text.replace('#',str(x)).replace('@',item) 
            for x in xrange(lower,upper+1)] for item in list.split(', ')}
    return d

You could in theory refactor out the dict creation using a make_dict(dict, outer, inner) or something like that, but I think that ends up being less clear for such short bits of code, especially given that you would then have to preprocess the ints in some way.

Upvotes: 1

Related Questions