LINQ Distinct Count returns 1 - c#

Im making a delegate Func inside my method to check if schedualCode fits in a certain place in a list, where the limit is 3.
i want to count the distinct values of schedualCode in my list. my problem is that schedualCodeCount returns 1. when it should return 2.
this is my code
Func<string, bool> CheckTimeLimit = delegate (string schedualCode)
{
// check enrolled period count (where limit is 3)
//int periodCount = currentEnrollments.GroupBy(t => t.Times)
//.Select(t => t.Key.Select(key => key.PeriodCode == time.PeriodCode).Distinct()).Count();
var allTimes = currentEnrollments.SelectMany(key => key.Times).ToList();
List<string> schedualCodes = allTimes.Where(key => key.SchedualCode == schedualCode && key.ViewOnSchedual)
.Select(key => key.SchedualCode).ToList();
//schedualCodes List returns a list of count = 2 , and 2 strings exactly the same of value = "A1"
// Getting the distinct count of "A1"
int schedualCodeCount = schedualCodes.Distinct().Count();
// schedualCodeCount gets the value = 1, where it should be 2
// time fits if true
return schedualCodeCount < 3;
};

You are misunderstanding what Distinct does. You have two identical items, Distinct will remove the duplicates leaving you with 1. What you probably want to do is Group and then get the counts of each group.
For example:
var list = new List<string>() { "A1", "A1" };
Console.WriteLine(list.Count); // 2, obviously
var distinct = list.Distinct(); // select only the *distinct* values
Console.WriteLine(distinct.Count()); // 1 - because there is only 1 distinct value
var groups = list.GroupBy(s => s); // group your list (there will only be one
// in this case)
foreach (var g in groups) // for each group
{
// Display the number of items with the same key
Console.WriteLine(g.Key + ":" + g.Count());
}

Related

Listing after implementing ranking skipping numbers

