This question already has answers here:
LINQ: Add RowNumber Column
(9 answers)
Closed 6 years ago.
I would like to access the row number in linq query.
There are many articles on the web stating how to do this but there is a catch:
I want to resolve the enumeration "later" and I want it to assign the same ID every time.
For this reason methods such as this do not work:
public IEnumerable<MyClass> GetThings(List<object> lst)
{
int ID=0;
return from i in lst
select new MyClass(ID++, i);
}
public class MyClass
{
public MyClass(int ID, object Stuff)
{ ... }
}
...
var x = GetThings(SomeList);
(fails because each time you resolve x by iterating each item gets a different id)
Actually you can use the Select overload that pass the index too, something like this:
lst.Select((o, i) => new MyClass(i, o));
Turns out the solution to this is quite simple - grab the row number of the source collection, not the output collection.
Obviously this only works if you are not filtering etc. and the number of items in your output collection is the same as the input collection. Unless you're ok with gaps and/or duplicates
i.e.
public IEnumerable<MyClass> GetThings(List<object> lst)
{
int ID=0;
return from i in lst.Select((item,id)=>new {Item=item, ID=id})
select new MyClass(i.ID, i.Item);
}
public class MyClass
{
public MyClass(int ID, object Stuff)
{ ... }
}
...
var x = GetThings(SomeList);
now the ID's of each item in x is the same every time you iterate it
Related
This question already has answers here:
Item from IEnumerable changed inside foreach, but change not saved to collection
(1 answer)
C# failing to set property inside IEnumerable
(5 answers)
Closed 3 years ago.
I have this code:
class Foo
{
public string A { get; set; }
}
class Program
{
static void Main(string[] args)
{
var strings = new List<string> { "foo", "foo" };
var list = strings.Select(x => new Foo { A = x });
foreach (var item in list)
{
item.A = "bar";
}
foreach (var item in list)
{
Console.WriteLine(item.A);
}
}
}
Which prints:
foo
foo
What exactly happens when you set item.A = "bar" ?
After the first foreach loop finishes, does the list var really contain the same two Foo objects with "bar" as the new string?
If so, how could you access these new values?
I understand that when the 2nd foreach loop runs, it is enumerating the collection of strings which is why you get two print outs of "foo", but I'm just confused as to what happens when item.A = "bar" is run, and if you are never able to access that new value, why does the compiler allow you to modify it?
What's happening here is that you are creating an enumerable list which you are enumerating multiple times.
Each time you enumerate list, the enumeration processes the elements of the strings list calling new Foo { A = x } for each element to create the elements of the resulting sequence.
That means the the Foo objects created by the first foreach enumeration are NOT the same as the ones created by the second foreach enumeration. New ones are created for each enumeration.
This is the reason that Resharper warns about "possible multiple enumeration".
To avoid this, you would use var list = strings.Select(x => new Foo { A = x }).ToList(); to enumerate the sequence just once and store the results in an actual List<T>.
The problem is that you haven't called ToList method to materialize your LINQ query. When you call ToList as below:
var list = strings.Select(x => new Foo { A = x })
.ToList();
an in-memory collection of Foo objects would be created, whose property value A would have the value x. Essentially two new objects of type Foo would be created with the value of A to be "foo". Then you can loop through this list and modify the property value.
Please look at this fiddle
You are right that if will not going to be chnaged, then why compiler allow. but if you want to print without updating actual item in this scenario above code will helpful.
One thing you should know that, you can not modified the item of IEnumerable object.
you have to use List();
var strings = new List<string> { "foo", "foo" };
var list = strings.Select(x => new Foo { A = x }).ToList();
foreach (var item in list)
{
item.A = "bar";
}
foreach (var item in list)
{
Console.WriteLine(item.A);
}
I have a complex type as:
class Row : IEquatable<Row>
{
public Type Type1 { get; }
public Type Type2 { get; }
public int dummy;
public override int GetHashCode()
{
var type1HashCode = Type1.GetHashCode();
//djb2 hash
unchecked
{
return ((type1HashCode << 5) + type1HashCode) ^ Type2.GetHashCode();
}
}
// Equals method also overrided
}
I have a HashSet<Row> and I want to merge it with another HashSet with two different strategies; first I want to merge and keep duplicates from main HashSet, I tried main.UnionWith(second) now I want to merge main with second (result being in main) and keep duplicates from second one; How can I do that? (it's a performance critical code)
My code:
var main = new HashSet<Row>()
{
new Row(typeof(int), typeof(long))
{
dummy = 10
}
};
var second = new HashSet<Row>()
{
new Row(typeof(int), typeof(long))
{
dummy = 20
}
};
// Merge here.
Trace.Write(main.First().dummy) //I want 20
I expect main.First().dummy to be 20.
The second strategy can be implemented by calling main.ExceptWith(second); first and then main.UnionWith(second) like the first strategy.
Since the UnionWith is basically a shortcut for
foreach (var element in second)
main.Add(element);
and ExceptWith - a shortcut for
foreach (var element in second)
main.Remove(element);
the second strategy can also be implemented with a single loop:
foreach (var element in second)
{
main.Remove(element);
main.Add(element);
}
But I think the performance gain would be negligible compared to ExceptWith + UnionWith approach.
If I'm reading this correctly, you want to keep duplicated values after merging. In this scenario, HashSet is the wrong data structure for your objective.
From the MSDN documentation for HashSet(T):
A HashSet collection is not sorted and cannot contain duplicate elements. If order or element duplication is more important than performance for your application, consider using the List class together with the Sort method.
This question already has answers here:
C# List of objects, how do I get the sum of a property
(4 answers)
Closed 8 years ago.
How can the following Generic List be summed up by total cost? Meaning is it possible to add both costs below to get a total?
Model:
public class Product
{
public string Name {get;set;}
public int Cost {get;set;}
}
Now I want to use that model in a Generic list like this:
public void GetTotal()
{
IList<Product> listOfJediProducts = new List<Product>();
Product newProduct1 = new Product();
newProduct1.Name = "LightSaber";
newProduct1.Cost = 1500;
listOfJediProducts.Add(newProduct1);
Product newProduct2 = new Product();
newProduct2.Name = "R2RobotSpec9";
newProduct2.Cost = 5000;
listOfJediProducts.Add(newProduct2);
}
How would I return say a Total for Products in list?
listOfJediProducts.Sum(p => p.Cost);
This runs the selector lambda expression over each element in the sequence (returning Cost in this case). The Sum function is then run on the "implicitly returned" IEnumerable, which obviously calculates the sum and returns it.
It is worth noting that the above is similar to writing:
listOfJediProducts.Select(p => p.Cost).Sum();
Which may be a little more obvious (if not verbose) in understanding what my first example does.
I say "implicitly returned" because Sum only makes sense on an IEnumerable of numbers, the internal workings are probably doing something closer to this:
int total;
foreach (Product p in listOfJediProducts)
total += p.Cost;
return total;
else, using foreach loop
int total_cost = 0;
foreach (Product p in listOfJediProducts)
{
total_cost+= p.cost;
}
I have a simple static inventory class which is a list of custom class Item. I am working on a crafting system and when I craft something I need to remove the required Items from my inventory list.
I tried to create a method that I can call which takes an array of the items to remove as a parameter, but its not working.
I think its because the foreach loop doesn't know which items to remove? I am not getting an error messages, it just doesn't work. How can I accomplish this?
public class PlayerInventory: MonoBehaviour
{
public Texture2D tempIcon;
private static List<Item> _inventory=new List<Item>();
public static List<Item> Inventory
{
get { return _inventory; }
}
public static void RemoveCraftedMaterialsFromInventory(Item[] items)
{
foreach(Item item in items)
{
PlayerInventory._inventory.Remove(item);
}
}
}
Here is the function that shows what items will be removed:
public static Item[] BowAndArrowReqs()
{
Item requiredItem1 = ObjectGenerator.CreateItem(CraftingMatType.BasicWood);
Item requiredItem2 = ObjectGenerator.CreateItem(CraftingMatType.BasicWood);
Item requiredItem3 = ObjectGenerator.CreateItem(CraftingMatType.String);
Item[] arrowRequiredItems = new Item[]{requiredItem1, requiredItem2, requiredItem3};
return arrowRequiredItems;
}
And here is where that is called:
THis is within the RecipeCheck static class:
PlayerInventory.RemoveCraftedMaterialsFromInventory(RecipeCheck.BowAndArrowReqs());
While I like Jame's answer (and it sufficiently covers the contracts), I will talk on how one might implement this equality and make several observations.
For starts, in the list returned there may be multiple objects of the same type - e.g. BasicWood, String. Then there needs to be a discriminator used for each new object.
It would be bad if RemoveCraftedMaterialsFromInventory(new [] { aWoodBlock }) to remove a Wood piece in the same way that two wood pieces were checked ("equals") to each other. This is because being "compatible for crafting" isn't necessarily the same as "being equals".
One simple approach is to assign a unique ID (see Guid.NewGuid) for each specific object. This field would be used (and it could be used exclusively) in the Equals method - however, now we're back at the initial problem, where each new object is different from any other!
So, what's the solution? Make sure to use equivalent (or identical objects) when removing them!
List<Item> items = new List<Item> {
new Wood { Condition = Wood.Rotten },
new Wood { Condition = Wood.Epic },
};
// We find the EXISTING objects that we already have ..
var woodToBurn = items.OfType<Wood>
.Where(w => w.Condition == Wood.Rotten);
// .. so we can remove them
foreach (var wood in woodToBurn) {
items.Remove(wood);
}
Well, okay, that's out of the way, but then we say: "How can we do this with a Recipe such that Equals isn't butchered and yet it will remove any items of the given type?"
Well, we can either do this by using LINQ or a List method that supports predicates (i.e. List.FindIndex) or we can implement a special Equatable to only be used in this case.
An implementation that uses a predicate might look like:
foreach (var recipeItem in recipeItems) {
// List sort of sucks; this implementation also has bad bounds
var index = items.FindIndex((item) => {
return recipeItem.MaterialType == item.MaterialType;
});
if (index >= 0) {
items.RemoveAt(index);
} else {
// Missing material :(
}
}
If class Item doesn't implement IEquatable<Item> and the bool Equals(Item other) method, then by default it will use Object.Equals which checks if they are the same object. (not two objects with the same value --- the same object).
Since you don't say how Item is implemented, I can't suggest how to write it's Equals(), however, you should also override GetHashCode() so that two Items that are Equal return the same hash code.
UPDATE (based on comments):
Essentially, List.Remove works like this:
foreach(var t in theList)
{
if (t.Equals(itemToBeRemove))
PerformSomeMagicToRemove(t);
}
So, you don't have to do anything to the code you've given in your question. Just add the Equals() method to Item.
My main problem description goes like this:
I have two lists of BankAccount objects. A BankAccount has properties such as BankCode and AccountNumber which uniquely identifies an account. So both the lists may contain the same bank account but they may have their Source, Amount, or AccountTypes differing.
The aim here is to merge those two lists:
add accounts to the first list if it is available in the second (but not in the first list).
If the bank accounts are the same in both lists, update the details of the bank account in the first list with the details of the (matching) bank account in the 2nd list.
I've tried implementing the solution mentioned in one SO post. I've went and tried writing my code down at a .NET code pad site. But I am not able to get the output after trying to execute line no. 93 which I've commented.
class BankAccount
{
public string BankCode{get;set;}
public string AccountNumber{get;set;}
public string AccountType{get;set;}
public string Amount{get;set;}
public string Source{get;set;}
public override bool Equals(object obj)
{
var acc = obj as BankAccount;
return Equals(acc);
}
public override int GetHashCode()
{
return this.GetHashCode();
}
public bool Equals(BankAccount acc2)
{
if(acc2 == null) return false;
if(string.IsNullOrEmpty(acc2.BankCode)) return false;
if(string.IsNullOrEmpty(acc2.AccountNumber)) return false;
return this.BankCode.Equals(acc2.BankCode) && this.AccountNumber.Equals(acc2.AccountNumber);
}
}
//List<BankAccount> lst3 = lst.Union(lst1).ToList(); // line 93
Full code can be viewed here.
PS: I'm not sure if this could be a problem with the codepad site or not.
Update - Monday, 14 February 2011 - 4:50:24 (am) / 04:50:24 GMT
Thanx for the update. But something is still amiss. In the output, list 3's first item should have AccountType=P and Source=lst2. The 2nd requirement isn't met. I figure Union() does only a part of what I need. What do I need to do satisfy the 2nd requirement.
EDIT by drachenstern: I'm not sure this title is any better, but it's definitely more informative than the previous title as to the actual question :\
Solution 1:
This solution doesn't achieve your (newly) stated 2 requirements, but aims to fix the issue in your attempt at solving the problem using a LINQ Union().
You've got a recursive call which is causing a stack overflow exception on this line (23):
public override int GetHashCode()
{
return this.GetHashCode();
}
I suggest changing it to something like this:
public override int GetHashCode()
{
return (BankCode + AccountNumber).GetHashCode();
}
EDIT:
Ensure that members BankCount and AccountNumber will never be null or an exception will be thrown. I suggest you look up standard practices for overriding the GetHashCode() method.
Resharper's autogenerated GetHashCode override:
(The 397 value ensures that the numbers won't clash if the BankCode and AccountNumber are swapped. The unchecked means that there won't be overflow issues with the number *397)
public override int GetHashCode()
{
unchecked
{
return ((BankCode != null ? BankCode.GetHashCode() : 0)*397) ^ (AccountNumber != null ? AccountNumber.GetHashCode() : 0);
}
}
Solution 2:
This solution aims to achieve your 2 requirements, without using a LINQ Union().
If you are wanting to merge the lists, using the 2nd list as the preference then perhaps try this:
var mergedList = new List<BankAccount>();
// add items from lst or any duplicates from lst1
foreach (var bankAccount in lst)
{
var account = bankAccount;
var dupe = lst1.FirstOrDefault(item => item.Equals(account));
mergedList.Add(dupe ?? bankAccount);
}
// add any items in lst1 that are not duplicates
foreach (var bankAccount in lst1.Where(item=>!mergedList.Contains(item)))
{
mergedList.Add(bankAccount);
}
If you're looking for code minimization:
// add items from lst or any duplicates from lst1
var temp = lst.Select(item => lst1.FirstOrDefault(item1 => item1.Equals(item)) ?? item);
// add any items in lst1 that are not duplicates
var result = temp.Union(lst1.Where(item => !temp.Contains(item)));
The problem is this method in class BankAccount. It's causing a stack overflow because it keeps calling itself.
public override int GetHashCode()
{
return this.GetHashCode();
}
Try using this.ToString().GetHashCode() and overriding ToString with something that makes sense for the class.