How to bypass or implement IComparable - c#

I get the below error and I can't understand why and how to get past it (or implement icomparable).
I'm trying to get the property Group from the object that has the biggest count of Group using Max().
public class Program
{
private static void Main(string[] args) {
var list = new List<Foo>();
for (var i = 0; i < 10; i++) {
list.Add(new Foo());
if (i == 5) {
var foo = new Foo() {
Group = { "One", "Two", "Three" }
};
list.Add(foo);
}
}
var maxGroup = list.Max(x => x.Group); //throws error
}
}
public class Foo {
public Guid Id { get; } = new Guid();
public int Field1 { get; set; }
public int Field2 { get; set; }
public int Field3 { get; set; }
public int Field4 { get; set; }
public List<string> Group { get; set; } = new List<string>();
}
at least one object must implement icomparable

I'm trying to get the property Group from the object that has the longest list
You don't want to do Max for that. Simply order by the length of the list, and take the first one:
Foo res = list.OrderByDescending(x => x.Group.Count).FirstOrDefault();
if (res != null) {
List<string> longestList = res.Group;
}

Related

Map one class data to another class with iteration

I have a C# project and looking for simple solution for map one class object data to list of another class object.
This is my input class
public class RatesInput
{
public string Type1 { get; set; }
public string Break1 { get; set; }
public string Basic1 { get; set; }
public string Rate1 { get; set; }
public string Type2 { get; set; }
public string Break2 { get; set; }
public string Basic2 { get; set; }
public string Rate2 { get; set; }
public string Type3 { get; set; }
public string Break3 { get; set; }
public string Basic3 { get; set; }
public string Rate3 { get; set; }
}
This is my another class structure
public class RateDetail
{
public string RateType { get; set; }
public decimal Break { get; set; }
public decimal Basic { get; set; }
public decimal Rate { get; set; }
}
it has a object like below. (For easiering the understanding, I use hardcoded values and actually values assign from a csv file)
RatesInput objInput = new RatesInput();
objInput.Type1 = "T";
objInput.Break1 = 100;
objInput.Basic1 = 50;
objInput.Rate1 = 0.08;
objInput.Type2 = "T";
objInput.Break2 = 200;
objInput.Basic2 = 50;
objInput.Rate2 = 0.07;
objInput.Type3 = "T";
objInput.Break3 = 500;
objInput.Basic3 = 50;
objInput.Rate3 = 0.06;
Then I need to assign values to "RateDetail" list object like below.
List<RateDetail> lstDetails = new List<RateDetail>();
//START Looping using foreach or any looping mechanism
RateDetail obj = new RateDetail();
obj.RateType = //first iteration this should be assigned objInput.Type1, 2nd iteration objInput.Type2 etc....
obj.Break = //first iteration this should be assigned objInput.Break1 , 2nd iteration objInput.Break2 etc....
obj.Basic = //first iteration this should be assigned objInput.Basic1 , 2nd iteration objInput.Basic2 etc....
obj.Rate = //first iteration this should be assigned objInput.Rate1, 2nd iteration objInput.Rate2 etc....
lstDetails.Add(obj); //Add obj to the list
//END looping
Is there any way to convert "RatesInput" class data to "RateDetail" class like above method in C#? If yes, how to iterate data set?
Try this:
public class RatesList : IEnumerable<RateDetail>
{
public RatesList(IEnumerable<RatesInput> ratesInputList)
{
RatesInputList = ratesInputList;
}
private readonly IEnumerable<RatesInput> RatesInputList;
public IEnumerator<RateDetail> GetEnumerator()
{
foreach (var ratesInput in RatesInputList)
{
yield return new RateDetail
{
RateType = ratesInput.Type1,
Break = Convert.ToDecimal(ratesInput.Break1, new CultureInfo("en-US")),
Basic = Convert.ToDecimal(ratesInput.Basic1, new CultureInfo("en-US")),
Rate = Convert.ToDecimal(ratesInput.Rate1, new CultureInfo("en-US"))
};
yield return new RateDetail
{
RateType = ratesInput.Type2,
Break = Convert.ToDecimal(ratesInput.Break2),
Basic = Convert.ToDecimal(ratesInput.Basic2),
Rate = Convert.ToDecimal(ratesInput.Rate2, new CultureInfo("en-US"))
};
yield return new RateDetail
{
RateType = ratesInput.Type3,
Break = Convert.ToDecimal(ratesInput.Break3),
Basic = Convert.ToDecimal(ratesInput.Basic3),
Rate = Convert.ToDecimal(ratesInput.Rate3, new CultureInfo("en-US"))
};
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
And use:
var list = new RatesList(new List<RatesInput>() { objInput });
foreach (var item in list)
{
Console.WriteLine(item.Basic);
}
You can use Reflection to get the properties info like this:
var props = objInput.GetType().GetProperties();
var types = props.Where(x => x.Name.StartsWith("Type"))
.Select(x => x.GetValue(objInput)).ToList();
var breaks = props.Where(x => x.Name.StartsWith("Break"))
.Select(x => x.GetValue(objInput)).ToList();
var basics = props.Where(x => x.Name.StartsWith("Basic"))
.Select(x => x.GetValue(objInput)).ToList();
var rates = props.Where(x => x.Name.StartsWith("Rate"))
.Select(x => x.GetValue(objInput)).ToList();
List<RateDetail> lstDetails = new List<RateDetail>();
for (int i = 0; i < types.Count; i++)
{
lstDetails.Add(new RateDetail
{
RateType = types[i].ToString(),
Break = Convert.ToDecimal(breaks[i]),
Basic = Convert.ToDecimal(basics[i]),
Rate = Convert.ToDecimal(rates[i])
});
}

C# Reactive Extensions (rx) FirstOrDefault enumerates entire collection

It seems that the expected behavior of FirstOrDefault is to complete after finding an item that matches the predicate and the expected behavior of concat is to evaluate lazily. However, the following example enumerates the entire collection even though the predicate matches the first item.
(Thanks for the friendlier code Shlomo)
void Main()
{
var entities = Observable.Defer(() => GetObservable().Concat());
Entity result = null;
var first = entities.FirstOrDefaultAsync(i => i.RowId == 1).Subscribe(i => result = i);
result.Dump();
buildCalled.Dump();
}
// Define other methods and classes here
public IEnumerable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.Select(i => Observable.Return(BuildEntity(i)));
}
public int buildCalled = 0;
public Entity BuildEntity(EntityTableRow entityRow)
{
buildCalled++;
return new Entity { RowId = entityRow.Id, StringVal = entityRow.StringVal };
}
public class Entity
{
public int RowId { get; set; }
public string StringVal { get; set; }
}
public class EntityTableRow
{
public int Id { get; set; }
public string StringVal { get; set; }
}
Is this the expected behavior? Is there a way to defer the enumeration of the objects (specifically the building in this case) until truly needed?
The following is Linqpad-friendly code equivalent to what you have:
void Main()
{
var entities = Observable.Defer(() => GetObservable().Concat());
Entity result = null;
var first = entities.FirstOrDefaultAsync(i => i.RowId == 1).Subscribe(i => result = i);
result.Dump();
buildCalled.Dump();
}
// Define other methods and classes here
public IEnumerable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.Select(i => Observable.Return(BuildEntity(i)));
}
public int buildCalled = 0;
public Entity BuildEntity(EntityTableRow entityRow)
{
buildCalled++;
return new Entity { RowId = entityRow.Id, StringVal = entityRow.StringVal };
}
public class Entity
{
public int RowId { get; set; }
public string StringVal { get; set; }
}
public class EntityTableRow
{
public int Id { get; set; }
public string StringVal { get; set; }
}
If you change GetObservable to the following, you'll get the desired result:
public IObservable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.ToObservable().Select(i => Observable.Return(BuildEntity(i)));
}
It appears the implementation of Concat<TSource>(IEnumerable<IObservable<TSource>>) is eager in evaluating the enumerable, whereas the implementation of Concat<TSource>(IObservable<IObservable<TSource>>) and ToObservable<TSource>(IEnumerable<TSource>) maintain laziness appropriately. I can't say I know why.

