I have a question about the hungarian method for assigment problems.
In the examples I found from the hungarian method you have 1 to n prefereces.
So at the moment, we have in the school the task to create a programm where you have a school class (1 to n students). The students take exactly ONE gift to the class. So (1 to amount of students )
After that every student hast exactly three CHOICES(to pick one gift, e.g. gift 1, gift 5, gift 9) and our programm is supossed to output the best assignment for the class.
But as mentioned before the examples we found about the hungarian methods have 1- to n preferences. And we need exactly three.
How would we solve this specific task ?
Is the hungarian method still the best way to solve this task or should we look at another algorithm ?
I just saw your last question get closed without an answer.
I've implemented a practical way to solve this problem in C#, assuming the wishes are given in a csv file of the following format:
2,10,6
2,7,3
4,7,1
...
The number of columns, i.e. the number of preferences, doesn't matter for my implementation.
Obviously, there's room for performance improvements, but I chose to keep it more readable for the sake of the answer.
Here are the classes used in the code below:
public class Student
{
public int StudentNumber { get; set; }
public List<WishVote> WishVotes { get; set; }
}
public class WishVote
{
public int WishNumber { get; set; }
public int Order { get; set; }
public string Id { get; set; }
}
public class WishVoteResult
{
public int WishNumber { get; set; }
// wish order
// vote count for that order
// wish number
public List<Tuple<int, int, int>> Assignments { get; set; }
public int TotalVoteCount { get; set; }
}
And here is the code you could run in Main to output the wishes' numbers alongside the total number of votes in descending order:
var lines = File.ReadAllLines("wishes.csv").ToList();
int studentNumber = lines.Count;
var students = new List<Student>();
int currentLine = 0;
lines.ForEach(l =>
{
currentLine++;
var wishVotes = new List<WishVote>();
int wishOrder = 0;
l.Split(',').ToList().ForEach(w =>
{
wishOrder++;
wishVotes.Add(new WishVote
{
Id = Guid.NewGuid().ToString(),
Order = wishOrder,
WishNumber = Convert.ToInt32(w)
});
});
students.Add(new Student
{
StudentNumber = currentLine,
WishVotes = wishVotes
});
});
var allWishVotes = students.SelectMany(s => s.WishVotes).ToList();
List<int> uniqueWishes = allWishVotes.Select(w => w.WishNumber).Distinct().ToList();
var wishVoteResults = new List<WishVoteResult>();
// assuming every row in the file has the same number of columns
int orderCount = students.First().WishVotes.Max(w => w.Order);
uniqueWishes.ForEach(uw =>
{
var wishVoteResult = new WishVoteResult
{
WishNumber = uw,
TotalVoteCount = allWishVotes.Where(w => w.WishNumber == uw).Count(),
Assignments = new List<Tuple<int, int, int>>()
};
for(int i = 1; i <= orderCount; i++)
{
wishVoteResult.Assignments.Add(new Tuple<int, int, int>(i, allWishVotes.Where(w => w.Order == i && w.WishNumber == uw).Count(), uw));
}
wishVoteResults.Add(wishVoteResult);
});
var assignments = wishVoteResults.SelectMany(w => w.Assignments).OrderByDescending(a => a.Item2).ThenBy(a => a.Item1).ToList();
Console.WriteLine("Wish {wishNumber}: {voteCount} {wishOrder}");
foreach (var assignment in assignments)
{
Console.WriteLine($"Wish number {assignment.Item3}: {assignment.Item2} {assignment.Item1}");
}
Console.WriteLine("Finished.");
Effectively, what we're doing is creating all the possible assignments and then just sorting accordingly.
Related
I have this query:
var professionalProfilesQuery =
(from profileCollection in _mongoContext.Database.GetCollection<Profile>("profiles").AsQueryable()
join userCollection in _mongoContext.Database.GetCollection<User>("users").AsQueryable()
on profileCollection.UserId equals userCollection.Id.Value
into users
orderby profileCollection.Name
select new ProfessionalProfile
{
Id = profileCollection.Id,
Name = profileCollection.Name,
UserId = profileCollection.UserId,
IsConfirmed = users.First().IsConfirmed,
})
.Where(p => p.IsConfirmed);
And then I have a stored javascript function in DB that calculates a "Score" for each ProfessionalProfile according to data in several collections (I need to sort results later according to that score). Something like:
function candidateScore(objectWithDataToCalculateScore, professionalProfile) {
var score = 0;
//Calculate score here
return score;
}
Is it possible to add a field to data returned by the query with the calculated score? How can I call the stored javascript function to do so? I'd like to do something like:
var professionalProfilesQuery =
(from profileCollection in _mongoContext.Database.GetCollection<Profile>("profiles").AsQueryable()
join userCollection in _mongoContext.Database.GetCollection<User>("users").AsQueryable()
on profileCollection.UserId equals userCollection.Id.Value
into users
orderby profileCollection.Name
select new ProfessionalProfile
{
Id = profileCollection.Id,
Name = profileCollection.Name,
UserId = profileCollection.UserId,
IsConfirmed = users.First().IsConfirmed,
Score = ***CallToCandidateScoreFunctionHere...***
})
.Where(p => p.IsConfirmed);
If that's not possible or it's not a good way to do it, would you suggest another approach instead of stored javascript function?
Thanks.
EDIT: After reading comments from Neil Lunn, I'll explain better the calculation I need.
This is an example of the class that models the collection that would be the first parameter in the previous candidateScore javascript function:
public class ProfessionalProfileRequirements
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }
public string Role { get; set; }
public string Province { get; set; }
public IEnumerable<string> LanguagesRequired { get; set; }
public IEnumerable<string> SkillsRequired { get; set; }
}
And this the javascript function again with some of the calculations (it's quite complex, so I simplified it. I hope it's enough to show what I need):
function candidateScore(professionalProfileRequirements, professionalProfile) {
var score = 0;
if (professionalProfileRequirements.Province === professionalProfile.Province) {
score += 1000;
}
if (professionalProfileRequirements.Role && professionalProfile.Roles && professionalProfile.ExperienceLevel){
if (professionalProfile.Roles.includes(professionalProfileRequirements.Role)) {
switch (professionalProfile.ExperienceLevel) {
case 'ExperienceLevel_NoExperience':
score += 1;
break;
case 'ExperienceLevel_LessThanOneYear':
score += 2;
break;
case 'ExperienceLevel_BetweenOneAndThreeYears':
score += 4;
break;
case 'ExperienceLevel_BetweenThreeAndFiveYears':
score += 6;
break;
case 'ExperienceLevel_MoreThan5Years':
score += 10;
break;
}
}
}
return score;
}
I don't know how I can do that kind of calculation using "aggregate". Any help or any other approach I could follow would be appreciated.
I am trying to update only one column with jsonb type. Insert works perfectly without any surprises but I can't find out how can I do update only one field with attribute [ComplextType('json')]
db.UpdateOnly(() => new QuestionPoco() {Answers = requestDto.answers},
where: q => q.Id.ToString() == question.id.ToString());
This should now be supported from this commit which is now available on MyGet.
We've also added new typed PgSqlTypes Attributes which you can use instead of [CustomField("json")], e.g:
public class Question
{
public int Id { get; set; }
public string Text { get; set; }
//equivalent to: [CustomField("json")]
[PgSqlJson]
public List<Answer> Answers { get; set; }
}
public class Answer
{
public int Id { get; set; }
public string Text { get; set; }
}
Which you can Insert/Update as normal, e.g:
db.DropAndCreateTable<Question>();
var createTableSql = db.GetLastSql();
Assert.That(createTableSql, Does.Contain("\"answers\" json NULL"));
db.Insert(new Question
{
Id = 1,
Answers = new List<Answer>
{
new Answer { Id = 1, Text = "Q1 Answer1" }
}
});
var question = db.SingleById<Question>(1);
Assert.That(question.Answers.Count, Is.EqualTo(1));
Assert.That(question.Answers[0].Text, Is.EqualTo("Q1 Answer1"));
db.UpdateOnly(() => new Question {
Answers = new List<Answer> { new Answer { Id = 1, Text = "Q1 Answer1 Updated" } }
},
#where: q => q.Id == 1);
question = db.SingleById<Question>(1);
Assert.That(question.Answers[0].Text, Is.EqualTo("Q1 Answer1 Updated"));
I use Breeze with Durandal (still 1.2) and I am facing a problem which I haven't found an easy solution for. I have 2 entities: Invoice & InvoiceLine like described below:
public class Invoice
{
[Key]
public int Id { get; set; }
public string InvoiceNumber { get; set; }
public string Comment { get; set; }
public double? TotalExclVAT { get; set; }
public double? TotalInclVAT { get; set; }
public double? TotalVAT { get; set; }
public bool? WithoutVAT { get; set; }
public virtual List<InvoiceLine> Lines { get; set; }
}
public class InvoiceLine
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
public double VatPercent { get; set; }
public double Amount { get; set; }
public int InvoiceId { get; set; }
public virtual Invoice Invoice { get; set; }
}
I need to compute the totals of the invoice (TotalExclVAT, TotalInclVAT, TotalVAT) in 2 cases:
Whenever someone adds/modifies an invoice line.
Whenever someone changes the flag WithoutVAT on the invoice.
I don't think this is a good idea to perform this compute client side. Performing this server side is better for security reasons mainly.
My first thought was to do the job in the BeforeSaveEntity of Invoice & InvoiceLine.
Here is what i did:
public bool BeforeSaveEntity(EntityState entityState, EntityInfo entityInfo)
{
var invoice = entityInfo.Entity as Invoice;
...
ComputeTotal(entityInfo, invoice);
}
private void ComputeTotal(EntityInfo entityInfo, Invoice invoice)
{
var query = Context.InvoiceLines.Where(x => x.invoiceId == invoice.Id).AsEnumerable();
double totalExclVAT = 0;
double totalVAT = 0;
int percent = 0;
foreach (var line in query.ToList())
{
totalExclVAT = ...
totalVAT = ...
}
entityInfo.OriginalValuesMap.Add("TotalExclVAT", invoice.TotalExclVAT);
entityInfo.OriginalValuesMap.Add("TotalInclVAT", invoice.TotalInclVAT);
entityInfo.OriginalValuesMap.Add("TotalVAT", invoice.TotalVAT);
accounting.TotalExclVAT = totalExclVAT;
accounting.TotalInclVAT = totalExclVAT + totalVAT;
accounting.TotalVAT = totalVAT;
}
The same kind of thing is done for the invoice line. As you can see in the ComputeTotal function, I perform a query to get invoice lines from DB then computing totals and saving results in the invoice.
It doesn't work quite well: in case of adding a new line on my invoice, performing a query on my DB doesn't get this added line! Because it is not already stored in DB.
It would have been easier to proceed client side but I don't think this is a good idea... is it?
So I am sure there is another way of doing but I don't find it myself.
Any help is greathly appreciated.
UPDATE
Below is my first shot with this problem.
public Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap)
{
List<EntityInfo> invoices;
List<EntityInfo> invoiceLines;
EntityInfo ei;
if (!saveMap.TryGetValue(typeof(InvoiceLine), out invoiceLines))
{
// if we fall here it means no invoice lines exists in the saveMap
}
if (!saveMap.TryGetValue(typeof(Invoice), out invoices))
{
// if we fall here it means no invoices exists in the saveMap
// >> getting the invoice from DB and add it to the map
using (var dc = new BreezeContext())
{
int invoiceId = ((InvoiceLine)invoiceLines[0].Entity).InvoiceId;
EFContextProvider<BreezeContext> cp = new EFContextProvider<BreezeContext>();
var acc = dc.Invoices.Where(x => x.Id == invoiceId).FirstOrDefault();
ei = cp.CreateEntityInfo(acc, Breeze.WebApi.EntityState.Modified);
invoices = new List<EntityInfo>();
saveMap.Add(typeof(Invoice), invoices);
invoices.Add(ei);
}
}
// There is only 1 invoice at a time in the saveMap
Invoice invoice = (Invoice)invoices[0].Entity;
ei = invoices[0];
Dictionary<int, InvoiceLine> hashset = new Dictionary<int, InvoiceLine>();
// Retrieving values of invoice lines from database (server side)
using (var dc = new BreezeContext())
{
var linesServerSide = dc.InvoiceLines.Where(x => x.InvoiceId == invoice.Id).AsEnumerable();
foreach (var elm in linesServerSide)
{
hashset.Add(elm.Id, elm);
}
}
// Retrieving values of invoice lines from modified lines (client side)
foreach (var entityInfo in invoiceLines)
{
InvoiceLine entity = (InvoiceLine)entityInfo.Entity;
switch (entityInfo.EntityState)
{
case Breeze.WebApi.EntityState.Added:
hashset.Add(entity.Id, entity);
break;
case Breeze.WebApi.EntityState.Deleted:
hashset.Remove(entity.Id);
break;
case Breeze.WebApi.EntityState.Modified:
hashset.Remove(entity.Id);
hashset.Add(entity.Id, entity);
break;
}
}
// Computing totals based on my hashset
double totalExclVAT = 0;
double totalInclVAT = 0;
double totalVAT = 0;
foreach (var elm in hashset)
{
InvoiceLine line = elm.Value;
totalExclVAT += line.Amount;
totalVAT += line.Amount * (int)line.VatPercent.Value / 100;
}
totalInclVAT = totalExclVAT + totalVAT;
// Adding keys if necessary
if (!ei.OriginalValuesMap.ContainsKey("TotalExclVAT"))
ei.OriginalValuesMap.Add("TotalExclVAT", invoice.TotalExclVAT);
if (!ei.OriginalValuesMap.ContainsKey("TotalInclVAT"))
ei.OriginalValuesMap.Add("TotalInclVAT", invoice.TotalInclVAT);
if (!ei.OriginalValuesMap.ContainsKey("TotalVAT"))
ei.OriginalValuesMap.Add("TotalVAT", invoice.TotalVAT);
// Modifying total values
invoice.TotalExclVAT = totalExclVAT;
invoice.TotalInclVAT = totalInclVAT;
invoice.TotalVAT = totalVAT;
return saveMap;
}
The solution above works well whenever the invoice & the invoiceLines are modified client side. I have a problem when no invoice is modified client side (only lines modified). In this case I need to add the related invoice to the saveMap by getting it from DB. That's what I do in my code as you can see. But I need to add keys to the OriginalValuesMap for properties I manually modified here and I cannot in this case because my dictionary object is null. Then when I do...
ei.OriginalValuesMap.Add("TotalExclVAT", invoice.TotalExclVAT);
... on a null object (OriginalValuesMap) it doesn't work.
So my new problem is now the next: how to add an entity to the saveMap which already exists on DB. So I don't want to mark this entity as ei = cp.CreateEntityInfo(acc, Breeze.WebApi.EntityState.Add); but rather ei = cp.CreateEntityInfo(acc, Breeze.WebApi.EntityState.Modified);. In this case my OriginalValuesMap is null and it seems to be a problem.
Hope you understand what I try to explain here.
Is there any reason not to use a triggered stored procedure for this? This would certainly be the simplest approach...
But... if there is, then the other approach would be to use 'BeforeSaveEntities' instead of 'BeforeSaveEntity' because this will give you access to the entire 'SaveMap'.
Then create a hashset of all of the invoiceLines for each modified invoice and construct this as the combination of your server side query of invoice lines per invoice overlayed with client side invoiceLines associated with this invoice (from the SaveMap). Next just total each hashSet and use this update your 'Totalxxx' properties.
A little terse but hopefully this makes sense.
This question already has answers here:
The entity cannot be constructed in a LINQ to Entities query
(14 answers)
Closed 10 years ago.
I have two functions that look exactly the same except they create lists of two different objects. The two different objects look very much alike, but when I try to run one of the functions on one of the objects, I get the error message, "The entity or complex type cannot be constructed in a LINQ to Entities query.". Can someone explain to me what is happening in very simple terms? Also, can you tell me how to change my code so that it works? Thanks, Allan.
Function 1 (works):
public static List<ChartApp> ListChartApplications()
{
using (var db = new LatencyDBContext())
{
var appNames = db.LoginApplications.Select(item => new ChartApp()
{
LoginApplicationID = item.LoginApplicationID,
LoginAppName = item.LoginAppName,
}).OrderBy(item => item.LoginAppName);
return appNames.ToList();
}
}
Function 2 (throws error on "return appNames.ToList();"):
public static List<LoginApplication> ListApplications()
{
using (var db = new LatencyDBContext())
{
var appNames = db.LoginApplications.Select(item => new LoginApplication()
{
LoginApplicationID = item.LoginApplicationID,
LoginAppName = item.LoginAppName,
}).OrderBy(item => item.LoginAppName);
return appNames.ToList();
}
}
Classes:
public class ChartApp
{
public ChartApp()
{
this.LoginHistories = new List<ChartHist>();
}
public int? LoginApplicationID { get; set; }
public string LoginAppName { get; set; }
public virtual ICollection<ChartHist> LoginHistories { get; set; }
public int Step { get; set; }
}
public class LoginApplication
{
public LoginApplication()
{
this.LoginHistories = new List<LoginHistory>();
}
public int LoginApplicationID { get; set; }
public string LoginAppName { get; set; }
public virtual ICollection<LoginHistory> LoginHistories { get; set; }
}
Edit: Could the difference possibly be that one of the objects are mapped to the database?
public class LoginApplicationMap : EntityTypeConfiguration<LoginApplication>
{
public LoginApplicationMap()
{
// Primary Key
this.HasKey(t => t.LoginApplicationID);
// Properties
this.Property(t => t.LoginAppName)
.HasMaxLength(500);
// Table & Column Mappings
this.ToTable("LoginApplication");
this.Property(t => t.LoginApplicationID).HasColumnName("LoginApplicationID");
this.Property(t => t.LoginAppName).HasColumnName("LoginAppName");
}
}
My solution in this case was to just delete the non-working function and use the working one in all places. For, similar functions that are mapped, I use the following function to return values.
public static List<LoginEnvironment> ListEnvironments(bool allSelection)
{
using (var db = new LatencyDBContext())
{
//GET ALL THE ENVIRONMENT NAMES
var envNames = from e in db.LoginEnvironments
orderby e.LoginEnvName
select e;
//PUT ALL THE ENVIRONMENTS INTO A LOCAL LIST
var listEnv = new List<LoginEnvironment>();
if (allSelection)
{
var defaultAll = new LoginEnvironment();
defaultAll.LoginEnvironmentID = 0;
defaultAll.LoginEnvName = "All";
listEnv.Add(defaultAll);
}
foreach (var item in envNames)
{
var localEnv = new LoginEnvironment();
localEnv.LoginEnvironmentID = item.LoginEnvironmentID;
localEnv.LoginEnvName = item.LoginEnvName;
listEnv.Add(localEnv);
}
return listEnv;
}
}
Say I had a class:
public class Post
{
public int PostId { get; set; }
public string Topic { get; set; }
public int UserId { get; set; }
[StringLength(5000)]
public string Body { get; set; }
public DateTime DateCreated { get; set; }
public string Name { get; set; }
public int Votes { get; set; }
}
And for each post, a user could input a topic. for example, if the topics were "Red" "Green" "Blue" and "Yellow", how could I create a list based on how many times those were used?
An example output:
Red | 70
Blue | 60
Green | 40
Yellow| 35
EDIT: How come this doesn't work and gives me an error where I cannot implicitly convert the type?
public List<string> GetPopularTopics(int count)
{
var posts = from p in db.Posts
group p by p.Topic into myGroup
select new
{
Topic = myGroup.Key,
Count = myGroup.Count()
};
return posts.ToList();
}
EDIT 2:
So I tried your solution out Dustin, and I'm getting an error. This is what I used:
public IEnumerable<IGrouping<string,int>> GetPosts()
{
var posts = from p in db.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
return posts.ToList();
}
This is giving me an error under posts.ToList():
Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.IEnumerable>'. An explicit conversion exists (are you missing a cast?)
To create the grouping you create an anonymous type such as:
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
Then to work with the date, lets say iterate over it:
foreach(var p in posts)
{
Response.Write(String.Format("{0} - {1}", p.Topic, p.Count));
}
You must create a new type if you do a projection and return it form method!
public class MyCounts
{
public string Topic { get; set; }
public int Count { get; set; }
}
public List<MyCounts> GetPopularTopics(int count)
{
var posts = from p in db.Posts
group p by p.Topic into myGroup
select new MyCounts
{
Topic = myGroup.Key,
Count = myGroup.Count()
};
return posts.ToList();
}
The problem is that you need to use an non anonymous type for your return value.
This query creates an IEnumerable of anonymous types.
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
It's the select new statement that creates the anonymous objects.
What you need to do is to create something that is non anonymous - an object that can be shared within and outside this method.
Like this:
public IEnumerable<TopicAndCount> GetPosts()
{
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new TopicAndCount
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
}
Note the select new TopicAndCount statement and the return value of the enclosing method.
That will solve your problem.