How to assign each item in a list an equal amount of items from another list in python

138 Views Asked by At

Let's say I have a list of people

['foo','bar','baz']

and a list of items

['hat','bag','ball','bat','shoe','stick','pie','phone']

and I want to randomly assign each person an equal amount of items, like so

{
'foo':['hat','bat','stick'],
'bar':['bag','shoe','phone'],
'baz':['ball','pie']
}

I think itertools is the job for this, but I couldn't seem to find the right function as most itertools functions seem to just work on one object.

EDIT: Order does not matter. I just want to randomly assign each person an equal amount of items.

5

There are 5 best solutions below

1
Andrej Kesely On BEST ANSWER

Another solution, using itertools.cycle:

import random
from itertools import cycle

persons = ["foo", "bar", "baz"]
items = ["hat", "bag", "ball", "bat", "shoe", "stick", "pie", "phone"]

random.shuffle(items)

out = {}
for p, i in zip(cycle(persons), items):
    out.setdefault(p, []).append(i)

print(out)

Prints (for example):

{
    "foo": ["phone", "pie", "bat"],
    "bar": ["bag", "stick", "hat"],
    "baz": ["shoe", "ball"],
}

If there could be fewer items than persons and each person should have key in output dictionary you can use:

import random
from itertools import cycle

persons = ["foo", "bar", "baz"]
items = ["hat", "bag", "ball", "bat", "shoe", "stick", "pie", "phone"]

random.shuffle(items)
random.shuffle(persons)  # to randomize who gets fewest items

out = {p: [] for p in persons}
for lst, i in zip(cycle(out.values()), items):
    lst.append(i)

print(out)
0
quamrana On

You can shuffle the items and calculate a number of items for each person:

from itertools import islice
from random import shuffle

# https://docs.python.org/3/library/itertools.html#itertools.batched
def batched(iterable, n):
    # batched('ABCDEFG', 3) --> ABC DEF G
    if n < 1:
        raise ValueError('n must be at least one')
    it = iter(iterable)
    while batch := tuple(islice(it, n)):
        yield batch

people = ['foo','bar','baz']
items = ['hat','bag','ball','bat','shoe','stick','pie','phone']

shuffle(items)
b = batched(items, ((len(items)-1) // len(people))+1)

output = {k:list(next(b)) for k in people}
print(output)

Sample output:

{'foo': ['phone', 'bat', 'ball'], 'bar': ['hat', 'bag', 'pie'], 'baz': ['stick', 'shoe']}

btw batched() is available in python 3.12

However, as Kelly Bundy points out, batched() doesn't always split evenly.

This split() function I found in an answer to another question works better:

def split(a, n):
    k, m = divmod(len(a), n)
    return (a[i*k+min(i, m):(i+1)*k+min(i+1, m)] for i in range(n))

b = split(items, len(people))
output = {k:list(next(b)) for k in people}
print(output)
2
JacobIRR On

How about this?

import random
from collections import defaultdict
from pprint import pprint

people = ['foo','bar','baz']
items = ['hat','bag','ball','bat','shoe','stick','pie','phone']

# set up a dictionary with values as a list so we can append to it
out = defaultdict(list)
# get a list of indexes for the items in the list
item_indexes = [i for i in range(len(items))]
# ... then shuffle these into random order
random.shuffle(item_indexes)

# Now loop over the randomized indexes, 
for i in item_indexes:
    # ... and pick an item from the list based on that index.
    out[people[i%len(people)]].append(items[i])

pprint(out)

Result:

defaultdict(<class 'list'>,
            {'bar': ['bag', 'phone', 'shoe'],
             'baz': ['stick', 'ball'],
             'foo': ['bat', 'hat', 'pie']})
3
Jacek Błocki On

What about this:

g=(i for i in ['hat','bag','ball','bat','shoe','stick','pie','phone']) #generator
keys=['foo','bar','baz']
d={k: [] for k in keys}
while True:
    try:
        for i in keys:
            d[i].append(next(g)) 
    except StopIteration:
        break

Version with randomization: Draw random key and use generator for values.

import random
g=(i for i in ['hat','bag','ball','bat','shoe','stick','pie','phone'])
keys=['foo','bar','baz']
d={k: [] for k in keys}
while True:
    try:
        i = random.randint(0, len(keys)-1)
        d[keys[i]].append(next(g)) 
    except StopIteration:
        break
0
Isaac Wolford On

Here is a nice simple one-liner solution that you can easily change for your needs:

import random

people: list = ['foo', 'bar', 'baz']
items: list = ['hat', 'bag', 'ball', 'bat', 'shoe', 'stick', 'pie', 'phone']

random.shuffle(items)
result: dict = {p: i for p, i in zip(people, [items[i:i+3] for i in range(0, len(items), 3)])}

print(result)