Search list of objects based on object variable - c#

I have a list of objects. These objects have three variables, ID, Name, & value. There can be a lot of objects in this list, and I need to find one based on the ID or Name, and change the value.
Example
class objec
{
public string Name;
public int UID;
public string value;
}
List<objec> TextPool = new List<objec>();
How would I find the one entry in TextPool that had the Name of 'test' and change its value to 'Value'.
The real program has many more search options, and values that need changing, so I couldn't just use a Dictionary (though Name and UID or unique identifiers).
Any help would be great

You could use LINQ to find it, then change the element directly:
var item = TextPool.FirstOrDefault(o => o.Name == "test");
if (item != null)
item.value = "Value";
If you wanted to change all elements that match, you could, potentially, even do:
TextPool.Where(o => o.Name == "test").ToList().ForEach(o => o.value = "Value");
However, I personally would rather split it up, as I feel the second option is less maintainable (doing operations which cause side effects directly on the query result "smells" to me)...

var find = TextPool.FirstOrDefault(x => x.Name == "test");
if (find != null)
{
find.Name = "Value";
}

Sounds like a job for LINQ!
var matchedObject =
from t in TextPool
where t.UName == "test"
select t;
This is assuming your search is defined in code. If your code is driven by the UI, you may simply need to do a linear iteration. To search all possible attributes, without indexing, it isn't going to get any faster.
[ Edit: Was beaten to the punch, but leaving this up as an example of a different syntax, plus a link ]

List<objec> TextPool = new List<objec>();
objec found = TextPool.FirstOrDefault(item => item.Name == "test");
if (found != null) found.value = "Value";
If you are going to perform many lookups, you could cache the results in multiple Dictionary<> instances (or Lookup<> instance if keys are not unique).

Related

Querying a chain of list of lists with LINQ

