从哈希表中获取随机项目,但值的总和必须等于设定的数字

Get random items from hashtable but the total of values has to be equal to a set number

我正在尝试为我和我妻子之间的家务任务构建一个简单的 "task distributor"。虽然这个概念在工作中也会非常有用,所以我需要好好学习它。

我的哈希表:

$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 分钟分成两部分。

我的目标:

我想 select 从该哈希表中随机选择项目并构建两个不同的哈希表 - 一个给我和我妻子,每个哈希表的总值为 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)

结果示例:

名称值
---- -----
布贝勒斯 5
Balayeuse 刨床 20
冲洗 15
波尔 5
厕所 5

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

你无法同时最大化随机性和公平性,所以你不得不放弃。我认为你不应该冒险对你的妻子不公平,所以公平必须占上风!

以随机性为代价的公平性

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

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

$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
}

您可能应该编写算法以始终让您在舍入误差中完成额外的任务(幸福的妻子,幸福的生活)。

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

$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

伙计们回答得很好,学到了很多东西。感谢 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

另一种方法:

$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\SO610011.ps1
50 
Count Sum
----- ---
    4  50
    7  55

PS D:\PShell> D:\PShell\SO610011.ps1
65 65 50 
Count Sum
----- ---
    6  50
    5  55