在给定的用户列表中分发

时间:2015-04-03 05:39:02

标签: c# algorithm c#-4.0 distribution

我有一个要求,我有一个可以分配该实体的实体和用户列表

  

E1可以通过U1或U2分发

     

E2必须由U5分发

     

E3可以通过U2或U3或U4分发

我有50K个实体,每个实体可能有1个或更多用户。对于1个用户,其clear和entity将仅分配给该用户。如果有多个用户,可以将其分配给任何一个用户。

我们希望分发它,以便每个用户获得相同数量的实体。并且存在最小可能/不可避免的偏差分布,每个用户可能已经拥有一些实体:U1已经有2K而U2已经有3K entitis,所以分布也应该照顾这个事实。

编辑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个。精细。但看到结果偏差

EDIT2

为什么每个实体都有不同的用户?有多种产品需要分发。一些用户处理多个产品,一些用户处理单个产品,但仍然需要对所有用户进行负载平衡。

3 个答案:

答案 0 :(得分:1)

我可能误解了你的问题,但是,根据我对你的描述的理解,这个问题似乎很简单。

创建新实体

Is that entity a "single user entity" ?
   Assign it to the given user
   : Assign it to the user that have the least number of entities. If you find users with the same number of instances, it doesn't matter, assign it to an arbitrary user.

关于重新定位现有实体

如果您想“重新分配”已分配的实体:

Take the number of entities you want to reallocate and divide it by the number of users and allocate them accordingly

答案 1 :(得分:1)

我是通过几个步骤完成的。

  1. 仅使用一个用户分配所有实体
  2. 按顺序分配剩余的实体
  3. 在用户之间平衡实体
  4. 通过分发任何差异来修复剩余问题
  5. 原始代码

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

    编辑1: 我对上面的代码做了一些更改。

    1. 现在,当创建实体列表时,程序会向实体添加一个随机潜在用户列表,这应该更符合您的真实场景
    2. 该程序现在运行所有实体,并根据该实体的哪些允许用户拥有最少的实体将其分配给用户
    3. 然后程序将反复尝试并平衡用户之间的实体,直到没有任何改变
    4. 代码会在之后进行检查,以确保分配给实体的用户实际上是在指定的用户中。

      有时最终结果分配是1或2个不同于理想情况的实体,但所有实体都被分配。

      代码:

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

答案 2 :(得分:0)

您可以维护一个基于CountOfEntities排序的TreeMap。每次在TreeMap(0)中为用户分配实体时,都会递增CountOfEntities。