代码之家  ›  专栏  ›  技术社区  ›  Rakha

从哈希表中获取随机项,但值的总和必须等于一个集合数

  •  3
  • Rakha  · 技术社区  · 6 年前

    我正试图建立一个简单的“任务分配者”,为我和我妻子之间的家庭任务。虽然这个概念在工作中也很有用,所以我需要好好学习。

    我的哈希表:

    $Taches = @{
        "Balayeuse plancher" = 20
        "Moppe plancher" = 20
        "Douche" = 15
        "Litières" = 5
        "Poele" = 5
        "Comptoir" = 5
        "Lave-Vaisselle" = 10
        "Toilette" = 5
        "Lavabos" = 10
        "Couvertures lit" = 5
        "Poubelles" = 5
    }
    

    所有项目的总值是105(分钟)。 所以大约50分钟,我们每个人分为两部分。

    我的目标是:

    我想从该哈希表中选择随机项,并构建两个不同的哈希表——一个为我和我妻子,每个哈希表的总值为50(这很公平)。例如20+20+10或5+5+5+15+20等。最困难的部分是,所有任务都必须在两个哈希表之间进行计算,并且每个哈希表只能出现一次(同一个哈希表不能清洗两次!).

    最好的选择是什么?

    目前,我成功地实现了一个总值为50的随机哈希表,如下所示:

    do {
        $Me = $null
        $sum = $null
        $Me = @{}
        $Me = $Taches.GetEnumerator() | Get-Random -Count 5
        $Me | ForEach-Object { $Sum += $_.value }
    } until ($sum -eq 50)
    

    结果示例:

    Name                           Value
    ----                           -----
    Poubelles                      5
    Balayeuse plancher             20
    Douche                         15
    Poele                          5
    Toilette                       5
    

    它起作用了,但是男孩,它感觉它是一种迂回和弯曲的方式。我确定有更好的方法吗?另外,我缺少重要的东西。所有的任务都必须考虑在内,不能出现两次。虽然起初看起来很简单,但这很复杂!

    4 回复  |  直到 6 年前
        1
  •  4
  •   Don Cruickshank    6 年前

    你不能同时最大限度地提高随机性和公平性,所以一个人必须付出。我认为你不应该冒着对你妻子不公平的风险,所以公平必须占上风!

    以牺牲随机性为代价的公平

    这种方法按时间降序对项目进行排序,然后将它们随机分配给每个人,除非分配不公平。

    这里的公平计算是,最大时间差最多应该是最快任务的持续时间。

    $DescendingOrder = $Taches.Keys | Sort-Object -Descending { $Taches[$_] }
    
    $Measures = $Taches.Values | Measure-Object -Sum -Minimum
    $UnfairLimit = ($Measures.Sum + $Measures.Minimum) / 2
    
    $Person1 = @{}
    $Person2 = @{}
    
    $Total1 = 0
    $Total2 = 0
    
    foreach ($Item in $DescendingOrder) {
    
        $Time = $Taches[$Item]
        $Choice = Get-Random 2
    
        if (($Choice -eq 0) -and (($Total1 + $Time) -gt $UnfairLimit)) {
            $Choice = 1
        }
    
        if (($Choice -eq 1) -and (($Total2 + $Time) -gt $UnfairLimit)) {
            $Choice = 0
        }
    
        if ($Choice -eq 0) {
            $Person1[$Item] = $Time
            $Total1 += $Time
        } else {
            $Person2[$Item] = $Time
            $Total2 += $Time
        }
    }
    

    示例运行:

    PS> $Person1 | ConvertTo-Json
    
    {
        "Comptoir":  5,
        "Lavabos":  10,
        "Litières":  5,
        "Couvertures lit":  5,
        "Douche":  15,
        "Lave-Vaisselle":  10
    }
    

    另一个人:

    PS> $Person2 | ConvertTo-Json
    
    {
        "Moppe plancher":  20,
        "Toilette":  5,
        "Balayeuse plancher":  20,
        "Poubelles":  5,
        "Poele":  5
    }
    

    以牺牲公平为代价的随机性

    这种方法是随机化列表,遍历每个项目,然后将其分配给迄今为止分配给它们的时间最少的人。

    早期的决定可能意味着以后的决定最终是不公平的。

    $RandomOrder = $Taches.Keys | Sort-Object { Get-Random }
    
    $Person1 = @{}
    $Person2 = @{}
    
    $Total1 = 0
    $Total2 = 0
    
    foreach ($Item in $RandomOrder) {
    
        $Time = $Taches[$Item]
    
        if ($Total1 -lt $Total2) {
            $Person1[$Item] = $Time
            $Total1 += $Time
        } else {
            $Person2[$Item] = $Time
            $Total2 += $Time
        }
    }
    

    示例运行:

    PS> $Person1 | ConvertTo-Json
    
    {
        "Poele":  5,
        "Douche":  15,
        "Couvertures lit":  5,
        "Lave-Vaisselle":  10,
        "Balayeuse plancher":  20,
        "Toilette":  5
    }
    

    另一个人:

    PS> $Person2 | ConvertTo-Json
    
    {
        "Lavabos":  10,
        "Comptoir":  5,
        "Poubelles":  5,
        "Litières":  5,
        "Moppe plancher":  20
    }
    
        2
  •  2
  •   Kory Gill    6 年前

    你也许应该编写一个算法,让你总是在舍入误差中承担额外的任务(幸福的妻子,幸福的生活)。

    这可能是设计过度,但我对这个问题很感兴趣,并在这个过程中学习了一些法语。

    $Taches = @{
    "Balayeuse plancher" = 20
    "Moppe plancher" = 20
    "Douche" = 15
    "Litières" = 5
    "Poele" = 5
    "Comptoir" = 5
    "Lave-Vaisselle" = 10
    "Toilette" = 5
    "Lavabos" = 10
    "Couvertures lit" = 5
    "Poubelles" = 5
    }
    
    $target = 0
    $epsilon = 5
    
    # copy if you don't want to destroy original list (not needed probably)
    # put all entries in first list.
    # randomly move entry to p2 if count over target +/- epsilon 
    # randomly move entry from p2 if count under target +/- epsilon 
    # (unless you know you can always get exactly target and not loop forever trying)
    $p1 = @{} # person 1
    $p2 = @{} # person 2
    $p1Total = 0 # optimizaton to not have to walk entire list and recalculate constantly
    $p2Total = 0 # might as well track this too...
    $Taches.Keys | % {
        $p1.Add($_, $Taches[$_])
        $p1Total += $Taches[$_]
        $target += $Taches[$_]
        }
    
    $target = $target / 2
    
    $done = $false
    while (-not $done)
    {
        if ($p1Total -gt ($target+$epsilon))
        {
            $item = $p1.Keys | Get-Random
            $value = $p1[$item]
            $p1.Remove($item)
            $p2.Add($item, $value)
            $p1Total -= $value
            $p2Total += $value
            continue
        }
        elseif ($p1Total -lt ($target-$epsilon))
        {
            $item = $p2.Keys | Get-Random
            $value = $p2[$item]
            $p2.Remove($item)
            $p1.Add($item, $value)
            $p1Total += $value
            $p2Total -= $value
            continue
        }
    
        $done = $true
    }
    
    "Final result"
    "p1"
    $p1Total
    $p1
    
    "`np2"
    $p2Total
    $p2
    
        3
  •  1
  •   JosefZ    6 年前

    另一种方法是:

    $MinSum  = ($Taches.Values | Measure-Object -Minimum ).Minimum
    $HalfSum = ($Taches.Values | Measure-Object -Sum ).Sum / 2
    do {
        $sum = 0
        $All = $Taches.GetEnumerator() | 
            Get-Random -Count $Taches.Keys.Count
        $Me = $All | ForEach-Object { 
            if ( $Sum -lt $HalfSum - $MinSum ) { 
                $Sum += $_.value
                @{ $_.Key = $_.Value }
            }
        }
        Write-Host "$sum " -NoNewline   # debugging output
    }  until ($sum -eq 50 )
    
    $Em = $Taches.Keys | ForEach-Object {
        if ( $_ -notin $Me.Keys ) {
            @{ $_ = $Taches.$_ }
        }
    }
    # show "fairness" (task count vs. task cost) 
    $Me.Values | Measure-Object -Sum | Select-Object -Property Count, Sum
    $Em.Values | Measure-Object -Sum | Select-Object -Property Count, Sum
    

    样本输出 (S):

    PS D:\PShell> D:\PShell\SO\54610011.ps1
    50 
    Count Sum
    ----- ---
        4  50
        7  55
    
    PS D:\PShell> D:\PShell\SO\54610011.ps1
    65 65 50 
    Count Sum
    ----- ---
        6  50
        5  55
    
        4
  •  0
  •   Rakha    6 年前

    回答很好,伙计们,学到了很多。以下是我在Reddit上的“Fischfreund”所做的事情( https://www.reddit.com/r/PowerShell/comments/aovs8s/get_random_items_from_hashtable_but_the_total_of/eg3ytds )

    他的方法非常简单,但我一点也没想到。

    第一个哈希表:随机数5,直到和为50。然后创建第二个哈希表,其中项不在第一个哈希表中!我将第一个包含5个项目的hahstable分配给我的妻子,所以我总是有一个额外的任务(就像kory建议的那样)。我安全了。

    $Taches = @{
    
        "Balayeuse plancher" = 20
        "Moppe plancher"     = 20
        "Douche"             = 15
        "Litières"           = 5
        "Poele"              = 5
        "Comptoir"           = 5
        "Lave-Vaisselle"     = 10
        "Toilette"           = 5
        "Lavabos"            = 10
        "Couvertures lit"    = 5
        "Poubelles"          = 5
    
    }
    
    do {
    $Selection1 = $Taches.GetEnumerator() | Get-Random -Count 5
    } until (($Selection1.Value | measure -Sum ).Sum -eq 50)
    
    
    $Selection2 = $Taches.GetEnumerator() | Where-Object {$_ -notin $Selection1}
    
    
    $Selection1 | select-object @{Name="Personne";expression={"Wife"} },Name,Value
    ""
    $Selection2 | select-object @{Name="Personne";expression={"Me"} },Name,Value