web-dev-qa-db-ja.com

リストの要素の公平な分割

プレイヤーの評価のリストを考えると、プレイヤー(つまり、評価)をできるだけ2つのグループに分割する必要があります。目標は、チームの累積評価の差を最小限に抑えることです。プレーヤーをチームに分割する方法に制約はありません(1つのチームには2人のプレーヤーがいて、もう1つのチームには10人のプレーヤーがいます)。

例:[5, 6, 2, 10, 2, 3, 4]([6, 5, 3, 2], [10, 4, 2])を返す必要があります

この問題を解決するアルゴリズムを知りたいのですが。私はオンラインプログラミングの入門コースを受講しているので、簡単なアルゴリズムをいただければ幸いです。

次のコードを使用していますが、なんらかの理由で、オンラインコードチェッカーで間違っていると表示されています。

def partition(ratings):
    set1 = []
    set2 =[]
    sum_1 = 0
    sum_2 = 0
    for n in sorted(ratings, reverse=True):
        if sum_1 < sum_2:
            set1.append(n)
            sum_1 = sum_1 + n
        else:
            set2.append(n)
            sum_2 = sum_2 + n
    return(set1, set2)

pdate:インストラクターに連絡し、関数内に別の「ヘルパー」関数を定義してすべての異なる組み合わせをチェックする必要があると言われました。次に、最小の違いをチェックする必要があります。

12
EddieEC

私はすべての可能なリストを生成する必要があることを知っているので、すべての可能性を生成するのに役立つ「ヘルパー」関数を作成する必要があります。それを行った後、私は真の違いをチェックすることに真実であり、その最小の違いを持つリストの組み合わせが望ましい解決策です。

ヘルパー関数は再帰的であり、リストの組み合わせのすべての可能性をチェックします。

def partition(ratings):

    def helper(ratings, left, right, aux_list, current_index):
        if current_index >= len(ratings):
            aux_list.append((left, right))
            return

        first = ratings[current_index]
        helper(ratings, left + [first], right, aux_list, current_index + 1)
        helper(ratings, left, right + [first], aux_list, current_index + 1)

    #l contains all possible sublists
    l = []
    helper(ratings, [], [], l, 0)
    set1 = []
    set2 = []
    #set mindiff to a large number
    mindiff = 1000
    for sets in l:
        diff = abs(sum(sets[0]) - sum(sets[1]))
        if diff < mindiff:
            mindiff = diff
            set1 = sets[0]
            set2 = sets[1]
    return (set1, set2)

例:r = [1, 2, 2, 3, 5, 4, 2, 4, 5, 5, 2]、最適なパーティションは次のとおりです:([1, 2, 2, 3, 5, 4], [2, 4, 5, 5, 2])との違い1

r = [73, 7, 44, 21, 43, 42, 92, 88, 82, 70]、最適なパーティションは次のとおりです:([73, 7, 21, 92, 88], [44, 43, 42, 82, 70])との違い0

1
EddieEC

次のアルゴリズムがこれを行います。

  • アイテムを並べ替えます
  • 開始するには、リストaに偶数のメンバーを入れ、リストbに奇数のメンバーを入れます
  • 変更がより適切である場合、abの間でアイテムをランダムに移動および交換します

例のリストの進行状況を示すために、printステートメントを追加しました。

# -*- coding: utf-8 -*-
"""
Created on Fri Dec  6 18:10:07 2019

@author: Paddy3118
"""

from random import shuffle, random, randint

#%%
items = [5, 6, 2, 10, 2, 3, 4]

def eq(a, b):
    "Equal enough"
    return int(abs(a - b)) == 0

def fair_partition(items, jiggles=100):
    target = sum(items) / 2
    print(f"  Target sum: {target}")
    srt = sorted(items)
    a = srt[::2]    # every even
    b = srt[1::2]   # every odd
    asum = sum(a)
    bsum = sum(b)
    n = 0
    while n < jiggles and not eq(asum, target):
        n += 1
        if random() <0.5:
            # move from a to b?
            if random() <0.5:
                a, b, asum, bsum = b, a, bsum, asum     # Switch
            shuffle(a)
            trial = a[0]
            if abs(target - (bsum + trial)) < abs(target - bsum):  # closer
                b.append(a.pop(0))
                asum -= trial
                bsum += trial
                print(f"  Jiggle {n:2}: Delta after Move: {abs(target - asum)}")
        else:
            # swap between a and b?
            apos = randint(0, len(a) - 1)
            bpos = randint(0, len(b) - 1)
            trya, tryb = a[apos], b[bpos]
            if abs(target - (bsum + trya - tryb)) < abs(target - bsum):  # closer
                b.append(trya)  # adds to end
                b.pop(bpos)     # remove what is swapped
                a.append(tryb)
                a.pop(apos)
                asum += tryb - trya
                bsum += trya - tryb
                print(f"  Jiggle {n:2}: Delta after Swap: {abs(target - asum)}")
    return sorted(a), sorted(b)

