从哈希表中获取随机项目,但值的总和必须等于设定的数字
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
我正在尝试为我和我妻子之间的家务任务构建一个简单的 "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