Sorting and Updating a Generic List of Object based on a Sub Object

I have the following objects:
public class TestResult
{
public string SectionName { get; set; }
public int Score { get; set; }
public int MaxSectionScore { get; set; }
public bool IsPartialScore { get; set; }
public string Name { get; set; }
public int NumberOfAttempts { get; set; }
}
public class TestResultGroup
{
public TestResultGroup()
{
Results = new List<TestResult>();
Sections = new List<string>();
}
public List<TestResult> Results { get; set; }
public List<string> Sections { get; set; }
public string Name { get; set; }
public int Rank { get; set; }
}
So, a TestResultGroup can have any number of results of type TestResult. These test results only differ by their SectionName.
I have a List<TestResultGroup> which I need to sort into descending order based on a score in the Results property, but only when Results has an item whos SectionName = "MeanScore" (if it doesnt have this section we can assume a score of -1). How would I go about ordering the list? Ideally I would also like to apply the result of this ordering to the Rank property.
Many Thanks
List<TestResultGroup> groups = ...
// group test result groups by the same score and sort
var sameScoreGroups = groups.GroupBy(
gr =>
{
var meanResult = gr.Results.FirstOrDefault(res => res.SectionName == "MeanScore");
return meanResult != null ? meanResult.Score : -1;
})
.OrderByDescending(gr => gr.Key);
int rank = 1;
foreach (var sameScoreGroup in sameScoreGroups)
{
foreach (var group in sameScoreGroup)
{
group.Rank = rank;
}
rank++;
}
// to obtain sorted groups:
var sortedGroups = groups.OrderByDescending(gr => gr.Rank).ToArray();
Or even write one expression with a side effect:
List<TestResultGroup> groups = ...
int rank = 1;
var sortedGroups = groups
.GroupBy(
gr =>
{
var meanResult = gr.Results.FirstOrDefault(res => res.SectionName == "MeanScore");
return meanResult != null ? meanResult.Score : -1;
})
.OrderByDescending(grouping => grouping.Key)
.SelectMany(grouping =>
{
int groupRank = rank++;
foreach (var group in grouping)
{
group.Rank = groupRank;
}
return grouping;
})
.ToArray(); // or ToList

