Find an item in a list by LINQ - c#

Here I have a simple example to find an item in a list of strings. Normally I use a for loop or anonymous delegate to do it like this:
int GetItemIndex(string search)
{
int found = -1;
if ( _list != null )
{
foreach (string item in _list) // _list is an instance of List<string>
{
found++;
if ( string.Equals(search, item) )
{
break;
}
}
/* Use an anonymous delegate
string foundItem = _list.Find( delegate(string item) {
found++;
return string.Equals(search, item);
});
*/
}
return found;
}
LINQ is new for me. Can I use LINQ to find an item in the list? If it is possible, how?

There are a few ways (note that this is not a complete list).
Single will return a single result, but will throw an exception if it finds none or more than one (which may or may not be what you want):
string search = "lookforme";
List<string> myList = new List<string>();
string result = myList.Single(s => s == search);
Note that SingleOrDefault() will behave the same, except it will return null for reference types, or the default value for value types, instead of throwing an exception.
Where will return all items which match your criteria, so you may get an IEnumerable<string> with one element:
IEnumerable<string> results = myList.Where(s => s == search);
First will return the first item which matches your criteria:
string result = myList.First(s => s == search);
Note that FirstOrDefault() will behave the same, except it will return null for reference types, or the default value for value types, instead of throwing an exception.

If you want the index of the element, this will do it:
int index = list.Select((item, i) => new { Item = item, Index = i })
.First(x => x.Item == search).Index;
// or
var tagged = list.Select((item, i) => new { Item = item, Index = i });
int index = (from pair in tagged
where pair.Item == search
select pair.Index).First();
You can't get rid of the lambda in the first pass.
Note that this will throw if the item doesn't exist. This solves the problem by resorting to nullable ints:
var tagged = list.Select((item, i) => new { Item = item, Index = (int?)i });
int? index = (from pair in tagged
where pair.Item == search
select pair.Index).FirstOrDefault();
If you want the item:
// Throws if not found
var item = list.First(item => item == search);
// or
var item = (from item in list
where item == search
select item).First();
// Null if not found
var item = list.FirstOrDefault(item => item == search);
// or
var item = (from item in list
where item == search
select item).FirstOrDefault();
If you want to count the number of items that match:
int count = list.Count(item => item == search);
// or
int count = (from item in list
where item == search
select item).Count();
If you want all the items that match:
var items = list.Where(item => item == search);
// or
var items = from item in list
where item == search
select item;
And don't forget to check the list for null in any of these cases.
Or use (list ?? Enumerable.Empty<string>()) instead of list.

Do you want the item in the list or the actual item itself (would assume the item itself).
Here are a bunch of options for you:
string result = _list.First(s => s == search);
string result = (from s in _list
where s == search
select s).Single();
string result = _list.Find(search);
int result = _list.IndexOf(search);

If it really is a List<string> you don't need LINQ, just use:
int GetItemIndex(string search)
{
return _list == null ? -1 : _list.IndexOf(search);
}
If you are looking for the item itself, try:
string GetItem(string search)
{
return _list == null ? null : _list.FirstOrDefault(s => s.Equals(search));
}

This method is easier and safer
var lOrders = new List<string>();
bool insertOrderNew = lOrders.Find(r => r == "1234") == null ? true : false

How about IndexOf?
Searches for the specified object and returns the index of the first occurrence within the list
For example
> var boys = new List<string>{"Harry", "Ron", "Neville"};
> boys.IndexOf("Neville")
2
> boys[2] == "Neville"
True
Note that it returns -1 if the value doesn't occur in the list
> boys.IndexOf("Hermione")
-1

This will help you in getting the first or default value in your LINQ List search
var results = _List.Where(item => item == search).FirstOrDefault();
This search will find the first or default value, which it will return.

I used to use a Dictionary which is some sort of an indexed list which will give me exactly what I want when I want it.
Dictionary<string, int> margins = new Dictionary<string, int>();
margins.Add("left", 10);
margins.Add("right", 10);
margins.Add("top", 20);
margins.Add("bottom", 30);
Whenever I wish to access my margins values, for instance, I address my dictionary:
int xStartPos = margins["left"];
int xLimitPos = margins["right"];
int yStartPos = margins["top"];
int yLimitPos = margins["bottom"];
So, depending on what you're doing, a dictionary can be useful.

If we need to find an element from the list, then we can use the Find and FindAll extensions method, but there is a slight difference between them. Here is an example.
List<int> items = new List<int>() { 10, 9, 8, 4, 8, 7, 8 };
// It will return only one 8 as Find returns only the first occurrence of matched elements.
var result = items.Find(ls => ls == 8);
// this will returns three {8,8,8} as FindAll returns all the matched elements.
var result1 = items.FindAll(ls => ls == 8);

