Auto-incrementing a generic list using LINQ in C# - c#

Is there a good way to provide an "auto-increment" style index column (from 1..x) when projecting items using LINQ?
As a basic example, I'm looking for the index column below to go from 1 to the number of items in list.
var items = from s1 in list
select new BrowsingSessionItemModel { Id = s1.Id, Index = 0 };
Iterating through the list would be the easy option but I was wondering if there was a better way to do this?

You can't do this with LINQ expressions. You could use the following .Select extension method though:
var items = list.Select((x, index) => new BrowsingSessionItemModel {
Id = x.Id,
Index = index
});

You can use the overload of Select which takes the provides the index to the projection as well:
var items = list.Select((value, index) => new BrowsingSessionItemModel {
Id = value.Id,
Index = index
});
Note that there is no query expression support for this overload. If you're actually fetching the values from a database (it's not clear whether list is really a List<T>) you should probably make sure you have an appropriate ordering, as otherwise the results are somewhat arbitrary.

Related

Can I use LINQ to find out if a given property in a list repeats itself?

I have a list of objects that have a name field on them.
I want to know if there's a way to tell if all the name fields are unique in the list.
I could just do two loops and iterate over the list for each value, but I wanted to know if there's a cleaner way to do this using LINQ?
I've found a few examples where they compare each item of the list to a hard coded value but in my case I want to compare the name field on each object between each other and obtain a boolean value.
A common "trick" to check for uniqueness is to compare the length of a list with duplicates removed with the length of the original list:
bool allNamesAreUnique = myList.Select(x => x.Name).Distinct().Count() == myList.Count();
Select(x => x.Name) transforms your list into a list of just the names, and
Distict() removes the duplicates.
The performance should be close to O(n), which is better than the O(n²) nested-loop solution.
Another option is to group your list by the name and check the size of those groups. This has the additional advantage of telling you which values are not unique:
var duplicates = myList.GroupBy(x => x.Name).Where(g => g.Count() > 1);
bool hasDuplicates = duplicates.Any(); // or
List<string> duplicateNames = duplicates.Select(g => g.Key).ToList();
While you can use LINQ to group or create a distinct list, and then compare item-wise with the original list, that incurs a bit of overhead you might not want, especially for a very large list. A more efficient solution would store the keys in a HashSet, which has better lookup capability, and check for duplicates in a single loop. This solution still uses a little bit of LINQ so it satisfies your requirements.
static public class ExtensionMethods
{
static public bool HasDuplicates<TItem,TKey>(this IEnumerable<TItem> source, Func<TItem,TKey> func)
{
var found = new HashSet<TKey>();
foreach (var key in source.Select(func))
{
if (found.Contains(key)) return true;
found.Add(key);
}
return false;
}
}
If you are looking for duplicates in a field named Name, use it like this:
var hasDuplicates = list.HasDuplicates( item => item.Name );
If you want case-insensitivity:
var hasDuplicates = list.HasDuplicates( item => item.Name.ToUpper() );

Implicit Index or Generating Index in LINQ

I recently encounter a couple of cases where it makes me wonder if there is any way to get internal index, or if not, to generate index efficiently when using LINQ.
Consider the following case:
List<int> list = new List() { 7, 4, 5, 1, 7, 8 };
In C#, if we are to return the indexes of "7" on the above array using LINQ, we cannot simply using IndexOf/LastIndexOf - for it will only return the first/last result.
This will not work:
var result = list.Where(x => x == 7).Select(y => list.IndexOf(y));
We have several workarounds for doing it, however.
For instance, one way of doing it could be this (which I typically do):
int index = 0;
var result = from x in list
let indexNow = index++
where x == 7
select indexNow;
It will introduce additional parameter indexNow by using let.
And also another way:
var result = Enumerable.Range(0, list.Count).Where(x => list.ElementAt(x) == 7);
It will generate another set of items by using Enumerable.Range and pick the result from it.
Now, I am wondering if there is any alternative, simpler way of doing it, such as:
if there is any (built-in) way to get the internal index of the IEnumerable without declaring something with let or
to generate the index for the IEnumerable using something other than Enumerable.Range (perhaps something like new? Which I am not too familiar how to do it), or
anything else which could shorten the code but still getting the indexes of the IEnumerable.
From IEnumerable.Select with index, How to get index using LINQ?, and so on: IEnumerable<T>.Select() has an overload that provides an index.
Using this, you can:
Project into an anonymous type (or a Tuple<int, T> if you want to expose it to other methods, anonymous types are local) that contains the index and the value.
Filter those results for the value you're looking for.
Project only the index.
So the code will look like this:
var result = list.Select((v, i) => new { Index = i, Value = v })
.Where(i => i.Value == 7)
.Select(i => i.Index);
Now result will contain an enumerable containing all indexes.
Note that this will only work for source collection types that guarantee insertion order and provide a numeric indexer, such as List<T>. When you use this for a Dictionary<TKey, TValue> or HashSet<T>, the resulting indexes will not be usable to index into the dictionary.

