C# 在给定用户列表中的分布
我有一个要求,即我有一个实体列表和可以分配该实体的用户 E1可以由U1或U2分配 E2必须由U5分配 E3可以由U2或U3或U4分配 我有这样的50K个实体,每个实体可能有1个或多个用户。对于1个用户,其clear和entity将仅分配给该用户。如果有多个用户,可以将其分配给其中的任何一个用户 我们希望将其分发给每个用户,以便每个用户获得相等数量的实体。存在最小可能/不可避免的扭曲分布,而且每个用户可能已经拥有一些实体:U1已经有2K,U2已经有3K实体,因此分布也应该考虑这一事实 编辑1 我们已经尝试了一种解决方案,即按顺序进行,并根据当时分配给用户的分配一次分配一个实体,但这会产生扭曲的结果,因为我们得到的用户在较早时分配较少,但在较晚时分配较多,反之亦然 E1至E25“必须由U1和U2中的任何一个处理 E26至E50“必须由U2和U3中的任何一个处理 如果我们按顺序进行,最后:U1得到12(从E1-E25),U2得到19(从E1-E25得到13,从E26-E50得到6),U3得到19(从E26-E50)。 所以总共分配了50个。好的但看到了扭曲的结果吗 EDIT2C# 在给定用户列表中的分布,c#,algorithm,c#-4.0,distribution,C#,Algorithm,C# 4.0,Distribution,我有一个要求,即我有一个实体列表和可以分配该实体的用户 E1可以由U1或U2分配 E2必须由U5分配 E3可以由U2或U3或U4分配 我有这样的50K个实体,每个实体可能有1个或多个用户。对于1个用户,其clear和entity将仅分配给该用户。如果有多个用户,可以将其分配给其中的任何一个用户 我们希望将其分发给每个用户,以便每个用户获得相等数量的实体。存在最小可能/不可避免的扭曲分布,而且每个用户可能已经拥有一些实体:U1已经有2K,U2已经有3K实体,因此分布也应该考虑这一事实 编辑1 我们
为什么每个实体有不同的用户?要分发多个产品。有些用户处理多个产品,有些用户处理单个产品,但所有用户都需要负载平衡。我可能误解了您的问题您的问题,但从我对您描述的理解来看,问题似乎很简单 关于创建新实体 论现有实体的重新配置 如果要“重新分配”已分配的实体:
Take the number of entities you want to reallocate and divide it by the number of users and allocate them accordingly
您好,您可以维护一个基于实体数排序的树形图吗。每次在树映射(0)处将实体分配给用户时,都会增加实体数 我分两步做了这件事
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
public class Entity
{
public string Type { get; set; }
}
public class User
{
public User()
{
EntitiesCount = new Dictionary<string, int>();
}
public string userId { get; set; }
public Dictionary<string, int> EntitiesCount { get; set; }
public int TotalEntities { get; set; }
public bool Ignored { get; set; }
}
class Program
{
static void Main(string[] args)
{
for (var myLoop = 0; myLoop < 100; myLoop++)
{
//Create users
var userList = new List<User> {
new User { userId = "U0" } ,
new User { userId = "U1" } ,
new User { userId = "U2" } ,
new User { userId = "U3" } ,
new User { userId = "U4" }
};
userList = userList.OrderBy(u => u.userId).ToList();
//Assign Users to Entities
var entityUsers = new Dictionary<string, List<User>>() {
{ "E0",
new List<User> {
userList[0] ,
userList[1]
}
} ,
{ "E1",
new List<User> {
userList[4]
}
} ,
{ "E2",
new List<User> {
userList[1],
userList[2],
userList[3]
}
}
};
//var entityUsers = new Dictionary<string, List<User>>() {
// { "E0",
// new List<User> {
// userList[0] ,
// userList[1]
// }
// } ,
// { "E1",
// new List<User> {
// userList[1],
// userList[2],
// }
// } ,
// };
//Load Entities, you can change the number of entities generated her
var entities = GenerateEntities(entityUsers.Count(), 50000);
//Group the Entities by their type and display total number
var lookupEntities = entities.ToLookup(e => e.Type);
foreach (var lookupEntity in lookupEntities)
{
Console.WriteLine(lookupEntity.Key + " has " + lookupEntity.Count());
}
// Users are ignored if there is a one to one mapping
var ignoreUsers = 0;
//Entities are ignored if they are only handled by one user
var ignoreEntities = 0;
foreach (var entityUser in entityUsers)
{
foreach (var user in entityUser.Value)
{
user.EntitiesCount.Add(entityUser.Key, 0);
}
}
//Assign entities where only one user available
foreach (var entityUser in entityUsers.Where(a => a.Value.Count == 1))
{
Console.WriteLine("Assigning all " + entityUser.Key + " to " + entityUser.Value[0].userId + " - " + lookupEntities[entityUser.Key].Count());
entityUser.Value[0].TotalEntities += lookupEntities[entityUser.Key].Count();
entityUser.Value[0].EntitiesCount[entityUser.Key] = lookupEntities[entityUser.Key].Count();
//Ignore these entities because they cannot changed
ignoreEntities += entityUser.Value[0].TotalEntities;
if (entityUsers.Count(e => e.Value.Contains(entityUser.Value[0])) == 1)
{
//The user is only assigned to this one entity so ignore user in balancing
ignoreUsers++;
entityUser.Value[0].Ignored = true;
}
}
//Assign entities where more than one user available
foreach (var entityUser in entityUsers.Where(a => a.Value.Count > 1))
{
var numberOfEntities = lookupEntities[entityUser.Key].Count();
for (var i = 0; i < numberOfEntities; i++)
{
var user = entityUser.Value.OrderBy(u => u.TotalEntities).First();
if (!user.EntitiesCount.ContainsKey(entityUser.Key))
user.EntitiesCount.Add(entityUser.Key, 0);
user.EntitiesCount[entityUser.Key]++;
user.TotalEntities++;
}
}
var averagePerUser = 0;
var busyUsers = userList.Count(a => a.TotalEntities != 0);
//Check to see if there is only one users assigned to each entity
if (busyUsers != ignoreUsers)
{
//Calculate the expected average per user
var totalEntities = entities.Count;
averagePerUser = (totalEntities - ignoreEntities) / (busyUsers - ignoreUsers);
Console.WriteLine();
Console.WriteLine("Total Entities: " + totalEntities);
Console.WriteLine("Average Entities: " + averagePerUser);
Console.WriteLine();
OutputAllocation(userList, averagePerUser);
var orderedUserList = userList.OrderByDescending(u => u.TotalEntities).ToList();
//Loop through the users and compare to the remaining users
for (var i = 0; i < orderedUserList.Count - 1; i++)
{
for (var j = i + 1; j < userList.Count; j++)
{
BalanceUsers(userList[i], userList[j], entityUsers, averagePerUser);
}
}
////Loop through the list in reverse order ?
//for (var i = userList.Count - 1; i >= 0; i--)
//{
// for (var j = i - 1; j >= 0; j--)
// {
// BalanceUsers(userList[i], userList[j], entityUsers, averagePerUser);
// }
//}
}
OutputAllocation(userList, averagePerUser);
Console.WriteLine("Total assigned: " + userList.Sum(u => u.TotalEntities));
Console.WriteLine();
//Even out remaining difference across entity Type
foreach (var entityUser in entityUsers.Where(a => a.Value.Count > 1))
{
if (entityUser.Value.Any(u => (u.TotalEntities - averagePerUser > 0)))
{
var users = entityUser.Value.Where(u => (u.TotalEntities - averagePerUser != 0) && u.EntitiesCount[entityUser.Key] > 0);
var difference = 0;
foreach (var user in users)
{
difference += user.TotalEntities - averagePerUser;
user.TotalEntities -= difference;
user.EntitiesCount[entityUser.Key] -= difference;
}
List<User> fixUsers = null;
if (difference < 0)
{
fixUsers = entityUser.Value.Where(u => (u.EntitiesCount[entityUser.Key] > 0)).ToList();
}
else
{
fixUsers = entityUser.Value;
}
var change = difference / fixUsers.Count();
var userCount = fixUsers.Count();
foreach (var fixUser in fixUsers)
{
fixUser.TotalEntities += change;
fixUser.EntitiesCount[entityUser.Key] += change;
difference -= change;
userCount--;
//Correct change so that nothing gets lost
if (userCount != 0)
change = difference / userCount;
else
change = difference;
}
}
}
OutputAllocation(userList, averagePerUser);
Console.WriteLine("Total assigned: " + userList.Sum(u => u.TotalEntities));
Console.WriteLine();
foreach (var lookupEntity in lookupEntities)
{
Console.Write(lookupEntity.Key + " - " + lookupEntity.Count());
Console.Write(" Allocation: ");
foreach (User user in entityUsers[lookupEntity.Key])
{
Debug.Assert(user.EntitiesCount[lookupEntity.Key] >= 0);
Console.Write(user.userId + " = " + user.EntitiesCount[lookupEntity.Key] + "; ");
}
Console.WriteLine();
}
}
Console.ReadLine();
}
private static void OutputAllocation(List<User> userList, int averagePerUser)
{
//Display allocation after initial assignment
foreach (var user in userList)
{
var difference = user.TotalEntities - averagePerUser;
if (user.Ignored)
Console.WriteLine("Assignment " + user.userId + " has " + user.TotalEntities);
else
Console.WriteLine("Assignment " + user.userId + " has " + user.TotalEntities + " difference " + difference);
}
Console.WriteLine("Total assigned: " + userList.Sum(u => u.TotalEntities));
Console.WriteLine();
}
/// <summary>
/// Compares two users and balances them out
/// </summary>
private static void BalanceUsers(User firstUser, User secondUser, Dictionary<string, List<User>> entityUsers, int averagePerWorker)
{
//Get the difference betweent the current users and the average worker
var firstUserDiff = firstUser.TotalEntities - averagePerWorker;
var secondUserDiff = secondUser.TotalEntities - averagePerWorker;
//Get all the entities which the two users share
var sharedEntityTypes = entityUsers.Where(x => x.Value.Contains(firstUser) && x.Value.Contains(secondUser)).Select(e => e.Key);
foreach (var entityType in sharedEntityTypes)
{
var difference = firstUserDiff;
if (firstUser.EntitiesCount.Count() > secondUser.EntitiesCount.Count())
{
difference = -1 * secondUserDiff;
}
else if (firstUser.EntitiesCount.Count() == secondUser.EntitiesCount.Count())
{
difference = firstUserDiff - secondUserDiff;
}
else
{
difference = firstUserDiff;
}
difference = firstUserDiff;
var maxAllowed = 0;
if (difference > 0)
{
maxAllowed = firstUser.EntitiesCount[entityType] > difference ? difference : firstUser.EntitiesCount[entityType];
}
else
{
maxAllowed = secondUser.EntitiesCount[entityType] > Math.Abs(difference) ? difference : -1 * secondUser.EntitiesCount[entityType];
}
firstUser.EntitiesCount[entityType] -= maxAllowed;
firstUser.TotalEntities -= maxAllowed;
secondUser.EntitiesCount[entityType] += maxAllowed;
secondUser.TotalEntities += maxAllowed;
firstUserDiff = firstUser.TotalEntities - averagePerWorker;
secondUserDiff = secondUser.TotalEntities - averagePerWorker;
}
}
private static List<Entity> GenerateEntities(int maxEntityTypes, int totalEntities)
{
var entityTypes = new List<string>();
for (var i = 0; i < maxEntityTypes; i++)
{
entityTypes.Add("E" + i);
}
var entities = new List<Entity>();
Random random = new Random();
for (var i = 0; i < totalEntities; i++)
{
//Randomly allocate user
entities.Add(new Entity { Type = entityTypes[random.Next(maxEntityTypes)] });
//Used to get even distribution
//entities.Add(new Entity { Type = entityTypes[i%maxEntityTypes] });
//Used to get specific ratio
//var type = "";
//switch (i % 3)
//{
// case 0:
// type = "E0";
// break;
// case 1:
// case 2:
// type = "E1";
// break;
//}
//entities.Add(new Entity { Type = type });
}
return entities;
}
}
使用系统;
使用System.Collections.Generic;
使用系统诊断;
使用System.Linq;
公共类实体
{
公共字符串类型{get;set;}
}
公共类用户
{
公共用户()
{
EntitiesCount=新字典();
}
公共字符串用户标识{get;set;}
公共字典实体计数{get;set;}
公共整数TotalEntities{get;set;}
公共布尔忽略{get;set;}
}
班级计划
{
静态void Main(字符串[]参数)
{
对于(var myLoop=0;myLoop<100;myLoop++)
{
//创建用户
var userList=新列表{
新用户{userId=“U0”},
新用户{userId=“U1”},
新用户{userId=“U2”},
新用户{userId=“U3”},
新用户{userId=“U4”}
};
userList=userList.OrderBy(u=>u.userId.ToList();
//将用户分配给实体
var entityUsers=new Dictionary(){
{“E0”,
新列表{
用户列表[0],
用户列表[1]
}
} ,
{“E1”,
新列表{
用户列表[4]
}
} ,
{“E2”,
新列表{
用户列表[1],
用户列表[2],
用户列表[3]
}
}
};
//var entityUsers=new Dictionary(){
//{“E0”,
//新列表{
//用户列表[0],
//用户列表[1]
// }
// } ,
//{“E1”,
//新列表{
//用户列表[1],
//用户列表[2],
// }
// } ,
// };
//加载实体时,可以更改生成的实体数
var entities=GenerateEntities(entityUsers.Count(),50000);
//按实体类型对其进行分组并显示总数
var lookupEntities=entities.ToLookup(e=>e.Type);
foreach(lookupEntities中的变量lookupEntity)
{
Console.WriteLine(lookupEntity.Key+“具有”+lookupEntity.Count());
}
//如果存在一对一映射,则忽略用户
var ignoreUsers=0;
//如果实体仅由一个用户处理,则将忽略它们
var ignoreEntities=0;
foreach(entityUsers中的var entityUser)
{
foreach(entityUser.Value中的var user)
{
user.EntitiesCount.Add(entityUser.Key,0);
}
}
//在只有一个用户可用的情况下分配实体
foreach(entityUsers.Where中的var entityUser(a=>a.Value.Count==1))
{
Console.WriteLine(“将所有“+entityUser.Key+”分配给“+entityUser.Value[0].userId+”-“+lookupEntities[entityUser.Key].Count());
实体用户。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
public class Entity
{
public string Type { get; set; }
}
public class User
{
public User()
{
EntitiesCount = new Dictionary<string, int>();
}
public string userId { get; set; }
public Dictionary<string, int> EntitiesCount { get; set; }
public int TotalEntities { get; set; }
public bool Ignored { get; set; }
}
class Program
{
static void Main(string[] args)
{
for (var myLoop = 0; myLoop < 100; myLoop++)
{
//Create users
var userList = new List<User> {
new User { userId = "U0" } ,
new User { userId = "U1" } ,
new User { userId = "U2" } ,
new User { userId = "U3" } ,
new User { userId = "U4" }
};
userList = userList.OrderBy(u => u.userId).ToList();
//Assign Users to Entities
var entityUsers = new Dictionary<string, List<User>>() {
{ "E0",
new List<User> {
userList[0] ,
userList[1]
}
} ,
{ "E1",
new List<User> {
userList[4]
}
} ,
{ "E2",
new List<User> {
userList[1],
userList[2],
userList[3]
}
}
};
//var entityUsers = new Dictionary<string, List<User>>() {
// { "E0",
// new List<User> {
// userList[0] ,
// userList[1]
// }
// } ,
// { "E1",
// new List<User> {
// userList[1],
// userList[2],
// }
// } ,
// };
//Load Entities, you can change the number of entities generated her
var entities = GenerateEntities(entityUsers.Count(), 50000);
//Group the Entities by their type and display total number
var lookupEntities = entities.ToLookup(e => e.Type);
foreach (var lookupEntity in lookupEntities)
{
Console.WriteLine(lookupEntity.Key + " has " + lookupEntity.Count());
}
// Users are ignored if there is a one to one mapping
var ignoreUsers = 0;
//Entities are ignored if they are only handled by one user
var ignoreEntities = 0;
foreach (var entityUser in entityUsers)
{
foreach (var user in entityUser.Value)
{
user.EntitiesCount.Add(entityUser.Key, 0);
}
}
//Assign entities where only one user available
foreach (var entityUser in entityUsers.Where(a => a.Value.Count == 1))
{
Console.WriteLine("Assigning all " + entityUser.Key + " to " + entityUser.Value[0].userId + " - " + lookupEntities[entityUser.Key].Count());
entityUser.Value[0].TotalEntities += lookupEntities[entityUser.Key].Count();
entityUser.Value[0].EntitiesCount[entityUser.Key] = lookupEntities[entityUser.Key].Count();
//Ignore these entities because they cannot changed
ignoreEntities += entityUser.Value[0].TotalEntities;
if (entityUsers.Count(e => e.Value.Contains(entityUser.Value[0])) == 1)
{
//The user is only assigned to this one entity so ignore user in balancing
ignoreUsers++;
entityUser.Value[0].Ignored = true;
}
}
//Assign entities where more than one user available
foreach (var entityUser in entityUsers.Where(a => a.Value.Count > 1))
{
var numberOfEntities = lookupEntities[entityUser.Key].Count();
for (var i = 0; i < numberOfEntities; i++)
{
var user = entityUser.Value.OrderBy(u => u.TotalEntities).First();
if (!user.EntitiesCount.ContainsKey(entityUser.Key))
user.EntitiesCount.Add(entityUser.Key, 0);
user.EntitiesCount[entityUser.Key]++;
user.TotalEntities++;
}
}
var averagePerUser = 0;
var busyUsers = userList.Count(a => a.TotalEntities != 0);
//Check to see if there is only one users assigned to each entity
if (busyUsers != ignoreUsers)
{
//Calculate the expected average per user
var totalEntities = entities.Count;
averagePerUser = (totalEntities - ignoreEntities) / (busyUsers - ignoreUsers);
Console.WriteLine();
Console.WriteLine("Total Entities: " + totalEntities);
Console.WriteLine("Average Entities: " + averagePerUser);
Console.WriteLine();
OutputAllocation(userList, averagePerUser);
var orderedUserList = userList.OrderByDescending(u => u.TotalEntities).ToList();
//Loop through the users and compare to the remaining users
for (var i = 0; i < orderedUserList.Count - 1; i++)
{
for (var j = i + 1; j < userList.Count; j++)
{
BalanceUsers(userList[i], userList[j], entityUsers, averagePerUser);
}
}
////Loop through the list in reverse order ?
//for (var i = userList.Count - 1; i >= 0; i--)
//{
// for (var j = i - 1; j >= 0; j--)
// {
// BalanceUsers(userList[i], userList[j], entityUsers, averagePerUser);
// }
//}
}
OutputAllocation(userList, averagePerUser);
Console.WriteLine("Total assigned: " + userList.Sum(u => u.TotalEntities));
Console.WriteLine();
//Even out remaining difference across entity Type
foreach (var entityUser in entityUsers.Where(a => a.Value.Count > 1))
{
if (entityUser.Value.Any(u => (u.TotalEntities - averagePerUser > 0)))
{
var users = entityUser.Value.Where(u => (u.TotalEntities - averagePerUser != 0) && u.EntitiesCount[entityUser.Key] > 0);
var difference = 0;
foreach (var user in users)
{
difference += user.TotalEntities - averagePerUser;
user.TotalEntities -= difference;
user.EntitiesCount[entityUser.Key] -= difference;
}
List<User> fixUsers = null;
if (difference < 0)
{
fixUsers = entityUser.Value.Where(u => (u.EntitiesCount[entityUser.Key] > 0)).ToList();
}
else
{
fixUsers = entityUser.Value;
}
var change = difference / fixUsers.Count();
var userCount = fixUsers.Count();
foreach (var fixUser in fixUsers)
{
fixUser.TotalEntities += change;
fixUser.EntitiesCount[entityUser.Key] += change;
difference -= change;
userCount--;
//Correct change so that nothing gets lost
if (userCount != 0)
change = difference / userCount;
else
change = difference;
}
}
}
OutputAllocation(userList, averagePerUser);
Console.WriteLine("Total assigned: " + userList.Sum(u => u.TotalEntities));
Console.WriteLine();
foreach (var lookupEntity in lookupEntities)
{
Console.Write(lookupEntity.Key + " - " + lookupEntity.Count());
Console.Write(" Allocation: ");
foreach (User user in entityUsers[lookupEntity.Key])
{
Debug.Assert(user.EntitiesCount[lookupEntity.Key] >= 0);
Console.Write(user.userId + " = " + user.EntitiesCount[lookupEntity.Key] + "; ");
}
Console.WriteLine();
}
}
Console.ReadLine();
}
private static void OutputAllocation(List<User> userList, int averagePerUser)
{
//Display allocation after initial assignment
foreach (var user in userList)
{
var difference = user.TotalEntities - averagePerUser;
if (user.Ignored)
Console.WriteLine("Assignment " + user.userId + " has " + user.TotalEntities);
else
Console.WriteLine("Assignment " + user.userId + " has " + user.TotalEntities + " difference " + difference);
}
Console.WriteLine("Total assigned: " + userList.Sum(u => u.TotalEntities));
Console.WriteLine();
}
/// <summary>
/// Compares two users and balances them out
/// </summary>
private static void BalanceUsers(User firstUser, User secondUser, Dictionary<string, List<User>> entityUsers, int averagePerWorker)
{
//Get the difference betweent the current users and the average worker
var firstUserDiff = firstUser.TotalEntities - averagePerWorker;
var secondUserDiff = secondUser.TotalEntities - averagePerWorker;
//Get all the entities which the two users share
var sharedEntityTypes = entityUsers.Where(x => x.Value.Contains(firstUser) && x.Value.Contains(secondUser)).Select(e => e.Key);
foreach (var entityType in sharedEntityTypes)
{
var difference = firstUserDiff;
if (firstUser.EntitiesCount.Count() > secondUser.EntitiesCount.Count())
{
difference = -1 * secondUserDiff;
}
else if (firstUser.EntitiesCount.Count() == secondUser.EntitiesCount.Count())
{
difference = firstUserDiff - secondUserDiff;
}
else
{
difference = firstUserDiff;
}
difference = firstUserDiff;
var maxAllowed = 0;
if (difference > 0)
{
maxAllowed = firstUser.EntitiesCount[entityType] > difference ? difference : firstUser.EntitiesCount[entityType];
}
else
{
maxAllowed = secondUser.EntitiesCount[entityType] > Math.Abs(difference) ? difference : -1 * secondUser.EntitiesCount[entityType];
}
firstUser.EntitiesCount[entityType] -= maxAllowed;
firstUser.TotalEntities -= maxAllowed;
secondUser.EntitiesCount[entityType] += maxAllowed;
secondUser.TotalEntities += maxAllowed;
firstUserDiff = firstUser.TotalEntities - averagePerWorker;
secondUserDiff = secondUser.TotalEntities - averagePerWorker;
}
}
private static List<Entity> GenerateEntities(int maxEntityTypes, int totalEntities)
{
var entityTypes = new List<string>();
for (var i = 0; i < maxEntityTypes; i++)
{
entityTypes.Add("E" + i);
}
var entities = new List<Entity>();
Random random = new Random();
for (var i = 0; i < totalEntities; i++)
{
//Randomly allocate user
entities.Add(new Entity { Type = entityTypes[random.Next(maxEntityTypes)] });
//Used to get even distribution
//entities.Add(new Entity { Type = entityTypes[i%maxEntityTypes] });
//Used to get specific ratio
//var type = "";
//switch (i % 3)
//{
// case 0:
// type = "E0";
// break;
// case 1:
// case 2:
// type = "E1";
// break;
//}
//entities.Add(new Entity { Type = type });
}
return entities;
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
public class Entity
{
public Entity()
{
_users = new List<User>();
}
public string Type { get; set; }
public List<User> _users;
public List<User> Users
{
get
{
//You can add your rules for users here
return _users;
}
}
public User AssignedUser { get; set; }
}
public class User
{
public User()
{
Entities = new Dictionary<string, Entity>();
}
public string userId { get; set; }
public Dictionary<string, Entity> Entities { get; set; }
public int TotalEntities { get; set; }
public bool Ignored { get; set; }
}
class Program
{
static void Main(string[] args)
{
//Load Entities, you can change the number of entities generated here
int numEntityToGenerate = 50001;
//Create users
var userList = new List<User> {
new User { userId = "U0" } ,
new User { userId = "U1" } ,
new User { userId = "U2" } ,
new User { userId = "U3" } ,
new User { userId = "U4" }
};
userList = userList.OrderBy(u => u.userId).ToList();
var entities = GenerateEntities(userList, numEntityToGenerate);
foreach (var entity in entities)
{
foreach (var user in entity.Users)
{
user.Entities.Add(entity.Type, null);
}
}
foreach (var entity in entities)
{
Console.Write(".");
var user = entity.Users.OrderBy(u => u.TotalEntities).First();
if (!user.Entities.ContainsKey(entity.Type))
user.Entities.Add(entity.Type, null);
user.Entities[entity.Type] = entity;
user.TotalEntities++;
entity.AssignedUser = user;
}
var averagePerUser = 0;
var busyUsers = userList.Count(a => a.TotalEntities != 0);
//Calculate the expected average per user
var totalEntities = entities.Count;
averagePerUser = (totalEntities) / (busyUsers);
Console.WriteLine();
Console.WriteLine("Total Entities: " + totalEntities);
Console.WriteLine("Average Entities: " + averagePerUser);
Console.WriteLine("Busy Users: " + busyUsers);
Console.WriteLine();
List<int> oldDifference = null;
List<int> newDifference = null;
do
{
oldDifference = GetDifferenceList(userList, averagePerUser);
OutputAllocation(userList, averagePerUser);
var orderedUserList = userList.OrderByDescending(u => u.TotalEntities).ToList();
//Loop through the users and compare to the remaining users
for (var i = 0; i < orderedUserList.Count - 1; i++)
{
for (var j = i + 1; j < userList.Count; j++)
{
BalanceUsers(userList[i], userList[j], entities, averagePerUser);
}
}
newDifference = GetDifferenceList(userList, averagePerUser);
} while (!Enumerable.SequenceEqual(oldDifference.OrderBy(t => t), newDifference.OrderBy(t => t)));
Console.WriteLine("Total assigned: " + userList.Sum(u => u.TotalEntities));
Console.WriteLine();
OutputAllocation(userList, averagePerUser);
Console.WriteLine("Total assigned: " + userList.Sum(u => u.TotalEntities));
Console.WriteLine();
//Check data quality
foreach (var entity in entities)
{
//Check to see if assigned user is valid
Debug.Assert(entity.Users.Contains(entity.AssignedUser));
}
Console.ReadLine();
}
private static List<int> GetDifferenceList(List<User> userList, int averagePerUser)
{
var differences = new List<int>();
foreach (var user in userList)
{
var difference = user.TotalEntities - averagePerUser;
if (user.TotalEntities != 0)
differences.Add(difference);
}
return differences;
}
private static void OutputAllocation(List<User> userList, int averagePerUser)
{
//Display allocation after initial assignment
foreach (var user in userList)
{
var difference = user.TotalEntities - averagePerUser;
if (user.TotalEntities == 0)
Console.WriteLine("Assignment " + user.userId + " has " + user.TotalEntities);
else
Console.WriteLine("Assignment " + user.userId + " has " + user.TotalEntities + " difference " + difference);
}
Console.WriteLine("Total assigned: " + userList.Sum(u => u.TotalEntities));
Console.WriteLine();
}
/// <summary>
/// Compares two users and balances them out
/// </summary>
private static void BalanceUsers(User firstUser, User secondUser, List<Entity> entities, int averagePerWorker)
{
//Get the difference betweent the current users and the average worker
var firstUserDiff = firstUser.TotalEntities - averagePerWorker;
var secondUserDiff = secondUser.TotalEntities - averagePerWorker;
if ((firstUserDiff != 0 && secondUserDiff != 0) && Math.Abs(firstUserDiff - secondUserDiff) > 1)
{
//Get all the entities which the two users share
var sharedEntity = entities.Where(x => x.Users.Contains(firstUser) && x.Users.Contains(secondUser));
foreach (var entity in sharedEntity)
{
//Find out the direction the change needs to occur
if (firstUserDiff >= secondUserDiff)
{
//Removing from firstUser so find out if it has the entity
if (firstUser.Entities[entity.Type] != null)
{
firstUser.Entities[entity.Type] = null;
firstUser.TotalEntities--;
secondUser.Entities[entity.Type] = entity;
secondUser.TotalEntities++;
entity.AssignedUser = secondUser;
}
}
else
{
//Removing from secondUser so find out if it has the entity
if (secondUser.Entities[entity.Type] != null)
{
firstUser.Entities[entity.Type] = entity;
firstUser.TotalEntities++;
secondUser.Entities[entity.Type] = null;
secondUser.TotalEntities--;
entity.AssignedUser = firstUser;
}
}
firstUserDiff = firstUser.TotalEntities - averagePerWorker;
secondUserDiff = secondUser.TotalEntities - averagePerWorker;
//Check to see if the two users have been balanced or if the difference is only one
//IF that is the case break the for loop
if ((firstUserDiff != 0 && secondUserDiff != 0) && (firstUserDiff == secondUserDiff) && Math.Abs(firstUserDiff - secondUserDiff) <= 1)
break;
}
}
}
/// <summary>
/// Generate a list of entities randomly adding a list of potential users to each entity
/// </summary>
/// <param name="userList">list of available users</param>
/// <param name="totalEntities">Total number of entities required</param>
/// <returns>A list of entities</returns>
private static List<Entity> GenerateEntities(List<User> userList, int totalEntities)
{
var entities = new List<Entity>();
Random random = new Random();
for (var i = 0; i < totalEntities; i++)
{
var entity = new Entity { Type = "E" + (i + 1).ToString() };
entities.Add(entity);
//This code will either an entity to the last user or to a random list of users excluding the last one
if (random.Next(12) == 0)
{
var user = userList[userList.Count() - 1];
entity.Users.Add(user);
}
else
{
var numOfUsers = random.Next(2);
for (var j = 0; j <= numOfUsers; j++)
{
var user = userList[random.Next(userList.Count() - 1)];
if (!entity.Users.Contains(user))
entity.Users.Add(user);
}
}
//if (i <= totalEntities / 2 )
//{
// entity.Users.Add(userList[0]);
// entity.Users.Add(userList[1]);
//}
//else
//{
// entity.Users.Add(userList[2]);
// entity.Users.Add(userList[1]);
//}
}
return entities;
}
}