Return grouped data from method - c#

I have a method from which I need to return a group, i.e.:
public static MyData BinSearch(MyData searchDate)
{
// First doing a binary search to get the index
if (index >= 0)
{
return recordList[index];
}
index = ~index;
if (index == 0 || index == recordList.Count)
{
return null;
}
int newIndex = (((index-1)+index)/2)+1;
string pointer = recordList[newIndex].TaxDet;
var groupData = recordList.GroupBy(p => p.TaxDet)
.ToDictionary(g => g.Key);
var output = groupData[pointer];
return (output); // Here I want to return a group of data
}
But I'm getting an error:
Cannot implicitly convert type
'System.Linq.IGrouping' to
'ConsoleApplication1.MyData'. An explicit conversion exists (are you
missing a cast?)
EDIT:
public class MyData
{
public string TaxDet{ get; set; }
public string empDetails { get; set; }
}

Since you are combining GroupBy and ToDictionary, by doing groupData[pointer] you are getting value from dictionary, which is IGrouping. If you need to extract data from grouping, you need 1 more index getter groupData[pointer][taxDet]
Probably .ToDictionary(g => g.Key); is redundant in this case

Related

First element foreach not showing c#

I have an empty List (objPassenger) with passengers spaces incoming, and another list with data of passengers confirmed (objFilePassenger). I can't start from the 0 index in foreach.
Demo in web
foreach (var filePassenger in objFilePassengers)
{
//TypeId == 1 is ADULT
//TypeId == 2 is CHILD
var passenger = objPassengers.FirstOrDefault(p => (
(p.TypeId == filePassenger.Type && p.TypeId == "1") || (p.TypeId != "1" && filePassenger.Type != "1" && p.Age == filePassenger.Age)));
if (passenger != null)
{
passenger.PassengerId = filePassenger.PassengerID;
passenger.TypeId = filePassenger.Type;
passenger.FirstName = filePassenger.Name.ToTitleCase();
passenger.LastName = filePassenger.LastName.ToTitleCase();
passenger.Age = filePassenger.Age;
}
}
How can I skip the object if p.TypeId== 1 && filePassenger.Type == 2? This deferred object I need to skip and go next, until the type is equal like TypeId == 2 and Type == 2 (CHILD).
Updated Answer
Have you considered using a custom Collection to hold your passengers? You can build all the logic into the collection and make it enumerable to iterate through the passengers.
Here's a basic example built from your code:
Two data objects to represent a passenger. I use records because they make equality checks and cloning easy, and you get immutability. Just use the class object when you want to edit the record.
public record PassengerRecord(Guid PassengerId, string PassengerName, int PassengerType);
public class Passenger
{
public Guid PassengerId { get; set; } = Guid.NewGuid();
public string PassengerName { get; set; } = "Not Set";
public int PassengerType { get; set; }
public PassengerRecord AsRecord => new(PassengerId, PassengerName, PassengerType);
public Passenger() { }
public Passenger(PassengerRecord record)
{
this.PassengerId = record.PassengerId;
this.PassengerName = record.PassengerName;
this.PassengerType = record.PassengerType;
}
}
And then a very basic PassengerList which implements IEnumerable, restricts the number of passengers and checks if they are already on the list. Build your own logic and functionality into this.
public class PassengerList : IEnumerable<PassengerRecord>
{
private List<PassengerRecord> _items = new List<PassengerRecord>();
private int _maxRecords;
public PassengerList(int maxRecords)
=> _maxRecords = maxRecords;
public bool TryAddItem(Passenger passenger)
=> addItem(passenger.AsRecord);
public bool TryAddItem(PassengerRecord passenger)
=> addItem(passenger);
public bool addItem(PassengerRecord passenger)
{
// Doi whatever checks you want to do
if (!_items.AsQueryable().Any(item => item == passenger) && _items.Count < _maxRecords)
_items.Add(passenger);
return true;
}
public PassengerRecord this[int index]
=> _items[index];
public IEnumerator<PassengerRecord> GetEnumerator()
=> _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> this.GetEnumerator();
}
This is how I solved it. The issue was 2 lists, one with the requested places, and another with the pre-loaded passengers. The first list could contain, for example, 6 places (3 adults and 3 minors) and the second list could have 2 adult passengers and 2 minor passengers. To skip the third adult (or the amount that was), as there is no others adults and the incomming passanger is a minor, it must be left blank. I solved it as follows.
var count = 0;
for (int i = 0; i < objFilePassengers.Count; i++)
{
if ((objFilePassengers[i].Type == "1" && objPassengers[i].TypeId == "1") || (objPassengers[i].TypeId == "2" && objFilePassengers[i].Type == "2" && objFilePassengers[i].Age < 18))
{
objPassengers[i].PassengerId = objFilePassengers[i].PassengerID;
objPassengers[i].TypeId = objFilePassengers[i].Type;
objPassengers[i].FirstName = objFilePassengers[i].Name.ToTitleCase();
objPassengers[i].LastName = objFilePassengers[i].LastName.ToTitleCase();
objPassengers[i].Age = objFilePassengers[i].Age;
}
else if ((objPassengers[i].TypeId == "1" && objFilePassengers[i].Type == "2"))
{
objFilePassengers.Insert(count + 1, new clsEnix_Passenger
{
PassengerID = objPassengers[i].PassengerId,
Name = "",
LastName = "",
});
count++;
}
}
Example result