Linq: Find elements of 2 list with different values but same index

I have 2 list of points (List<Point>) for the coordinates of some label elements. One list for before and one list for after they were moved, so the indexes refer to the same label elements. I want to compare each element with the same index and see which had their points changed.
List<int> changedIndexes = new List<int>();
for(int i = 0; i < labelLocationsBefore.Count; i++)
{
if (labelLocationsBefore[i].X != labelLocationsAfter[i].X || labelLocationsBefore[i].Y != labelLocationsAfter[i].Y)
{
changedIndexes.Add(i);
}
}
Which is what this loop does. But how can I convert this into a Linq expression and retrieve the changed labels index?
You are looking for this overload of Select method which takes a Func<T, int, bool> where the second argument is the index:
changedIndexes = labelLocationsBefore
.Select((point,idx) => new { point, idx })
.Where(p => p.point.X != labelLocationsAfter[p.idx].X ||
p.point.Y != labelLocationsAfter[p.idx].Y)
.Select(p => p.idx)
.ToList();
One option is to use Enumerable.Zip to join the two collections, then Select to get the index of each joined pair, then filter appropriately:
var changedIndexes = labelLocationsBefore
.Zip(labelLocationsAfter, (before, after) => before.Equals(after))
.Select((equal, index) => new { Moved = !equal, Index = index })
.Where(result => result.Moved)
.Select(result => result.Index)
.ToList();
This snippet has a few nice properties (it's based on an expression, easy to read, there is no repetition), but it's necessarily more cumbersome and less performant than a straight for loop because of the need to produce the "moved?/index" pair for all before/after sets of points -- even for those where simply determining that they have not been moved would be enough to disregard them.

remove all element except from given index number

I have following list and how can I remove with linq all elements from given index number:
List<string> a = new List<string>();
a.Add("number1");
a.Add("number2");
a.Add("number3");
How can I remove all element just except element which is index number =2 using linq.
LINQ isn't about removing things - it's about querying.
You can call RemoveRange to remove a range of items from a list though. So:
a.RemoveRange(0, 2);
will leave just "number3".
Or you could create a new list as per dasblinkenlight's answer. If you can tell us more about what you're trying to achieve and why you think LINQ is the solution, we may be able to help you more.
EDIT: Okay, now we have clearer requirements, you can use LINQ:
var newList = a.Where((value, index) => index != 2)
.ToList();
Assume you have list of indices you want to keep, you can use Where with index to filter:
var indexList = new[] {2};
var result = a.Where((s, index) => indexList.Contains(index));
An equivalent operation to "remove everything but X" is "keep X". The simplest way to do it is constructing a new list with a single element at index 2, like this:
a = new List<string>{a[2]};
Although #dasblinkenlight's answer is the better option, here is the linq (or at least one iteration)
a.Where((item,index) => b1 == 2);
or to return a single string objects rather than an IEnumberable
a.Where((a1,b1) => b1 == 2).First();

Numbering the rows per group after an orderby in LINQ

Say you have columns AppleType, CreationDate and want to order each group of AppleType by CreationDate. Furthermore, you want to create a new column which explicitly ranks the order of the CreationDate per AppleType.
So, the resulting DataSet would have three columns, AppleType, CreationDate, OrderIntroduced.
Is there a LINQ way of doing this? Would I have to actually go through the data programmatically (but not via LINQ), create an array, convert that to a column and add to the DataSet? I have there is a LINQ way of doing this. Please use LINQ non-method syntax if possible.
So are the values actually appearing in the right order? If so, it's easy - but you do need to use method syntax, as the query expression syntax doesn't support the relevant overload:
var queryWithIndex = queryWithoutIndex.Select((x, index) => new
{
x.AppleType,
x.CreationDate,
OrderIntroduced = index + 1,
});
(That's assuming you want OrderIntroduced starting at 1.)
I don't know offhand how you'd then put that back into a DataSet - but do you really need it in a DataSet as opposed to in the strongly-typed sequence?
EDIT: Okay, the requirements are still unclear, but I think you want something like:
var query = dataSource.GroupBy(x => x.AppleType)
.SelectMany(g => g.OrderBy(x => x.CreationDate)
.Select((x, index ) => new {
x.AppleType,
x.CreationDate,
OrderIntroduced = index + 1 }));
Note: The GroupBy and SelectMany calls here can be put in query expression syntax, but I believe it would make it more messy in this case. It's worth being comfortable with both forms.
If you want a pure Linq to Entities/SQL solution you can do something like this:
Modified to handle duplicate CreationDate's
var query = from a in context.AppleGroup
orderby a.CreationDate
select new
{
AppleType = a.AppleType,
CreationDate = a.CreationDate,
OrderIntroduced = (from b in context.AppleGroup
where b.CreationDate < a.CreationDate
select b).Count() + 1
};

Categories