Here is one way to rewrite your method to use LINQ:
public static int GetItemIndex(string search)
{
List<string> _list = new List<string>() { "one", "two", "three" };
var result = _list.Select((Value, Index) => new { Value, Index })
.SingleOrDefault(l => l.Value == search);
return result == null ? -1 : result.Index;
}
Thus, calling it with
GetItemIndex("two") will return 1,
and
GetItemIndex("notthere") will return -1.
Reference: linqsamples.com

Try this code:
return context.EntitytableName.AsEnumerable().Find(p => p.LoginID.Equals(loginID) && p.Password.Equals(password)).Select(p => new ModelTableName{ FirstName = p.FirstName, UserID = p.UserID });

You can use FirstOfDefault with the Where LINQ extension to get a MessageAction class from the IEnumerable. Reme
var action = Message.Actions.Where(e => e.targetByName == className).FirstOrDefault<MessageAction>();
where
List<MessageAction> Actions { get; set; }

One more way to check the existence of an element in a List<string>:
var result = myList.Exists(users => users.Equals("Vijai"))

You want to search an object in object list.
This will help you in getting the first or default value in your Linq List search.
var item = list.FirstOrDefault(items => items.Reference == ent.BackToBackExternalReferenceId);
or
var item = (from items in list
where items.Reference == ent.BackToBackExternalReferenceId
select items).FirstOrDefault();

Related

How do I pick out values between a duplicate value in a collection?