How can I read a complete XML element here?

I have an XML file with a number of Units:
<Unit Name="Length">
<Prefix Char="c"
IsSelected="false"
Factor="1"/>
<Prefix Char="d"
IsSelected="true"
Factor="104"/>
</Unit>
I want to read an entire object:
public static Dictionary<string, Unit> Units { get; set; }
public class Prefix
{
public Func<double, double> fnc = null;
public Prefix(string c, double f, bool i, bool ifix = false,string fn = null)
{
Char = c;
Factor = f;
IsFixed = ifix;
Unit.funcs.TryGetValue(fn, out fnc);
}
public bool IsSelected { get; set; }
public bool IsFixed { get; set; }
public double Factor { get; set; }
public string Char { get; set; }
}
public Unit() { }
public Unit(string n, List<Prefix> p)
{
_name = n;
Prefixes = p;
}
private List<Prefix> _prefixes;
public List<Prefix> Prefixes
{
get { return _prefixes; }
set { _prefixes = value; }
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
....
}
I now have this:
Form.Units = (data.Descendants("Unit").Select(x => new Unit
(
x.Attribute("Name").Value,
(List<Prefix>) x.Descendants("Prefix").Select(p => new Prefix(
p.Attribute("Char").Value,
Convert.ToDouble(p.Attribute("Factor").Value),
p.Attribute("IsSelected").Value == "true",
p.Attribute("IsFixed").Value == "true",
p.Attribute("Func").Value)
)
)
)
).ToDictionary(x => x.Name, x => x);
and get the following error:
"Unable to cast object of type
'WhereSelectEnumerableIterator2[System.Xml.Linq.XElement,DD.Prefix]'
to type 'System.Collections.Generic.List1[DD.Prefix]'."
Apparently there is something wrong with the (List<Prefix>)
What would the query have to be then? How do I get stuff in the List<>?
.
I would express this code in the following manner.. for it avoids doing parameter constructors that would constrain these types to IEnuermable expressions. i.e. avoid doing parameter constructors on types you plan to use for querying..
I'm placeholding the types that are using in/by Decendents as XmlElement type... but I'm sure that's inaccurate.. just replace it with whatever is the correct type.
Additionally, this snippet doesn't account for Unit.funcs.TryGetValue(fn, out fnc);.. and it presumes there is property name Func on the Prefix type. You can perform null checks during he assignment/setting..
data.Descendants("Unit")
.Select<XmlElement, Unit>(x => new Unit()
{
Name = x.Attribute("Name").Value,
Prefixes = x.Descendants("Prefix").Select<XmlElement, Prefix>(p => new Prefix()
{
Char = p.Attribute("Char").Value,
Factor = Convert.ToDouble(p.Attribute("Factor").Value),
IsSelectd = p.Attribute("IsSelected").Value == "true",
IsFixed = p.Attribute("IsFixed").Value == "true",
Func = p.Attribute("Func").Value
}).ToList()
})
.Select<Unit, KeyValuePair<string, Unit>>(unit => new KeyValuePair<string, Unit>()
{
Key = x.Name,
Value = x
})
.ToList()
.ForEach( kvp => {
Form.Units.Add(kvp.Key, kvp.Value);
});
It's not a list as is but a query, so you can't just cast it to a list but you can call ToList on it to have it enumerated and a list returned:
Form.Units = (data.Descendants("Unit").Select(x => new Unit
(
x.Attribute("Name").Value,
//(List<Prefix>) no casting needed
x.Descendants("Prefix").Select(p => new Prefix(
p.Attribute("Char").Value,
Convert.ToDouble(p.Attribute("Factor").Value),
p.Attribute("IsSelected").Value == "true",
p.Attribute("IsFixed").Value == "true",
p.Attribute("Func").Value)
).ToList() // you had an IEnumerable<Prefix> now this is a List<Prefix>
)
)
).ToDictionary(x => x.Name, x => x);
To expand upon my comments, there are a couple of things here. First, you can use the .ToList() extension method on the .Select to convert the IEnumerable<T> collection to a List<T>.
Secondly, you will get a null reference exception if any attributes or elements are missing in the query. You can handle this safely by explicitly casting to string (and then converting the result if needed).
The updated query would look like this:
Form.Units = (data.Descendants("Unit").Select(x => new Unit
((string)x.Attribute("Name"),
x.Descendants("Prefix").Select(p => new Prefix(
(string)p.Attribute("Char"),
Convert.ToDouble((string)p.Attribute("Factor")),
(string)p.Attribute("IsSelected") == "true",
(string)p.Attribute("IsFixed") == "true",
(string)p.Attribute("Func")).ToList()
)
)
)
).ToDictionary(x => x.Name, x => x);
Note that you don't need .Value when using (string) (since .Value is already string).