I am trying to achieve ranking functionality as below:
Name Points rank
ram 9 1
kamal 9 1
preet 8 2
lucky 7 3
kishan 6.5 4
devansh 6 5
neha 6 5
I have used below code to achieve this:
finalResult = finalResult.OrderByDescending(i => i.points).ThenBy(i => i.academy).ToList();
finalResult = finalResult.AsEnumerable() // Client-side from here on
.Select((player, index) => new RankingEntity()
{
competitorid = player.competitorid,
firstname = player.firstname,
lastname = player.lastname,
academy = player.academy,
points = player.points,
place = player.place,
eventId = player.eventId,
eventname = player.eventname,
categoryname = player.categoryname,
Rank = index + 1
}).ToList();
var t = (from i in finalResult
let rank = finalResult.First(x => x.points == i.points)
select new
{
Col1 = i,
Rank = rank.Rank
}).ToList();
List<RankingEntity> ttt = new List<RankingEntity>();
foreach (var item in t)
{
var a = item.Col1;
var row = new RankingEntity();
row.competitorid = a.competitorid;
row.firstname = a.firstname;
row.lastname = a.lastname;
row.academy = a.academy;
row.points = a.points;
row.place = a.place;
row.eventId = a.eventId;
row.eventname = a.eventname;
row.categoryname = a.categoryname;
row.Rank = item.Rank;
ttt.Add(row);
}
And i am getting result like below:
Please help what i am doing wrong.
What you are trying to achieve is a ranking of a "group" so group the results by the points and then order the groups. For each item in the group give the same rank.
finalResult.GroupBy(item => item.Points) // Group by points
.OrderDescendingBy(g => g.Key) // Order the groups
.Select((g, index) => new { Data = g, GroupRank = index + 1}) // Rank each group
.SelectMany(g => g.Data.Select(item => new RankingEntity
{
/* properties of each item */
Rank = g.GroupIndex
}); // Flatten groups and set for each item the group's ranking
The problem in your method is that you give the ranking for individual items and not the group. Then when you retrieve the rank for the group (from i in finalResult let rank = finalResult.First(x => x.points == i.points)...) you actually set for each item in the group the ranking of one of the elements in it. Therefore, if you first got the last item of the group - that will be the Rank value of each item in it.
Also notice that in the first line of your code you use ToList. Therefore there is not need to use AsEnumerable in the line under it - it is already a materialized in memory collection.

How to Order an out of sequence sequence

Consider this list of elements which has three properties, Value and Sequence Number and Group:
Value Sequence Number Group
Header, 1, null
Invoice, 2, 1
Invoice Line, 3, 1
Trailer, 4, null
The goal is only to sort by the sequence number. The value is irrelevant.
In the above, the obvious answer is to order by sequence number.
However, elements can repeat:
Header, 1, null
InvoiceA, 2, 1
Line Item, 3, 1
InvoiceB, 2, 2
Line Item, 3, 2
Trailer, 4, null
The above is the desired sequence. What Linq statement will produce the above?
Sorting by Sequence no longer works. Sorting by Group, then Sequence does not work.
The application of this is in EDI where the order of the data is significant.
So the first "trick" here is that you want all items with a null group to be separate groups, rather than having all null items combined into a single group.
This is actually fairly easy. We can just create an IEqualityComparer that compares items based on some other comparer, but that always considers two null items to be different, instead of being the same (typically two null items would be considered "equal").
public class SeparateNullComparer<T> : IEqualityComparer<T>
{
private IEqualityComparer<T> comparer;
public SeparateNullComparer(IEqualityComparer<T> comparer = null)
{
this.comparer = comparer ?? EqualityComparer<T>.Default;
}
public bool Equals(T x, T y)
{
if (x == null || y == null)
return false;
return comparer.Equals(x, y);
}
public int GetHashCode(T obj)
{
return comparer.GetHashCode(obj);
}
}
We can now group the items using this comparer so that all non-null items will be grouped together, whereas all of the null items will have their own groups.
Now how do we order the groups? We need to order these groups based on their sequence numbers, but we have a sequence of them, not just one, so we need a way of comparing two sequences to see which sequence comes first. We do this by checking the first item in each sequence, and then continually checking the next until one comes first or one ends and the other doesn't:
public class SequenceComparer<T> : IComparer<IEnumerable<T>>
{
private IComparer<T> comparer;
public SequenceComparer(IComparer<T> compareer = null)
{
this.comparer = comparer ?? Comparer<T>.Default;
}
public int Compare(IEnumerable<T> x, IEnumerable<T> y)
{
using (var first = x.GetEnumerator())
using (var second = x.GetEnumerator())
{
while (true)
{
var firstHasMore = first.MoveNext();
var secondHasMore = second.MoveNext();
if (!firstHasMore && !secondHasMore)
return 0;
var lengthComparison = firstHasMore.CompareTo(secondHasMore);
if (lengthComparison != 0)
return lengthComparison;
var nextComparison = comparer.Compare(first.Current, second.Current);
if (nextComparison != 0)
return nextComparison;
}
}
}
}
Combine that with flattening all of the groups back out when we're done, and we just need to put it all together:
var query = data.GroupBy(item => item.Group, new SeparateNullComparer<int?>())
.Select(group => group.OrderBy(item => item.SequenceNumber)
.ToList())
.OrderBy(group => group, new SequenceComparer<Foo>())
.ThenBy(group => group.First().Group)
.SelectMany(x => x);
You can also rely on the fact that GroupBy maintains the original order of items within groups, allowing you to order the data by SequenceNumber before grouping, instead of after. It'll do basically the same thing. It turns out to be a prettier query, but you just need to "know" that GroupBy maintains the proper ordering:
var query = data.OrderBy(item => item.SequenceNumber)
.GroupBy(item => item.Group, new SeparateNullComparer<int?>())
.OrderBy(group => group, new SequenceComparer<Foo>())
.ThenBy(group => group.Key)
.SelectMany(x => x);
If it doesn't have to be a linq query, you could write a single comparer that looks like this:
public class ValSeqGroupComparer : IComparer<ValSeqGroup>
{
public int Compare(ValSeqGroup x, ValSeqGroup y)
{
if (x == y) return 0;
// If only one has a group or there is no group in either
if (x.Group.HasValue ^ y.Group.HasValue || !x.Group.HasValue)
return x.Seq.CompareTo(y.Seq);
if (x.Group.Value != y.Group.Value)
return x.Group.Value.CompareTo(y.Group.Value);
return x.Seq.CompareTo(y.Seq);
}
}
Then using it like this:
[TestMethod]
public void One()
{
List<ValSeqGroup> items = new List<ValSeqGroup>()
{
new ValSeqGroup("x", 1, null),
new ValSeqGroup("x", 4, null),
new ValSeqGroup("x", 2, 1),
new ValSeqGroup("x", 2, 2),
new ValSeqGroup("x", 3, 1),
new ValSeqGroup("x", 3, 2)
};
items.Sort(new ValSeqGroupComparer());
foreach (var item in items)
{
Console.WriteLine("{0} {1} {2}", item.Value, item.Seq,item.Group);
}
}
You can achieve this by sorting the elements by Sequence Number (OrderBy(x => x.SequenceNumber)).
After that you can sort elements with exising group number (.Where(x => x.Group != null).OrderBy(x => x.Group))
In the end you have to insert null elements in list at the corresponding index.
var elements = new List<Element>
{
new Element{SequenceNumber = 1, Group = null}, new Element{SequenceNumber = 4, Group = null},new Element{SequenceNumber = 3, Group = 1},new Element{SequenceNumber = 3, Group = 3}, new Element{SequenceNumber = 3, Group = 2},new Element{SequenceNumber = 2, Group = 3},new Element{SequenceNumber = 2, Group = 1},new Element{SequenceNumber = 2, Group = 2}
};
// first sort
var sortedElements = elements.OrderBy(x => x.SequenceNumber).ToList();
// save null elements
var elementsWithNull = sortedElements
.Where(x => x.Group == null).ToList();
// group sorting
sortedElements = sortedElements
.Where(x => x.Group != null)
.OrderBy(x => x.Group).ToList();
// insert elements with null in sorted list
foreach (var element in elementsWithNull)
{
var firstIndexOfSequence = 0;
for (firstIndexOfSequence = 0;firstIndexOfSequence < sortedElements.Count && sortedElements[firstIndexOfSequence].SequenceNumber >= element.SequenceNumber; firstIndexOfSequence++)
{
// just to get index of the element with null group to know where to insert
}
sortedElements.Insert(firstIndexOfSequence, element);
}

Get Row Index in a list by using entity framework

I have 5 datas in my list.
MyList
So i want to know , how can i get selected Id's Row Number ?
var MyProductId= 135;
var SelectedDataRowNumber = context.SP_MyList.ToList()
.Where(s=>s.Id==MyProductId)
.FirstOrDefault();
As instance,
My List has 5 datas like below,
Id-Name
6,Computer
135,KeyBoard
68,Mouse
98,Telephone
213,Laptop,
MyProductId is 135 so it matchs with Keyboard.It is row count number( index )must be "1" because 0 is Computer.
How can i get selected Id's row count(index) number ?
You can get it directly the element by Index if your list has implemented indexer. You can find that How to implement Indexer.
Another way could be as described in Here.
The above link shows it like:
COPIED From Above link:
Person agedTwenty = myList.Where<Person>( x => return x.Age == 20; ).Single<Person>();
int index = myList.IndexOf(agedTwenty);
or alternatively
int index = myList.Where<Person>( x => return x.Age == 20; ).Select<Person,int>( x =>
myList.IndexOf(x)).Single<int>();
In case there can be more than one result you'd do this:
IEnumerable<Person> allAgedTwenty = myList.Where<Person>( x => return x.Age == 20; );
IEnumerable<int> indices = allAgedTwenty.Select<Person,int>( x => myList.IndexOf(x) );
The first case will get you only one int and the second case will leave you with a list of ints.
If you want the count you need to groupBy
var SelectedDataRowNumber = context.SP_MyList.ToList()
.GroupBy(x = x.Id)
.Where(s=>s.Key == MyCustomerId )
.Select(x => new {Id = x.Key, Count = x.Count()});
This gives you the output
Id = 135
Count = 3 //depending on the actual count
alternative solution, if you want to output id, name and count:
//"items" is my custom list I created to reflect your data
var SelectedDataRowNumber = from x in items
group x by new {x.Id, x.Name} into g
where g.Key.Id == 135
select new
{
ID = g.Key.Id,
Name = g.Key.Name,
Count = g.Count()
};
Regarding your comment: is this the result you are expecting: http://abload.de/img/so1j5rta.jpg ?

Optimizing a value based search algorithm with LINQ

I want to build a value based search algorithm. What this means is that once I'm given a list of words I would like to search for entries on the database using those words. However depending on what column/property those words match, I want to alter the value of results returned.
Here is a lazy algorithm that achieves that but is very slow.
//search only active entries
var query = (from a in db.Jobs where a.StatusId == 7 select a);
List<SearchResult> baseResult = new List<SearchResult>();
foreach (var item in search)
{
//if the company title is matched, results are worth 5 points
var companyMatches = (from a in query where a.Company.Name.ToLower().Contains(item.ToLower()) select new SearchResult() { ID = a.ID, Value = 5 });
//if the title is matched results are worth 3 points
var titleMatches = (from a in query where a.Title.ToLower().Contains(item.ToLower()) select new SearchResult() { ID = a.ID, Value = 3 });
//if text within the body is matched results are worth 2 points
var bodyMatches = (from a in query where a.FullDescription.ToLower().Contains(item.ToLower()) select new SearchResult() { ID = a.ID, Value = 2 });
//all results are then added
baseResult = baseResult.Concat(companyMatches.Concat(titleMatches).Concat(bodyMatches)).ToList();
}
// the value gained for each entry is then added and sorted by highest to lowest
List<SearchResult> result = baseResult.GroupBy(x => x.ID).Select(p => new SearchResult() { ID = p.First().ID, Value = p.Sum(i => i.Value) }).OrderByDescending(a => a.Value).ToList<SearchResult>();
//the query for the complete result set is built based on the sorted id value of result
query = (from id in result join jbs in db.Jobs on id.ID equals jbs.ID select jbs).AsQueryable();
I'm looking for ways to optimize this. I am new to LINQ query so I was hoping I could get some help. If there is away I can create the LINQ query that achieves all of this in one go instead of checking for company name and then title and the body text and bringing it all together and creating a sorted list and running it again against the database to get full listing it would be great.
It's best if I study the problem first. My previous answer was optimizing the wrong thing. The primary problem here is going over the results list multiple times. We can change that:
foreach (var a in query)
{
foreach (var item in search)
{
itemLower = item.ToLower();
int val = 0;
if (a.Company.Name.ToLower.Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 5});
if (a.Title.ToLower.Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 3});
if (a.FullDescription.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 2});
}
}
After that, you have your base result and you can continue with your processing.
That reduces it to a single query rather than three queries for each search item.
I wasn't sure if you wanted unique items in your baseResult, or if there was some reason you allowed duplicates and then used the sum of the values to order them. If you want unique items, you could make baseResult a Dictionary, with the ID as the key.
Edit after comment
You could reduce the number of items in the list by doing:
int val = 0;
if (a.Company.Name.ToLower.Contains(itemLower))
val += 5;
if (a.Title.ToLower.Contains(itemLower))
val += 3;
if (a.FullDescription.ToLower().Contains(itemLower))
val += 2;
if (val > 0)
{
baseResult.Add(new SearchResult { ID = a.ID, Value = val });
}
That won't eliminate duplicates altogether, though, because the company name could match one search term, and the title might match another search term. But it would reduce the list somewhat.
Thanks to Jim's answer and some tweeking on my side I managed to reduce the time it takes to complete the search by 80%
Here is the final solution:
//establish initial query
var queryBase = (from a in db.Jobs where a.StatusId == 7 select a);
//instead of running the search against all of the entities, I first take the ones that are possible candidates, this is done through checking if they have any of the search terms under any of their columns. This is the one and only query that will be run against the database
if (search.Count > 0)
{
nquery = nquery.Where(job => search.All(y => (job.Title.ToLower() + " " + job.FullDescription.ToLower() + " " + job.Company.Name.ToLower() + " " + job.NormalLocation.ToLower() + " " + job.MainCategory.Name.ToLower() + " " + job.JobType.Type.ToLower()).Contains(y))); // + " " + job.Location.ToLower() + " " + job.MainCategory.Name.ToLower() + " " + job.JobType.Type.ToLower().Contains(y)));
}
//run the query and grab a list of baseJobs
List<Job> baseJobs = nquery.ToList<Job>();
//A list of SearchResult object (these object act as a container for job ids and their search values
List<SearchResult> baseResult = new List<SearchResult>();
//from here on Jim's algorithm comes to play where it assigns points depending on where the search term is located and added to a list of id/value pair list
foreach (var a in baseJobs)
{
foreach (var item in search)
{
var itemLower = item.ToLower();
if (a.Company.Name.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 5 });
if (a.Title.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 3 });
if (a.FullDescription.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 2 });
}
}
List<SearchResult> result = baseResult.GroupBy(x => x.ID).Select(p => new SearchResult() { ID = p.First().ID, Value = p.Sum(i => i.Value) }).OrderByDescending(a => a.Value).ToList<SearchResult>();
//the data generated through the id/value pair list are then used to reorder the initial jobs.
var NewQuery = (from id in result join jbs in baseJobs on id.ID equals jbs.ID select jbs).AsQueryable();