I have a method that returns a collection that has a duplicate value.
static List<string> GenerateItems()
{
var _items = new List<string>();
_items.Add("Tase");
_items.Add("Ray");
_items.Add("Jay");
_items.Add("Bay");
_items.Add("Tase");
_items.Add("Man");
_items.Add("Ran");
_items.Add("Ban");
return _items;
}
I want to search through that collection and find the first place that duplicate value is located and start collecting all the values from the first appearance of the duplicate value to its next appearance. I want to put this in a collection but I only want the duplicate value to appear once in that collection.
This is what I have so far but.
static void Main(string[] args)
{
string key = "Tase";
var collection = GenerateItems();
int index = collection.FindIndex(a => a == key);
var matchFound = false;
var itemsBetweenKey = new List<string>();
foreach (var item in collection)
{
if (item == key)
{
matchFound = !matchFound;
}
if (matchFound)
{
itemsBetweenKey.Add(item);
}
}
foreach (var item in itemsBetweenKey)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
There must be an easier way of doing this. Perhaps with Indexing or a LINQ query?
You can do something like that
string key = "Tase";
var collection = GenerateItems();
int indexStart = collection.FindIndex(a => a == key);
int indexEnd = collection.FindIndex(indexStart+1, a => a == key);
var result = collection.GetRange(indexStart, indexEnd-indexStart);
You can use linq select and group by to find the first index and last index of all duplicates (Keep in mind if something is in the list more then 2 times it would ignore the middle occurences.
But I personally think the linq for this seems overcomplicated. I would stick with simple for loops and if statements (Just turn it into a method so it reads better)
Here is a solution with Linq to get all duplicate and all values between those duplicates including itself once as you mentioned.
var collection = GenerateItems();
var Duplicates = collection.Select((x,index) => new { index, value = x })
.GroupBy(x => x.value)//group by the strings
.Where(x => x.Count() > 1)//only take duplicates
.Select(x=>new {
Value = x.Key,
FirstIndex = x.Min(y=> y.index),//take first occurenc
LastIndex = x.Max(y => y.index)//take last occurence
}).ToList();
var resultlist = new List<List<string>>();
foreach (var duplicaterange in Duplicates)
resultlist .Add(collection.GetRange(duplicaterange.FirstIndex, duplicaterange.LastIndex - duplicaterange.FirstIndex));
Try this function
public List<string> PickOut(List<string> collection, string key)
{
var index = 0;
foreach (var item in collection)
{
if (item == key)
{
return collection.Skip(index).TakeWhile(x=> x != key).ToList();
}
index++;
};
return null;
}
First finding the duplicate key then find the second occurrence of the item and then take result.
var firstduplicate = collection.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key).First();
var indices = collection.Select((b, i) => b == firstduplicate ? i : -1).Where(i => i != -1).Skip(1).FirstOrDefault();
if (indices>0)
{
var result = collection.Take(indices).ToList();
}

Sort a list in C#

How to sort a List in such a way item of list matching string comes first.
Suppose if i have
"vishal pandey"
in string then item of list matching "vishal pandey" comes first then it should show result of item containing "vishal" and item containing "pandey"
It is not possible for me bring data in that order from database
Currently I am getting list like this
var matchedProjects = (from project in unitOfWork.ProjectRepository.All()
where project.IsActive
&& project is Project
&& (
queryList.Contains(project.Name)
|| project.Name.StartsWith(query)
|| project.Name.Contains(query)
|| project.Name.EndsWith(query)
|| project.ProjectAddress.City.Name.StartsWith(query)
|| project.ProjectAddress.City.Name.Contains(query)
|| project.ProjectAddress.City.Name.EndsWith(query)
|| queryList.Contains(project.ProjectAddress.City.Name)
|| queryList.Contains(project.ProjectAddress.Address1)
)
select project as Project).Distinct().AsParallel().ToList();
-Thanks
Simplest (but not very efficient) solution is just applying lots of sorts to your items:
var keywords = "vishal pandey";
var items = new[] { "pandey", "other", "vishal", "vishal pandey" };
var query = items.OrderByDescending(i => i.Contains(keywords));
foreach (var keyword in keywords.Split())
query = query.ThenByDescending(i => i.Contains(keyword));
Output:
vishal pandey
vishal
pandey
other
But if you have many keywords, or there is lot of items, custom comparer will be much better solution.
UPDATE1: If order of partial matches will not be important, you can use this simple solution suggested by Frank:
var pattern = "vishal pandey".Replace(' ', '|');
var items = new[] { "pandey", "other", "vishal", "vishal pandey" };
var query = items.OrderByDescending(i => Regex.Matches(i, pattern).Count);
UPDATE2: Custom comparer sample
public class ItemsComparer : IComparer<string>
{
private string[] keywords;
private string pattern;
public ItemsComparer(string keywords)
{
this.keywords = keywords.Split();
this.pattern = keywords.Replace(' ', '|');
}
public int Compare(string x, string y)
{
var xMatches = Regex.Matches(x, pattern).Count;
var yMatches = Regex.Matches(y, pattern).Count;
if (xMatches != yMatches)
return yMatches.CompareTo(xMatches);
if (xMatches == keywords.Length || xMatches == 0)
return 0;
foreach (var keyword in keywords)
{
var result = y.Contains(keyword).CompareTo(x.Contains(keyword));
if (result == 0)
continue;
return result;
}
return 0;
}
}
Usage:
var items = new[] { "pandey", "other", "vishal", "vishal pandey" };
var comparer = new ItemsComparer("vishal pandey");
Array.Sort(items, comparer);
What I would do is to create a custom comparer.
Then you create a class that implements this IComparer<T> interface.
In the constructor you can pass the list of expected strings.
In the compare method you can compare two instances based on how many of the expected strings the item has.

Search an Object list for duplicate keys, and place string values into a string array

What I want to be able to do is search the searchValues list for duplicate itemId's, and when I find them, place the single string value into the string values array.
The SearchValue object:
public class SearchValue<TItemId>
{
public TItemId ItemId { get; set; }
public string Value { get; set; }
public string[] Values { get; set; }
}
My test search values after init looks like this:
searchValues[0]
.ItemId == 16
.Value == "2"
searchValues[1]
.ItemId == 16
.Value == "3"
searchValues[2]
.ItemId == 15
.Value == "6"
searchValues[3]
.ItemId == 15
.Value == "3"
searchValues[4]
.ItemId == 5
.Value == "Vertonghen"
I'd like my final result to look like this:
searchValues[0]
.ItemId == 16
.Values == "2,3"
searchValues[1]
.ItemId == 15
.Values == "6,3"
searchValues[2]
.ItemId == 5
.Value == "Vertonghen"
I'd really want to do this using LINQ. I've managed to create another List of SearchValue thusly:
List<SearchValue<Byte>> duplicateSearchItems = (from x in searchValues
group x by x.ItemId into grps
orderby grps.Key
where grps.Count() > 1
select grps).SelectMany(group => group).ToList();
...but getting the value into the values array is giving me trouble. Ideally if the LINQ could return a single list that contains the duplicate records transmogrified into the array with the non-duplicates intact would be best. Probably a nested query of some sort? I'm stumped. Any ideas?
Thanks in advance!
why don't you use Dictionary
Dictionary<int,string> d = new Dictionary<int,string>();
foreach(var x in searchValues)
{
if(d.ContainsKey(x.ItemId))
d[x.ItemId] = string.Format("{0},{1}",d[x.ItemId],x.Value);
else
d.Add(x.ItemId,x.Value);
}
on the end simply iterate throug the Dictionary
foreach(var entry in d)
{
ConsoleWriteline(entry.Key+" : "+entry.Value);
}
Linq alone cannot be used to modify the original list or to modify the items in the list. However, you could do this to create a new list:
List<SearchValue<Byte>> results =
(from x in searchValues
group x by x.ItemId into g
select new SearchValue<Byte>()
{
ItemId = g.Key,
Value = g.Value.First().Value,
Values = g.Value.Select(i => i.Value).ToArray(),
}
.ToList();
Or in fluent syntax:
List<SearchValue<Byte>> results = searchValues
.GroupBy(x => x.ItemId)
.Select(g => new SearchValue<Byte>()
{
ItemId = g.Key,
Value = g.Value.First().Value,
Values = g.Value.Select(i => i.Value).ToArray(),
})
.ToList();
However, depending on your situation, an ILookup may be more appropriate for you:
var results = searchValues.ToLookup(x => x.ItemId, x => x.Value);
Console.Write(String.Join(", ", results[16])); // 2, 16
I don't think LINQ would provide you with the best solution here. Similar to Nikola, I'd use a Dictionary. If you aren't married to your SearchValue data type, you can avoid the second loop that just pushes your data back into your type. A Dictionary> would work here.
var searchValues = new List<SearchValue<int>>();
var distinctItemIds = new Dictionary<int, List<string>>();
foreach (var item in searchValues)
{
if (!distinctItemIds.ContainsKey(item.ItemId))
{
distinctItemIds.Add(item.ItemId, new List<string>());
}
// Add the value
distinctItemIds[item.ItemId].Add(item.Value);
}
// Put values back into your data object
var finishedValues = new List<SearchValue<int>>();
foreach (var keyValuePair in distinctItemIds)
{
finishedValues.Add(new SearchValue<int>()
{
ItemId = keyValuePair.Key,
Values = keyValuePair.Value.ToArray()
});
}
I managed to work it out using LINQ.
// Get a new list of unique items to add our duplicated items to
List<SearchValue<Byte>> finalSearchItems = (from x in searchValues
group x by x.ItemId into grps
orderby grps.Key
where grps.Count() == 1
select grps).SelectMany(group => group).ToList();
byte[] duplicateIds = (from x in searchValues
group x by x.ItemId into grps
where grps.Count() > 1
select grps.Key).ToArray();
// Smash the string 'Value' into 'Values[]'
foreach (byte id in duplicateIds)
{
SearchValue<Byte> val = new SearchValue<byte>();
val.ItemId = id;
// Smash
val.Values = (from s in searchValues
where s.ItemId == id
select s.Value).ToArray();
finalSearchItems.Add(val);
}

Short way to add item after item in List?

If there any short way besides foreach loop and new collection creation to add object after specific object in existing List ?
Just an example:
"amy","jerry","tony","amy","jack".
I want to add "simon" after each "amy" in short way
If you know where you want to enter an item, you can do
List.Insert(position, item)
See List.Insert() on MSDN
You can use Linq to do this.
foreach (var item in values
.Select((o, i) => new { Value = o, Index = i })
.Where(p => p.Value == "amy")
.OrderByDescending(p => p.Index))
{
if (item.Index + 1 == values.Count) values.Add("simon");
else values.Insert(item.Index + 1, "simon");
}
Uses a foreach but you can put it into a extension method to keep the code clear.
Extension method
You can easily put this into a extension method.
public static void AddAfterEach<T>(this List<T> list, Func<T, Boolean> condition, T objectToAdd)
{
foreach (var item in list.Select((o, i) => new { Value = o, Index = i }).Where(p => condition(p.Value)).OrderByDescending(p => p.Index))
{
if (item.Index + 1 == list.Count) list.Add(objectToAdd);
else list.Insert(item.Index + 1, objectToAdd);
}
}
Now the call:
List<String> list = new List<String>() { "amy","jerry","tony","amy","jack" };
list.AddAfterEach(p => p == "amy", "simon");
You have to use the Insert(int index, T item) in a List for adding the element into the List at the specified index. If index is equal to Count, item is added to the end of List. This method is an O(n) operation, where n is Count.

Use LINQ to move item to top of list

Is there a way to move an item of say id=10 as the first item in a list using LINQ?
Item A - id =5
Item B - id = 10
Item C - id =12
Item D - id =1
In this case how can I elegantly move Item C to the top of my List<T> collection?
This is the best I have right now:
var allCountries = repository.GetCountries();
var topitem = allCountries.Single(x => x.id == 592);
var finalList = new List<Country>();
finalList.Add(topitem);
finalList = finalList.Concat(allCountries.Where(x=> x.id != 592)).ToList();
What do you want to order by, other than the known top item? If you don't care, you can do this:
var query = allCountries.OrderBy(x => x.id != 592).ToList();
Basically, "false" comes before "true"...
Admittedly I don't know what this does in LINQ to SQL etc. You may need to stop it from doing the ordering in the database:
var query = allCountries.AsEnumerable()
.OrderBy(x => x.id != 592)
.ToList();
LINQ is strong in querying collections, creating projections over existing queries or generating new queries based on existing collections. It is not meant as a tool to re-order existing collections inline. For that type of operation it's best to use the type at hande.
Assuming you have a type with a similar definition as below
class Item {
public int Id { get; set; }
..
}
Then try the following
List<Item> list = GetTheList();
var index = list.FindIndex(x => x.Id == 12);
var item = list[index];
list[index] = list[0];
list[0] = item;
Linq generallyworks on Enumerables, so it doesn't now that the underlying type is a collection. So for moving the item on top of the list I would suggest using something like (if you need to preserve the order)
var idx = myList.FindIndex(x => x.id == 592);
var item = myList[idx];
myList.RemoveAt(idx);
myList.Insert(0, item);
If your function returns only an IEnumerable, you can use the ToList() method to convert it to a List first
If you don't preserve the order you can simply swap the values at position 0 and position idx
var allCountries = repository.GetCountries();
allCountries.OrderByDescending(o => o.id == 12).ThenBy(o => o.id)
This will insert the object with id=12 at the top of the list and rotate the rest down, preserving the order.
Here is an extension method you might want to use. It moves the element(s) that match the given predicate to the top, preserving order.
public static IEnumerable<T> MoveToTop(IEnumerable<T> list, Func<T, bool> func) {
return list.Where(func)
.Concat(list.Where(item => !func(item)));
}
In terms of complexity, I think it would make two passes on the collection, making it O(n), like the Insert/Remove version, but better than Jon Skeet's OrderBy suggestion.
You can "group by" in two groups with Boolean key, and then sort them
var finalList= allCountries
.GroupBy(x => x.id != 592)
.OrderBy(g => g.Key)
.SelectMany(g => g.OrderBy(x=> x.id ));
I know this a old question but I did it like this
class Program
{
static void Main(string[] args)
{
var numbers = new int[] { 5, 10, 12, 1 };
var ordered = numbers.OrderBy(num => num != 10 ? num : -1);
foreach (var num in ordered)
{
Console.WriteLine("number is {0}", num);
}
Console.ReadLine();
}
}
this prints:
number is 10
number is 1
number is 5
number is 12
public static IEnumerable<T> ServeFirst<T>(this IEnumerable<T> source,
Predicate<T> p)
{
var list = new List<T>();
foreach (var s in source)
{
if (p(s))
yield return s;
else
list.Add(s);
}
foreach (var s in list)
yield return s;
}
Its interesting the number of approaches you find when trying to solve a problem.
var service = AutogateProcessorService.GetInstance();
var allConfigs = service.GetAll();
allConfigs = allConfigs.OrderBy(c => c.ThreadDescription).ToList();
var systemQueue = allConfigs.First(c => c.AcquirerId == 0);
allConfigs.Remove(systemQueue);
allConfigs.Insert(0, systemQueue);
To also check if the item was found without Exception, something like:
var allCountries = repository.GetCountries();
var lookup = allCountries.ToLookup(x => x.id == 592);
var finalList = lookup[true].Concat(lookup[false]).ToList();
if ( lookup[true].Count() != 1 ) YouAreInTrouble();
Even easier if you have the object:
listOfObjects.Remove(object);
listOfObjects.Insert(0, object);
I wrote a static extension method to do this. Note this doesn't preserve the order, it simply swaps the item out. If you needed to preserve the order you should do a rotate not a simple swap.
/// <summary>
/// Moves the item to the front of the list if it exists, if it does not it returns false
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="collection"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static bool MoveToFrontOfListWhere<T>(this List<T> collection, Func<T, bool> predicate)
{
if (collection == null || collection.Count <= 0) return false;
int index = -1;
for (int i = 0; i < collection.Count; i++)
{
T element = collection.ElementAt(i);
if (!predicate(element)) continue;
index = i;
break;
}
if (index == -1) return false;
T item = collection[index];
collection[index] = collection[0];
collection[0] = item;
return true;
}

Categories

Resources