Get first Item in list

I am using this function to get a list of groups
public static IEnumerable<QuizGroups> GetGroups(string sectorId)
{
var Quizes = _QuizDataSource.AllQuizGroups.Where(x => x.Subtitle == sectorId);
return Quizes;
}
But this returns all, I want to return the first group in the list.
How do i change the statement to get the first group?
You can use First or FirstOfDefault and return enumerable with single element:
public static IEnumerable<QuizGroups> GetGroups(string sectorId)
{
return new[]
{
_QuizDataSource.AllQuizGroups.FirstOrDefault(x => x.Subtitle == sectorId)
};
}
Also note:
If you plan to always return one element only, consider refactoring GetGroups method to return QuizGroups only.
AllQuizGroups property may be null. Add null-check for that property.
You should consider using C# Naming Conventions.
Local variable names should be Camel Cased; so it should be: var quizes.
Property _QuizDataSource should be renamed to QuizDataSource if it is public or _quizDataSource or quizDataSource if it is private.¹
¹ — It's a heat debate whether to use _ or not for private fields, read more at: https://stackoverflow.com/questions/1630844/c-sharp-member-variables-fields-naming-convention
Try First or FirstOrDefault.
public static IEnumerable<QuizGroups> GetGroups(string sectorId)
{
var Quizes = _QuizDataSource.AllQuizGroups.FirstOrDefault(x => x.Subtitle == sectorId);
return Quizes;
}
public static QuizGroups GetGroups(string sectorId)
{
return QuizDataSource.AllQuizGroups.FirstOrDefault(
x => x.Subtitle == sectorId);
}
or add an indexer to the collection class (assuming MyQuizGroupsCollection is the type of _QuizDataSource
public class MyQuizGroupsCollection: IEnumerable<QuizGroups>
{
// other stuff
public QuizGroups this[string sectorId]
{ get { return FirstOrDefault(x => x.Subtitle == sectorId); } }
}
... then you could simply write
var Quizes = _QuizDataSource[sectorId];

Group a list by another list within it with LINQ

I'm looking to group a list based on a list within that list itself, given the following data structure:
public class AccDocumentItem
{
public string AccountId {get;set;}
public List<AccDocumentItemDetail> DocumentItemDetails {get;set;}
}
And
public class AccDocumentItemDetail
{
public int LevelId {get;set;}
public int DetailAccountId {get;set;}
}
I now have a List<AccDocumentItem> comprised of 15 items, each of those items has a list with variable number of AccDocumentItemDetail's, the problem is that there may be AccDocumentItems that have identical AccDocumentItemDetails, so I need to group my List<AccDocumentItem> by it's AccDocumentItemDetail list.
To make it clearer, suppose the first 3 (of the 15) elements within my List<AccDocumentItem> list are:
1:{
AccountId: "7102",
DocumentItemDetails:[{4,40001},{5,40003}]
}
2:{
AccountId: "7102",
DocumentItemDetails:[{4,40001},{6,83003},{7,23423}]
}
3:{
AccountId: "7102",
DocumentItemDetails:[{4,40001},{5,40003}]
}
How can I group my List<AccDocumentItem> by it's DocumentItemDetails list such that row 1 and 3 are in their own group, and row 2 is in another group?
Thanks.
You could group by the comma separated string of detail-ID's:
var query = documentItemList
.GroupBy(aci => new{
aci.AccountId,
detailIDs = string.Join(",", aci.DocumentItemDetails
.OrderBy(did => did.DetailAccountId)
.Select(did => did.DetailAccountId))
});
Another, more ( elegant,efficient,maintainable ) approach is to create a custom IEqualityComparer<AccDocumentItem>:
public class AccDocumentItemComparer : IEqualityComparer<AccDocumentItem>
{
public bool Equals(AccDocumentItem x, AccDocumentItem y)
{
if (x == null || y == null)
return false;
if (object.ReferenceEquals(x, y))
return true;
if (x.AccountId != y.AccountId)
return false;
return x.DocumentItemDetails
.Select(d => d.DetailAccountId).OrderBy(i => i)
.SequenceEqual(y.DocumentItemDetails
.Select(d => d.DetailAccountId).OrderBy(i => i));
}
public int GetHashCode(AccDocumentItem obj)
{
if (obj == null) return int.MinValue;
int hash = obj.AccountId.GetHashCode();
if (obj.DocumentItemDetails == null)
return hash;
int detailHash = 0;
unchecked
{
foreach (var detID in obj.DocumentItemDetails.Select(d => d.DetailAccountId))
detailHash = detailHash * 23 + detID;
}
return hash + detailHash;
}
}
Now you can use it for GroupBy:
var query = documentItemList.GroupBy(aci => aci, new AccDocumentItemComparer());
You can use that for many other Linq extension methods like Enumerable.Join etc. also.

How can I get LINQ to return the object which has the max value for a given property? [duplicate]

This question already has answers here:
How to perform .Max() on a property of all objects in a collection and return the object with maximum value [duplicate]
(9 answers)
How to use LINQ to select object with minimum or maximum property value
(20 answers)
Closed 7 years ago.
If I have a class that looks like:
public class Item
{
public int ClientID { get; set; }
public int ID { get; set; }
}
And a collection of those items...
List<Item> items = getItems();
How can I use LINQ to return the single "Item" object which has the highest ID?
If I do something like:
items.Select(i => i.ID).Max();
I'll only get the highest ID, when what I actually want returned is the Item object itself which has the highest ID? I want it to return a single "Item" object, not an int.
This will loop through only once.
Item biggest = items.Aggregate((i1,i2) => i1.ID > i2.ID ? i1 : i2);
Thanks Nick - Here's the proof
class Program
{
static void Main(string[] args)
{
IEnumerable<Item> items1 = new List<Item>()
{
new Item(){ ClientID = 1, ID = 1},
new Item(){ ClientID = 2, ID = 2},
new Item(){ ClientID = 3, ID = 3},
new Item(){ ClientID = 4, ID = 4},
};
Item biggest1 = items1.Aggregate((i1, i2) => i1.ID > i2.ID ? i1 : i2);
Console.WriteLine(biggest1.ID);
Console.ReadKey();
}
}
public class Item
{
public int ClientID { get; set; }
public int ID { get; set; }
}
Rearrange the list and get the same result
.OrderByDescending(i=>i.id).First()
Regarding the performance concern, it is very likely that this method is theoretically slower than a linear approach. However, in reality, most of the time we are not dealing with the data set that is big enough to make any difference.
If performance is a main concern, Seattle Leonard's answer should give you linear time complexity. Alternatively, you may also consider to start with a different data structure that returns the max value item at constant time.
First() will do the same as Take(1) but returns the item directly instead of an enumeration containing the item.
int max = items.Max(i => i.ID);
var item = items.First(x => x.ID == max);
This assumes there are elements in the items collection of course.
Use MaxBy from the morelinq project:
items.MaxBy(i => i.ID);
This is an extension method derived from #Seattle Leonard 's answer:
public static T GetMax<T,U>(this IEnumerable<T> data, Func<T,U> f) where U:IComparable
{
return data.Aggregate((i1, i2) => f(i1).CompareTo(f(i2))>0 ? i1 : i2);
}
In case you don't want to use MoreLINQ and want to get linear time, you can also use Aggregate:
var maxItem =
items.Aggregate(
new { Max = Int32.MinValue, Item = (Item)null },
(state, el) => (el.ID > state.Max)
? new { Max = el.ID, Item = el } : state).Item;
This remembers the current maximal element (Item) and the current maximal value (Item) in an anonymous type. Then you just pick the Item property. This is indeed a bit ugly and you could wrap it into MaxBy extension method to get the same thing as with MoreLINQ:
public static T MaxBy(this IEnumerable<T> items, Func<T, int> f) {
return items.Aggregate(
new { Max = Int32.MinValue, Item = default(T) },
(state, el) => {
var current = f(el.ID);
if (current > state.Max)
return new { Max = current, Item = el };
else
return state;
}).Item;
}
Or you can write your own extension method:
static partial class Extensions
{
public static T WhereMax<T, U>(this IEnumerable<T> items, Func<T, U> selector)
{
if (!items.Any())
{
throw new InvalidOperationException("Empty input sequence");
}
var comparer = Comparer<U>.Default;
T maxItem = items.First();
U maxValue = selector(maxItem);
foreach (T item in items.Skip(1))
{
// Get the value of the item and compare it to the current max.
U value = selector(item);
if (comparer.Compare(value, maxValue) > 0)
{
maxValue = value;
maxItem = item;
}
}
return maxItem;
}
}
try this:
var maxid = from i in items
group i by i.clientid int g
select new { id = g.Max(i=>i.ID }
In LINQ you can solve it the following way:
Item itemMax = (from i in items
let maxId = items.Max(m => m.ID)
where i.ID == maxId
select i).FirstOrDefault();
You could use a captured variable.
Item result = items.FirstOrDefault();
items.ForEach(x =>
{
if(result.ID < x.ID)
result = x;
});

Categories