if __name__ == '__main__':
    for _ in range(5):           
        print('\nFinal:', fair_partition(items), '\n')  

出力:

  Target sum: 16.0
  Jiggle  1: Delta after Swap: 2.0
  Jiggle  7: Delta after Swap: 0.0

Final: ([2, 3, 5, 6], [2, 4, 10]) 

  Target sum: 16.0
  Jiggle  4: Delta after Swap: 0.0

Final: ([2, 4, 10], [2, 3, 5, 6]) 

  Target sum: 16.0
  Jiggle  9: Delta after Swap: 3.0
  Jiggle 13: Delta after Move: 2.0
  Jiggle 14: Delta after Swap: 1.0
  Jiggle 21: Delta after Swap: 0.0

Final: ([2, 3, 5, 6], [2, 4, 10]) 

  Target sum: 16.0
  Jiggle  7: Delta after Swap: 3.0
  Jiggle  8: Delta after Move: 1.0
  Jiggle 13: Delta after Swap: 0.0

Final: ([2, 3, 5, 6], [2, 4, 10]) 

  Target sum: 16.0
  Jiggle  5: Delta after Swap: 0.0

Final: ([2, 4, 10], [2, 3, 5, 6]) 
1
Paddy3118

これは、パフォーマンスよりも教育を目的とした、かなり複雑な例です。それはいくつかの興味深いPython=リスト内包表記やジェネレータなどの概念、ならびにフリンジケースを適切にチェックする必要がある再帰の良い例を紹介します。例えば、等しい数のチームのみの拡張プレーヤーは有効であり、適切な個々の関数で実装するのは簡単です。

def listFairestWeakTeams(ratings):
    current_best_weak_team_rating = -1
    fairest_weak_teams = []
    for weak_team in recursiveWeakTeamGenerator(ratings):
        weak_team_rating = teamRating(weak_team, ratings)
        if weak_team_rating > current_best_weak_team_rating:
            fairest_weak_teams = []
            current_best_weak_team_rating = weak_team_rating
        if weak_team_rating == current_best_weak_team_rating:
            fairest_weak_teams.append(weak_team)
    return fairest_weak_teams


def recursiveWeakTeamGenerator(
    ratings,
    weak_team=[],
    current_applicant_index=0
):
    if not isValidWeakTeam(weak_team, ratings):
        return
    if current_applicant_index == len(ratings):
        yield weak_team
        return
    for new_team in recursiveWeakTeamGenerator(
        ratings,
        weak_team + [current_applicant_index],
        current_applicant_index + 1
    ):
        yield new_team
    for new_team in recursiveWeakTeamGenerator(
        ratings,
        weak_team,
        current_applicant_index + 1
    ):
        yield new_team


def isValidWeakTeam(weak_team, ratings):
    total_rating = sum(ratings)
    weak_team_rating = teamRating(weak_team, ratings)
    optimal_weak_team_rating = total_rating // 2
    if weak_team_rating > optimal_weak_team_rating:
        return False
    Elif weak_team_rating * 2 == total_rating:
        # In case of equal strengths, player 0 is assumed
        # to be in the "weak" team
        return 0 in weak_team
    else:
        return True


def teamRating(team_members, ratings):
    return sum(memberRatings(team_members, ratings))    


def memberRatings(team_members, ratings):
    return [ratings[i] for i in team_members]


def getOpposingTeam(team, ratings):
    return [i for i in range(len(ratings)) if i not in team]


ratings = [5, 6, 2, 10, 2, 3, 4]
print("Player ratings:     ", ratings)
print("*" * 40)
for option, weak_team in enumerate(listFairestWeakTeams(ratings)):
    strong_team = getOpposingTeam(weak_team, ratings)
    print("Possible partition", option + 1)
    print("Weak team members:  ", weak_team)
    print("Weak team ratings:  ", memberRatings(weak_team, ratings))
    print("Strong team members:", strong_team)
    print("Strong team ratings:", memberRatings(strong_team, ratings))
    print("*" * 40)

出力:

Player ratings:      [5, 6, 2, 10, 2, 3, 4]
****************************************
Possible partition 1
Weak team members:   [0, 1, 2, 5]
Weak team ratings:   [5, 6, 2, 3]
Strong team members: [3, 4, 6]
Strong team ratings: [10, 2, 4]
****************************************
Possible partition 2
Weak team members:   [0, 1, 4, 5]
Weak team ratings:   [5, 6, 2, 3]
Strong team members: [2, 3, 6]
Strong team ratings: [2, 10, 4]
****************************************
Possible partition 3
Weak team members:   [0, 2, 4, 5, 6]
Weak team ratings:   [5, 2, 2, 3, 4]
Strong team members: [1, 3]
Strong team ratings: [6, 10]
****************************************
1
Sander