Linq At least one object must implement IComparable

I am trying to order a List of Entities that contains another list of Entities. I have implemented IComparable for all entities and still get the exception. All of the examples I have seen address the issue where you have one list and you order by a given field in that list but not where you have a list of lists. This issue is happening for Linq to Objects per below and also for Linq to Entities. What am I missing?
[TestClass]
public class OrderBy
{
[TestMethod]
public void OrderByTest()
{
var hobbies = new Collection<Hobby> { new Hobby { HobbyId = 1, Name = "Eating" }, new Hobby() { HobbyId = 2, Name = "Breathing" } };
var p1 = new Person
{
PersonId = 1,
Name = "A",
PersonHobbies = new Collection<PersonHobby> { new PersonHobby() { PersonHobbyId = 1}}
};
var p2 = new Person
{
PersonId = 2,
Name = "Z",
PersonHobbies = new Collection<PersonHobby> { new PersonHobby() { PersonHobbyId = 2 }}
};
var people = new List<Person> { p1, p2 };
var pplEnumerable = people.AsEnumerable();
pplEnumerable = pplEnumerable.OrderByDescending(r => r.PersonHobbies.OrderByDescending(p => p.Hobby.Name));
foreach (var person in pplEnumerable)
{
Console.WriteLine(person.Name);
}
}
public class Person : IComparable
{
public int PersonId { get; set; }
public string Name { get; set; }
public virtual ICollection<PersonHobby> PersonHobbies { get; set; }
public int CompareTo(object obj)
{
if (obj == null) return 1;
var otherPerson = obj as Person;
return PersonId.CompareTo(otherPerson.PersonId);
}
}
public class PersonHobby : IComparable
{
public int PersonHobbyId { get; set; }
public int HobbyId { get; set; }
public virtual Person Person{ get; set; }
public int PersonId { get; set; }
public virtual Hobby Hobby { get; set; }
public int CompareTo(object obj)
{
if (obj == null) return 1;
var otherPersonHobby = obj as PersonHobby;
return PersonHobbyId.CompareTo(otherPersonHobby.PersonHobbyId);
}
}
public class Hobby : IComparable
{
public int HobbyId { get; set; }
public string Name { get; set; }
public int CompareTo(object obj)
{
if (obj == null) return 1;
var otherHobby = obj as Hobby;
return HobbyId.CompareTo(otherHobby.HobbyId);
}
}
}
You cannot apply ordering to lists by default. You need to write up a custom class (sort of EquatableList etc.) or use LINQ Except & Intersect operators to compare lists.
But based on your comment, if you're looking for the LINQ equivalent of:
select * from Person p join PersonHobby ph
on ph.PersonId = p.PersonId join Hobby h
on h.HobbyId = ph.HobbyId order by h.Name
then that can be achieved as:
var query = people.SelectMany(p => p.PersonHobbies)
.Join(hobbies, ph => ph.HobbyId, h => h.HobbyId,
(ph, h) => new
{
Person = ph.Person, PersonHobby = ph, Hobby = h
})
.OrderBy(r => r.Hobby.Name);
basically we join person, person hobbies and hobby on the keys, and project all columns and sort it by the hobby.name field, as mentioned in your SQL.

How to copy a List<> to another List<> with Comparsion in c#