I am working with an XML standard called SDMX. It's fairly complicated but I'll make it as short as possible. I am receiving an object called CategoryScheme. This object can contain a number of Category, and each Category can contain more Category, and so on, the chain can be infinite. Every Category has an unique ID.
Usually each Category contains a lot of Categories. Together with this object I am receiving an Array, that contains the list of IDs that indicates where a specific Category is nested, and then I am receiving the ID of that category.
What I need to do is to create an object that maintains the hierarchy of the Category objects, but each Category must have only one child and that child has to be the one of the tree that leads to the specific Category.
So I had an idea, but in order to do this I should generate LINQ queries inside a cycle, and I have no clue how to do this. More information of what I wanted to try is commented inside the code
Let's go to the code:
public void RemoveCategory(ArtefactIdentity ArtIdentity, string CategoryID, string CategoryTree)
{
try
{
WSModel wsModel = new WSModel();
// Prepare Art Identity and Array
ArtIdentity.Version = ArtIdentity.Version.Replace("_", ".");
var CatTree = JArray.Parse(CategoryTree).Reverse();
// Get Category Scheme
ISdmxObjects SdmxObj = wsModel.GetCategoryScheme(ArtIdentity, false, false);
ICategorySchemeMutableObject CatSchemeObj = SdmxObj.CategorySchemes.FirstOrDefault().MutableInstance;
foreach (var Cat in CatTree)
{
// The cycle should work like this.
// At every iteration it must delete all the elements except the correct one
// and on the next iteration it must delete all the elements of the previously selected element
// At the end, I need to have the CatSchemeObj full of the all chains of categories.
// Iteration 1...
//CatSchemeObj.Items.ToList().RemoveAll(x => x.Id != Cat.ToString());
// Iteration 2...
//CatSchemeObj.Items.ToList().SingleOrDefault().Items.ToList().RemoveAll(x => x.Id != Cat.ToString());
// Iteration 3...
//CatSchemeObj.Items.ToList().SingleOrDefault().Items.ToList().SingleOrDefault().Items.ToList().RemoveAll(x => x.Id != Cat.ToString());
// Etc...
}
}
catch (Exception ex)
{
throw ex;
}
}
Thank you for your help.
So, as i already said in my comment, building a recursive function should fix the issue. If you're new to it, you can find some basic information about recursion in C# here.
The method could look something like this:
private void DeleteRecursively(int currentRecursionLevel, string[] catTree, ICategorySchemeMutableObject catSchemeObj)
{
catSchemeObj.Items.ToList().RemoveAll(x => x.Id != catTree[currentRecursionLevel].ToString());
var leftoverObject = catSchemeObj.Items.ToList().SingleOrDefault();
if(leftoverObject != null) DeleteRecursively(++currentRecursionLevel, catTree, leftoverObject);
}
Afterwards you can call this method in your main method, instead of the loop:
DeleteRecursively(0, CatTree, CatSchemeObject);
But as i also said, keep in mind, that calling the method in the loop, seems senseless to me, because you already cleared the tree, besides the one leftover path, so calling the method with the same tree, but another category, will result in an empty tree (in CatSchemeObject).
CAUTION! Another thing to mention i noticed right now: Calling to list on your Items property and afterwards deleting entries, will NOT affect your source object, as ToList is generating a new object. It IS keeping the referenced original objects, but a deletion only affects the list. So you must write back the resulting list to your Items property, or find a way to directly delete in the Items object. (Assuming it's an IEnumerable and not a concrete collection type you should write it back).
Just try it out with this simple example, and you will see that the original list is not modified.
IEnumerable<int> test = new List<int>() { 1, 2, 3, 4 , 1 };
test.ToList().RemoveAll(a => a != 1);
Edited:
So here is another possible way of going after the discussion below.
Not sure what do you really need so just try it out.
int counter = 0;
var list = CatSchemeObj.Items.ToList();
//check before you call it or you will get an error
if(!list.Equals(default(list)))
{
while(true)
{
var temp = list.Where(x => CatTree[counter++] == x.Id); // or != ? play with it .
list = temp.Items.ToList().SingleOrDefault();
if(list.Equals(default(list))
{
break;
}
}
}
I just translated you problem to 2 solutions, but I am not sure if you won't lose data because of the SingleOrDefault call. It means 'Grab the first item regardless of everything'. I know you said you have only 1 Item that is ok, but still... :)
Let me know in comment if this worked for you or not.
//solution 1
// inside of this loop check each child list if empty or not
foreach (var Cat in CatTree)
{
var list = CatSchemeObj.Items.ToList();
//check before you call it or you will get an error
if(!list.Equals(default(list)))
{
while(true)
{
list.RemoveAll(x => x.Id != Cat.ToString());
list = list.ToList().SingleOrDefault();
if(list.Equals(default(list))
{
break;
}
}
}
}
//solution 2
foreach (var Cat in CatTree)
{
var list = CatSchemeObj.Items.ToList();
//check before you call it or you will get an error
if(!list.Equals(default(list)))
{
CleanTheCat(cat, list);
}
}
//use this recursive function outside of loop because it will cat itself
void CleanTheCat(string cat, List<typeof(ICategorySchemeMutableObject.Items) /*Place here whatever type you have*/> CatSchemeObj)
{
CatSchemeObj.RemoveAll(x => x.Id != cat);
var catObj = CatSchemeObj.Items.ToList().SingleOrDefault();
if (!catObj.Equals(default(catObj)){
CleanTheCat(cat, catObj);
}
}
Thank you to whoever tried to help but I solved it by myself in a much easier way.
I just sent the full CategoryScheme object to the method that converted it in the XML format, then just one line did the trick:
XmlDocument.Descendants("Category").Where(x => !CatList.Contains(x.Attribute("id").Value)).RemoveIfExists();

return the value of the list by elements's name not index in C# [duplicate]

I have a getvalue object that contains a price list which consists of 5 items. I need to get the value of one of the elements. I can get the value by index:
return (getValue1.ValuationPrices[4].Value.ToString());
Instead of using 4 (the index) I would like to use the name of the field. Can I do that?
More detail:
I want to say if PriceType is "Wholesale" return the value that is 18289
that is the answer to this question:
foreach (var item in getValue1.ValuationPrices)
{
if (item.PriceType == ServiceReference1.PriceType.Wholesale)
{
carValue= item.Value.ToString();
}
}
You can either change your array to Dictionary<string, yourType> or use LINQ to perform linear search for your object by name:
return getValue1.ValuationPrices.First(x => x.Name == "myName").Value.ToString();
You could do this by adding an indexer property to the ValuationPrices type.
public ValuationPrice this[string name]
{
get
{
return this.First(n => n.Name == value);
}
}
Then you would be able to write getvalue1.ValuationPrices["fieldName"].
The implementation of the indexer property will vary depending on the internal structure of your classes, but hopefully this gives you some idea of the syntax used to implement the indexer.
The screenshot helped quite a bit... people can't guess what your classes look like internally. Your comment to Marcin indicates that PriceType might be an enumeration. So assuming:
PriceType is actually an enum, not a string
The PriceType you're searching for is guaranteed to be in that collection at once and one time only
This should work:
return getValue1.ValuationProces.Single(x => x.PriceType == PriceType.WholeSale).Value.ToString();
This is basically the same as Marcin's - if I'm right about PriceType being an enum and this works, then you should just accept his answer and move on.

Simple lookup function

I've got a simple struct which I want to use as a lookup table:
public struct TileTypeSize
{
public string type;
public Size size;
public TileTypeSize(string typeIn, Size sizeIn)
{
type = typeIn;
size = sizeIn;
}
}
I populate this thusly:
tileTypeSizeList.Add(new TileTypeSize("W",rectangleSizeWall));
tileTypeSizeList.Add(new TileTypeSize("p",rectangleSizePill));
tileTypeSizeList.Add(new TileTypeSize("P",rectangleSizePowerPill));
tileTypeSizeList.Add(new TileTypeSize("_",rectangleSizeWall));
tileTypeSizeList.Add(new TileTypeSize("=",rectangleSizeWall));
What is the most efficient way to look up the size for a given type?
Thanks in advance!
In general, the most efficient way would be to put your data into a Dictionary or similar container instead (SortedDictionary and SortedList have small differences from Dictionary and are an even better fit in certain cases):
var dict = new Dictionary<string, Size>
{
{ "W", rectangleSizeWall },
// etc
}
And then:
var size = dict["W"];
You can of course still iterate sequentially over the values in the dictionary if there is reason to do so.
If 5 types is all you are going to be looking up (i.e. the size of the problem is ridiculously small) then a straight list like you have would likely be faster than an associative container. So:
var tileStruct = tileTypeSizeList.FirstOrDefault(s => s.type == "W");
if (tileStruct.type == "") {
// not found
}
else {
var size = tileStruct.size;
}
You may remove the "if found" check if you are sure that you will never have a search miss.
If you know there will be one and only one match in the collection, then you can use:
var size = tileTypeSizeList.Single(t => t.type == someType).size;
If not, you'll have to be a little more clever to properly handle the cases where no match is found:
Size size;
var match =
tileTypeSizeList
.Cast<TileTypeSize?>().FirstOrDefault(t => t.type == someType);
if(match != null) size = match.size;
Keep in mind, though, that there are better ways to store this information if that is the only data in the struct. I would suggest a Dictionary<string, Size>.
var type = tileTypeSizeList.FirstOrDefault(t => t.type == someType);
if(type==null) throw new NotFoundException();
return type.size;
But if the list is big and you need to lookup data really often you better use Dictionary as noticed in other answers.
Use a Dictionary instead of a List:
Dictionary<string, TileTypeSize> tileTypeSizeDictionary = Dictionary<string, TileTypeSize>();
tileTypeSizeDictionary.Add("W", new TileTypeSize("W",rectangleSizeWall));
...
You lookup your elements with:
TileTypeSize rectangleSizeWall = tileTypeSizeDictionary["W"];
A dictionary is faster than a list when you need to lookup by key.

How to make sure a user only gets added to a list once

I have to list a list of users through a custom search, where I take all the users from all the groups added to the permissions list on the sharepoint web. My issue is that users can be in several groups, thus they get added multiple times to the list that gets returned. How do I make sure they only gets added once?
C#
// keywords is the whatever value a user types into the search textbox
private static IEnumerable<SPUser> GetUsers(SPWeb web, string keywords)
{
var oList = new List<SPUser>();
var oListUsers = web.Groups.Cast<SPGroup>().SelectMany(grp => grp.Users.Cast<SPUser>().Where(user => user.Name.Contains(keywords))).ToList();
foreach (SPUser user in oListUsers)
{
// My attempt here is to check if the list already contains the current item
// but it seems to ignore it. I've tried counting too, but same outcome.
if (!oList.Contains(user))
oList.Add(user);
}
return oList;
}
Try this (instead of contains)
if (! oList.Any(u => u.Name == user.Name ))
{
oList.Add(user);
}
Looks like your SPUser class needs to implement IEquatable<SPUser> for contains to work as you want it.
You can use Linq to get unique records
var uniqueValues = oList.Distinct();
This will remove SPUser object with same reference. Also you can implement IEqualityCompaprer<SPUser> for your own equlity logic
Use a Hashset instead of a List. That way, you don't have to check for containment, and duplicate items will just be ignored.
This will be faster too, as the HashSet is able to reject duplicates almost trivially, while the List<>.Contains() is O(n)
var oList = new HashSet<SPUser>();
var oListUsers = web.Groups.Cast<SPGroup>().SelectMany(grp => grp.Users.Cast<SPUser> ().Where(user => user.Name.Contains(keywords))).ToList();
foreach (SPUser user in oListUsers)
{
oList.Add(user);
}
By brute force, you could just use Distinct:
var oListUsers = web.Groups.Cast<SPGroup>().SelectMany(grp => grp.Users.Cast<SPUser>().Where(user => user.Name.Contains(keywords))).Distinct().ToList();
The problem is that you have different objects in your oListUsers list that represent the same user, but have different object references - since Contains() uses object references to check, you won't be able to catch this case unless you define a custom comparer / Equality on your SPUser class.
Alternatively if i.e. the user name is unique you could filter out duplicates that way.

How to access a particular data in LINQ query result?

I know, this is very simple for you guys.
Please consider the following code:
string[] str = { "dataReader", "dataTable", "gridView", "textBox", "bool" };
var s = from n in str
where n.StartsWith("data")
select n;
foreach (var x in s)
{
Console.WriteLine(x.ToString());
}
Console.ReadLine();
Supposedly, it will print:
dataReader
dataTable
right?
What if for example I don't know the data, and what the results of the query will be (but I'm sure it will return some results) and I just want to print the second item that will be produced by the query, what should my code be instead of using foreach?
Is there something like array-indexing here?
You're looking forEnumerable.ElementAt.
var secondMatch = str.Where(item => item.StartsWith("data")) //consider null-test
.ElementAt(1);
Console.WriteLine(secondMatch); //ToString() is redundant
SinceWherestreams its results, this will be efficient - enumeration of the source sequence will be discontinued after the second match (the one you're interested in) has been found.
If you find that the implicit guarantee you have that the source will contain two matches is not valid, you can use ElementAtOrDefault.
var secondMatch = str.Where(item => item.StartsWith("data"))
.ElementAtOrDefault(1);
if(secondMatch == null) // because default(string) == null
{
// There are no matches or just a single match..
}
else
{
// Second match found..
}
You could use array-indexing here as you say, but only after you load the results into... an array. This will of course mean that the entire source sequence has to be enumerated and the matches loaded into the array, so it's a bit of a waste if you are only interested in the second match.
var secondMatch = str.Where(item => item.StartsWith("data"))
.ToArray()[1]; //ElementAt will will work too
you got a few options:
s.Skip(1).First();
s.ElementAt(1);
The first is more suited for scenarios where you want X elements but after the y first elements. The second is more clear when you just need a single element on a specific location

Categories

Resources