I have a class in my Windows application like so:
public class Pets
{
String Name {get;set;}
int Amount {get;set;}
}
In one of my other classes i made a List of that class like so.
List<Pets> myPets = new List<Pets>();
myPets.Add(new Pets{ Name = "Fish", Amount = 8});
myPets.Add(new Pets{ Name = "Dogs", Amount = 2});
myPets.Add(new Pets{ Name = "Cats", Amount = 2});
Is there a way i can get the Index of the Pets whos Name = "Fish"?
I realize i can do this
int pos = 0;
for(int x = 0; x<myPets.Count;x++)
{
if( myPets[x].Name == "Fish")
{
pos = x;
}
}
But in the case that i have ALOT of items in myPets it would take long to loop through them to find the one i am looking for. Is there another way to complete the task above. That would make my application run quicker? In the case that myPets has a lot of items in it.
The way you have your data structured at the moment does not lend itself well to searching by pet's name if the list is large.
So iterating manually like you suggest and what FindIndex is doing is known as a linear search which is a brute force algorithm. If you have N items in your collection, the worst case scenario to find an item is N iterations. This is known as O(N) using the Big O Notation. The speed of search grows linearly with the number of items within your collection.
For faster searching you need to change to a different data structure (like a Hashtable), use a database or implement a different search algorithm, such as a binary search( O(log(n)) complexity ).
Have a look at this question for an example:
Can LINQ use binary search when the collection is ordered?
If you want to find index only to access item of List you can use Dictionary instead.
var pets = new Dictionary<string, int>();
pets.Add("Dogs", 2);
pets.Add("Fish", 8);
int amount = pets["Fish"];
Related
What is the fastest method for searching data from list array in C#?
My code:
public class fruits
{
public string Initial;
public string Fruit;
public fruits(string initials, string names)
{
Initial = initials;
Fruit = names;
}
}
// load
List<fruits> List = new List<fruits>();
List.Add(new fruits("A", "Apple"));
List.Add(new fruits("P", "Pineapple"));
List.Add(new fruits("AP", "Apple Pineapple"));
//combo box select text
var text = combobox.SelectText();
for (int i=0; i<list.Count(); i++)
{
if (list[i].Fruit == text)
{
MessageBox.Show(list[i].Initial);
}
}
I know this search method is not good, if list data contains too much data.
If you want a "fast" solution, you should use a foreach instead of LINQ. This solution can improve your perfomance a lot:
fruits firstOrDefault = null:
foreach (fruits f in List)
{
if (f.Fruit == text)
{
FirstOrDefault = f;
break;
}
}
You can get few more information about the LINQ performance in posts like
Is a LINQ statement faster than a 'foreach' loop?
http://geekswithblogs.net/BlackRabbitCoder/archive/2010/04/23/c-linq-vs-foreach---round-1.aspx
https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/
You can use linq
var result = List.FirstOrDefault(q => q.Fruit == text );
MessageBox.Show(result.Initial);
The best (and only) way to tell what method is fastest for a certain situation is to actually benchmark/measure it with different algorithms. You already have two answers/approaches here (LINQ and foreach). Time both of them and then pick the faster one.
Or in other words: Measuring your code gives you an advantage over those people who think they are too smart to measure. ;)
To speed things up further you might want to consider to keep the list sorted and then do a binary search on the list. It increases the time for insertion, because you have to sort the list after inserts, but it should speed up the search process. But then again: Do not just take my word for it, measure it!
class Employee
{
int id;
Position position;
}
class Position
{
string name;
}
public List<List<Tuple<Position, Employee>>> getAllCombinations(Dictionary<Position, int> positionToCountMap, List<Employee> allEmployees)
{
}
positionToCountMap to would be something like this: {"Manager", 1}, {"Developer", "3"}, {"PM", "1"} (it's unknown how many keys there are)
I need to return a list of all possible combinations of allEmployees such that it satisfies the count requirements in the positionToCountMap. I need al combinations of 1 manager, 3 developers and 1 PM. My first step was to create a new dictionary of position to a list of employees with that position.
var positionToEmployeeMap = new Dictionary<Position, List<Employee>>()
//Loop through allEmployees adding each to this dictionary
Now the problem becomes that I have several lists, and I need to find all possible combinations taking the amount specified in positionToCountMap from each list.
Is this a good way to approach the problem? Even if it is, I can't wrap my head around how I would actually brute force this. I was originally trying to think of some recursive solution, but the size of the list may be large enough that recursion might not be a great choice. I'm stuck and could use some advice.
EDIT
I think I have a solution, though it's not great and i'd still love to get some advice.
var positionToEmployeeMap = new Dictionary<Position, List<Employee>>()
//Loop through allEmployees adding each to this dictionary
var relevantLists = new List<Employee>();
//for each key in positionToCountMap, find the list in positionToEmployeeMap and add it to relevantLists
var allCombos = new List<List<Employee>>();
//Loop through relevantLists. For each list, recursively generate all possible combinations of sublists of size N, where N is the number in positionToCountMap. Add a list of all the combinations to allCombos
//recursively loop through allCombos finding all possible combinations taking 1 element from each list
I'd use LINQ. You can do something similar to a SQL CROSS JOIN operation with the following:
var ListA = new List<object>();
var ListB = new List<object>();
var ListC = new List<object>();
var result = (from a in listA
from b in listB
from c in listC
select new { a, b, c }).ToList();
This will result in a list including all combinations of the values in ListA, ListB, and ListC.
I have the following data set and would like to store the three arrays in one variable for lookup.
name date size
aaa 201201 0.82
bbb 201306 1.04
ccc 201209 0.91
................
How do I store all the information in one variable? There are hundreds of rows. I am working with C#. I need to be able to search through the variable. For example, if time = 201201, name = aaa, then the size is 0.82.
Best way? Create a wrapper class, store in a List, query using Linq to objects:
public class YourStuff{
public string Name;
public DateTime Date;
public double Size;
}
...
List<Stuff> myStuff = new List<Stuff>();
//then load from DataSet, DataTable, etc.
Some Linq examples:
var greaterThanOne = myStuff.Where(stuff => stuff.Size > 1);
var greaterThanOneCount = myStuff.Count(stuff => stuff.Size > 1);
var sumOfAllSizes = myStuff.Sum(stuff => stuff.Size);
With Linq to objects you can find, sort, slice and dice, group by, you name it.
EDIT: I modified my original answer to be able to search by name and date and get size.
You could use the Tuple class like this:
Dictionary<Tuple<string, DateTime>, float> dict = new Dictionary<Tuple<string, DateTime>, float>();
dict[new Tuple<string, DateTime>("aaa", DateTime.Now)] = 0.82f;
This approach assumes that the combination of name and date is unique, like an index. You can easily search by index, then.
If you need to search name/date by size, though, the wrapper class approach Adrian Carneiro suggested is probably better.
I'm trying to achieve a super-fast search, and decided to rely heavily on caching to achieve this. The order of events is as follows;
1) Cache what can be cached (from entire database, around 3000 items)
2) When a search is performed, pull the entire result set out of the cache
3) Filter that result set based on the search criteria. Give each search result a "relevance" score.
4) Send the filtered results down to the database via xml to get the bits that can't be cached (e.g. prices)
5) Display the final results
This is all working and going at lightning speed, but in order to achieve (3) I've given each result a "relevance" score. This is just a member integer on each search result object. I iterate through the entire result set and update this score accordingly, then order-by it at the end.
The problem I am having is that the "relevance" member is retaining this value from search to search. I assume this is because what I am updating is a reference to the search results in the cache, rather than a new object, so updating it also updates the cached version. What I'm looking for is a tidy solution to get around this. What I've come up with so far is either;
a) Clone the cache when i get it.
b) Create a seperate dictionary to store relevances in and match them up at the end
Am I missing a really obvious and clean solution or should i go down one of these routes? I'm using C# and .net.
Hopefully it should be obvious from the description what I'm getting at, here's some code anyway; this first one is the iteration through the cached results in order to do the filtering;
private List<QuickSearchResult> performFiltering(string keywords, string regions, List<QuickSearchResult> cachedSearchResults)
{
List<QuickSearchResult> filteredItems = new List<QuickSearchResult>();
string upperedKeywords = keywords.ToUpper();
string[] keywordsArray = upperedKeywords.Split(' ');
string[] regionsArray = regions.Split(',');
foreach (var item in cachedSearchResults)
{
//Check for keywords
if (keywordsArray != null)
{
if (!item.ContainsKeyword(upperedKeywords, keywordsArray))
continue;
}
//Check for regions
if (regionsArray != null)
{
if (!item.IsInRegion(regionsArray))
continue;
}
filteredItems.Add(item);
}
return filteredItems.OrderBy(t=> t.Relevance).Take(_maxSearchResults).ToList<QuickSearchResult>();
}
and here is an example of the "IsInRegion" method of the QuickSearchResult object;
public bool IsInRegion(string[] regions)
{
int relevanceScore = 0;
foreach (var region in regions)
{
int parsedRegion = 0;
if (int.TryParse(region, out parsedRegion))
{
foreach (var thisItemsRegion in this.Regions)
{
if (thisItemsRegion.ID == parsedRegion)
relevanceScore += 10;
}
}
}
Relevance += relevanceScore;
return relevanceScore > 0;
}
And basically if i search for "london" i get a score of "10" the first time, "20" the second time...
If you use the NetDataContractSerializer to serialize your objects in the cache, you could use a [DataMember] attribute to control what gets serialized and what doesn't. For instance, you could store your temporarary calculated relevance value in a field that is not serialized.
I've produced a function to get back a random set of submissions depending on the amount passed to it, but I worry that even though it works now with a small amount of data when the large amount is passed through, it would become efficent and cause problems.
Is there a more efficent way of doing the following?
public List<Submission> GetRandomWinners(int id)
{
List<Submission> submissions = new List<Submission>();
int amount = (DbContext().Competitions
.Where(s => s.CompetitionId == id).FirstOrDefault()).NumberWinners;
for (int i = 1 ; i <= amount; i++)
{
bool added = false;
while (!added)
{
bool found = false;
var randSubmissions = DbContext().Submissions
.Where(s => s.CompetitionId == id && s.CorrectAnswer).ToList();
int count = randSubmissions.Count();
int index = new Random().Next(count);
foreach (var sub in submissions)
{
if (sub == randSubmissions.Skip(index).FirstOrDefault())
found = true;
}
if (!found)
{
submissions.Add(randSubmissions.Skip(index).FirstOrDefault());
added = true;
}
}
}
return submissions;
}
As I say, I have this fully working and bringing back the wanted result. It is just that I'm not liking the foreach and while checks in there and my head has just turned to mush now trying to come up with the above solution.
(Please read all the way through, as there are different aspects of efficiency to consider.)
There are definitely simpler ways of doing this - and in particular, you really don't need to perform the query for correct answers repeatedly. Why are you fetching randSubmissions inside the loop? You should also look at ElementAt to avoid the Skip and FirstOrDefault - and bear in mind that as randSubmissions is a list, you can use normal list operations, like the Count property and the indexer!
The option which comes to mind first is to perform a partial shuffle. There are loads of examples on Stack Overflow of a modified Fisher-Yates shuffle. You can modify that code very easily to avoid shuffling the whole list - just shuffle it until you've got as many random elements as you need. In fact, these days I'd probably implement that shuffle slightly differently to you could just call:
return correctSubmissions.Shuffle(random).Take(amount).ToList();
For example:
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
T[] elements = source.ToArray();
for (int i = 0; i < elements.Length; i++)
{
// Find an item we haven't returned yet
int swapIndex = i + rng.Next(elements.Length - i);
T tmp = elements[i];
yield return elements[swapIndex];
elements[swapIndex] = tmp;
// Note that we don't need to copy the value into elements[i],
// as we'll never use that value again.
}
}
Given the above method, your GetRandomWinners method would look like this:
public List<Submission> GetRandomWinners(int competitionId, Random rng)
{
List<Submission> submissions = new List<Submission>();
int winnerCount = DbContext().Competitions
.Single(s => s.CompetitionId == competitionId)
.NumberWinners;
var correctEntries = DbContext().Submissions
.Where(s => s.CompetitionId == id &&
s.CorrectAnswer)
.ToList();
return correctEntries.Shuffle(rng).Take(winnerCount).ToList();
}
I would advise against creating a new instance of Random in your method. I have an article on preferred ways of using Random which you may find useful.
One alternative you may want to consider is working out the count of the correct entries without fetching them all, then work out winning entries by computing a random selection of "row IDs" and then using ElementAt repeatedly (with a consistent order). Alternatively, instead of pulling the complete submissions, pull just their IDs. Shuffle the IDs to pick n random ones (which you put into a List<T>, then use something like:
return DbContext().Submissions
.Where(s => winningIds.Contains(s.Id))
.ToList();
I believe this will use an "IN" clause in the SQL, although there are limits as to how many entries can be retrieved like this.
That way even if you have 100,000 correct entries and 3 winners, you'll only fetch 100,000 IDs, but 3 complete records. Hope that makes sense!