Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redistribute dictionary value lists

I have the following dict:

groups = {"group 1": [1, 2, 3, 4],
          "group 2": [5, 6, 7, 8],
          "group 3": [9, 10, 11, 12],
          "group 4": [13, 14]}

When the length of a group is smaller than a minimum size (group_size=4), I want to redistribute the members to the other groups. The result in this case would be something like:

groups = {"group 1": [1, 2, 3, 4, 13],
          "group 2": [5, 6, 7, 8, 14],
          "group 3": [9, 10, 11, 12]}

I have the following code, which works, but is less efficient than I'd like:

# Identify small groups
small_groups = []
for group_name, group_members in groups.items():
    if len(group_members) < group_size:
        small_groups.append(group_name)

# Redistribute members of small groups to the larger groups
to_redistribute = []
for group_name in small_groups:
    to_redistribute.extend(groups.pop(group_name))

for group_name, group_members in groups.items():
    if not to_redistribute:
        break
    group_members.append(to_redistribute.pop())

Important note: The real members of groups are strings, not integers.

Is there a better way to redistribute dictionary value lists?

like image 526
ZaxR Avatar asked May 14 '18 23:05

ZaxR


2 Answers

Your solution is good, but you can combine the pop and re-distribution logic together using itertools.cycle.

from itertools import cycle

for k in list(groups.keys()):
    if len(groups[k]) < group_size:
        for v, k_ in zip(groups.pop(k), cycle(groups.keys())):
            groups[k_].append(v)

The idea is to keep cycling through keys to re-distribute the data equally. It determines—at each iteration—whether a group is over the threshold or not. If a group is valid, then augmenting it later (via re-distribution) will never bring it under the threshold. However groups that are initially under the threshold (but are not reached until later during future iterations) can possibly become valid if and when you augment values to it from another group that was removed. If that does not happen, then it will be removed and its data redistributed on a future iteration.

Keep in mind, groups that were initially slated for removal may now become valid after redistribution, so our solutions will differ in the output for some inputs.

print(groups)
{'group 1': [1, 2, 3, 4, 13],
 'group 2': [5, 6, 7, 8, 14],
 'group 3': [9, 10, 11, 12]}
like image 195
cs95 Avatar answered Oct 04 '22 09:10

cs95


  1. Use filter and sum to pull out concatenated lists with lengths less than 4
  2. Use comprehension to rebuild new dictionary with lists whose lengths are greater than or equal to 4
  3. iterative remove one item from the filtered list and append it to the newly built dictionary key until all items from filtered list are exhausted.

from itertools import cycle

f = lambda v: len(v) < 4
x = sum(filter(f, groups.values()), [])
g = {k: v for k, v in groups.items() if not f(v)}

c = cycle(g)
while x:
    g[next(c)].append(x.pop())

g

{'group 1': [1, 2, 3, 4, 14],
 'group 2': [5, 6, 7, 8, 13],
 'group 3': [9, 10, 11, 12]}
like image 25
piRSquared Avatar answered Oct 04 '22 09:10

piRSquared