I an having Two Lists. I want to get the matched and unmatched values based on ID and add the results to another List. I can get both of these using Intersect/Except.
But I can get only ID in the resultant variables (matches and unmatches) . I need all the properties in the Template.
List<Template> listForTemplate = new List<Template>();
List<Template1> listForTemplate1 = new List<Template1>();
var matches = listForTemplate .Select(f => f.ID)
.Intersect(listForTemplate1 .Select(b => b.ID));
var ummatches = listForTemplate .Select(f => f.ID)
.Except(listForTemplate1.Select(b => b.ID));
public class Template
{
public string ID{ get; set; }
public string Name{ get; set; }
public string Age{ get; set; }
public string Place{ get; set; }
public string City{ get; set; }
public string State{ get; set; }
public string Country{ get; set; }
}
public class Template1
{
public string ID{ get; set; }
}
If you don't want to implement IEquality for this simple task, you can just modify your LINQ queries:
var matches = listForTemplate.Where(f => listForTemplate1.Any(b => b.ID == f.ID));
and
var unmatches = listForTemplate.Where(f => listForTemplate1.All(b => b.ID != f.ID));
You might want to check for null before accessing ID, but it should work.
You are looking for the overloaded function, with the second parameter IEqualityComparer. So make your comparer ( example: http://www.blackwasp.co.uk/IEqualityComparer.aspx ), and use the same comparer in intersect / except.
And for the generic part: maybe you should have a common interface for templates e.g. ObjectWithID describing that the class have a string ID property. Or simply use dynamic in your comparer (but I think this is very-very antipattern because you can have run time errors if using for the bad type).
You also have a problem: intersecting two collections with two different types will result in a collection of Object (common parent class). Then you have to cast a lot (antipattern). I advise you to make a common abstract class/interface for your template classes, and it is working. If you need to cast the elements back, do not cast, but use the visitior pattern: http://en.wikipedia.org/wiki/Visitor_pattern
Example (good):
static void Main(string[] args)
{
// http://stackoverflow.com/questions/16496998/how-to-copy-a-list-to-another-list-with-comparsion-in-c-sharp
List<Template> listForTemplate = new Template[] {
new Template(){ID = "1"},
new Template(){ID = "2"},
new Template(){ID = "3"},
new Template(){ID = "4"},
new Template(){ID = "5"},
new Template(){ID = "6"},
}.ToList();
List<Template1> listForTemplate1 = new Template1[] {
new Template1(){ID = "1"},
new Template1(){ID = "3"},
new Template1(){ID = "5"}
}.ToList();
var comp = new ObjectWithIDComparer();
var matches = listForTemplate.Intersect(listForTemplate1, comp);
var ummatches = listForTemplate.Except(listForTemplate1, comp);
Console.WriteLine("Matches:");
foreach (var item in matches) // note that item is instance of ObjectWithID
{
Console.WriteLine("{0}", item.ID);
}
Console.WriteLine();
Console.WriteLine("Ummatches:");
foreach (var item in ummatches) // note that item is instance of ObjectWithID
{
Console.WriteLine("{0}", item.ID);
}
Console.WriteLine();
}
}
public class ObjectWithIDComparer : IEqualityComparer<ObjectWithID>
{
public bool Equals(ObjectWithID x, ObjectWithID y)
{
return x.ID == y.ID;
}
public int GetHashCode(ObjectWithID obj)
{
return obj.ID.GetHashCode();
}
}
public interface ObjectWithID {
string ID { get; set; }
}
public class Template : ObjectWithID
{
public string ID { get; set; }
public string Name { get; set; }
public string Age { get; set; }
public string Place { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
public class Template1 : ObjectWithID
{
public string ID { get; set; }
}
Output:
Matches:
1
3
5
Ummatches:
2
4
6
Press any key to continue . . .
For comparison, this should also work (the first part is a variation on #MAV's answer):
var matches = from item in listForTemplate
join id in listForTemplate1 on item.ID equals id.ID
select item;
var unmatches = listForTemplate.Where(item => matches.All(elem => elem.ID != item.ID));
matches and unmatches will both be IEnumerable<Template> which is the type you require.
However, MAV's answer works fine so I'd go for that one.
As mentioned, Implement the IEqualityComparer<T> interface.
IEqualityComparer<T> MSDN
Then use this as an argument in your method for Except() and Intersect()
Intersect
There is a good example of how to do so on the link for the Intersect() method.
If you don't absolutely have to use LINQ, why not code something like this?
var matches = new List<Template>();
var unmatches = new List<Template>();
foreach (var entry in listForTemplate)
{
bool matched = false;
foreach (var t1Entry in listForTemplate1)
{
if (entry.ID == t1Entry.ID)
{
matches.Add(entry);
matched = true;
break;
}
}
if (!matched)
{
unmatches.Add(entry);
}
}
A disadvantage of the LINQ approach is that you're traversing the lists twice.

Categories

Resources