あなたがチームでさえ欲しいなら、あなたは各チームのレーティングの目標スコアを知っています。これは、評価の合計を2で割ったものです。

したがって、次のコードはあなたが望むことをするはずです。

_from itertools import combinations

ratings = [5, 6, 2, 10, 2, 3, 4]

target = sum(ratings)/2 

difference_dictionary = {}
for i in range(1, len(ratings)): 
    for combination in combinations(ratings, i): 
        diff = sum(combination) - target
        if diff >= 0: 
            difference_dictionary[diff] = difference_dictionary.get(diff, []) + [combination]

# get min difference to target score 
min_difference_to_target = min(difference_dictionary.keys())
strong_ratings = difference_dictionary[min_difference_to_target]
first_strong_ratings = [x for x in strong_ratings[0]]

weak_ratings = ratings.copy()
for strong_rating in first_strong_ratings: 
    weak_ratings.remove(strong_rating)
_

出力

_first_strong_ratings 
[6, 10]

weak_rating 
[5, 2, 2, 3, 4]
_

同じfairnessのスプリットが他にもあります。これらはすべてstrong_ratingsタプル内で見つけることができます。渡した評価リストには常に存在するため、最初のスプリットを確認することを選択します(ただし、len(ratings) > 1)。

1
WGP

貪欲なソリューションは、次善のソリューションをもたらす可能性があります。これはかなり単純な貪欲な解決策です。アイデアは、バケットへの評価の追加の影響を減らすために、リストを降順に並べ替えることです。レーティングの合計が少ないバケットにレーティングが追加されます

lis = [5, 6, 2, 10, 2, 3, 4]
lis.sort()
lis.reverse()

bucket_1 = []
bucket_2 = []

for item in lis:
    if sum(bucket_1) <= sum(bucket_2):
        bucket_1.append(item)
    else:
        bucket_2.append(item)

print("Bucket 1 : {}".format(bucket_1))
print("Bucket 2 : {}".format(bucket_2))

出力:

Bucket 1 : [10, 4, 2]
Bucket 2 : [6, 5, 3, 2]

編集:

別のアプローチは、リストのすべての可能なサブセットを生成することです。リストのサブセットの1つであるl1があるとすると、l2 = list(original)-l1のようなリストl2を簡単に取得できます。サイズnのリストのすべての可能なサブセットの数は2 ^ nです。それらを0から2 ^ n -1の整数のシーケンスとして表すことができます。例として、リスト= [1、3、5]とすると、可能な組み合わせは2 ^ 3、つまり8ではありません。すべての組み合わせを次のように記述できます。

  1. 000-[]-0
  2. 001-[1]-1
  3. 010-[3]-2
  4. 011-[1,3]-3
  5. 100-[5]-4
  6. 101-[1,5]-5
  7. 110-[3,5]-6
  8. 111-[1,3,5]-7とl2は、この場合、2 ^ n-1のxorをとることで簡単に取得できます。

解決:

def sum_list(lis, n, X):
    """
    This function will return sum of all elemenst whose bit is set to 1 in X
    """
    sum_ = 0
    # print(X)
    for i in range(n):
        if (X & 1<<i ) !=0:
            # print( lis[i], end=" ")
            sum_ += lis[i]
    # print()
    return sum_

def return_list(lis, n, X):
    """
    This function will return list of all element whose bit is set to 1 in X
    """
    new_lis = []
    for i in range(n):
        if (X & 1<<i) != 0:
            new_lis.append(lis[i])
    return new_lis

lis = [5, 6, 2, 10, 2, 3, 4]
n = len(lis)
total = 2**n -1 

result_1 = 0
result_2 = total
result_1_sum = 0
result_2_sum = sum_list(lis,n, result_2)
ans = total
for i in range(total):
    x = (total ^ i)
    sum_x = sum_list(lis, n, x)
    sum_y = sum_list(lis, n, i)

    if abs(sum_x-sum_y) < ans:
        result_1 =  x
        result_2 = i
        result_1_sum = sum_x
        result_2_sum = sum_y
        ans = abs(result_1_sum-result_2_sum)

"""
Produce resultant list
"""

bucket_1 = return_list(lis,n,result_1)
bucket_2 = return_list(lis, n, result_2)

print("Bucket 1 : {}".format(bucket_1))
print("Bucket 2 : {}".format(bucket_2))


出力:

Bucket 1 : [5, 2, 2, 3, 4]
Bucket 2 : [6, 10]
0
vkSinha