How to get values AND indices of duplicate items in a list?

I have a list of file names (targetFileList), some of which are duplicates (ex. I have two files called m4.txt). The following statement finds the duplicated filenames and adds them to another list (currentTargetFiles):
currentTargetFiles = targetFileList.FindAll(item => item == baselineFilename);
As is, this line is returning a list of strings (filenames), which is good, but I also need their index value. Is there some way to modify it so that it also returns the indices of the files?
Well, here is my answer to "find the duplicate names and their indices". It might not fit the presented problem exactly, as there is no baselineFilename considered - but that is covered by other answers. YMMV.
var names = new [] {"a", "a", "c", "b", "a", "b"};
var duplicatesWithIndices = names
// Associate each name/value with an index
.Select((Name, Index) => new { Name, Index })
// Group according to name
.GroupBy(x => x.Name)
// Only care about Name -> {Index1, Index2, ..}
.Select(xg => new {
Name = xg.Key,
Indices = xg.Select(x => x.Index)
})
// And groups with more than one index represent a duplicate key
.Where(x => x.Indices.Count() > 1);
// Now, duplicatesWithIndices is typed like:
// IEnumerable<{Name:string,Indices:IEnumerable<int>}>
// Let's say we print out the duplicates (the ToArray is for .NET 3.5):
foreach (var g in duplicatesWithIndices) {
Console.WriteLine("Have duplicate " + g.Name + " with indices " +
string.Join(",", g.Indices.ToArray()));
}
// The output for the above input is:
// > Have duplicate a with indices 0,1,4
// > Have duplicate b with indices 3,5
Of course, the provided results must be used correctly - and this depends on what must ultimately be done.
You can select all the items, with their indexes, with:
tempList = targetFileList.Select((item, index) =>
new { Value = item, Index = index }).Where(x => x.Value == baselineFilename);
Now, you can create lists of the names and corresponding indexes with:
var indexes = tempList.Select(x => x.Index).ToList();
And the values:
currentTargetFiles = tempList.Select(x => x.Value).ToList();
Then, indexes[0] will hold the list index of currentTargetFiles[0].
int i = -1;
var currentTargetFiles = targetFileList.Select(x => new
{
Value = x,
Index = i++
})
.Where(x => x.Value == baselineFilename);
Is linq a requirement?
A traditional for loop and a dictionary would do fine:
Dictionary<int, string> currentTargetFiles = new Dictionary<int, string>();
for (int i = 0; i < targetFileList.Count; ++i)
if(targetFileList[i] == baselineFilename)
currentTargetFiles.Add(i, targetFileList[i]);
P.S.:
Just realized that you comparing an exact string (item == baselineFilename).
If this is the case you don't even need to keep each value for each index (since all values are the same).
List<int> currentTargetFilesIndices = new List<int>();
for (int i = 0; i < targetFileList.Count; ++i)
if(targetFileList[i] == baselineFilename)
currentTargetFiles.Add(i);

Categories