I need to find the max, min and mean (stats) value of a list of objects that have its own stats (ResultGroup class stats, based on all Result stats).
When I add objects the values are easily updated, but if I change or remove one of them, then I need to find the stats again. Usually there will be more than 40.000 items, and I need it to be a fast operation.
Is there any better way than looping through all items?
public class ResultGroup
{
private Stats resultStats;
//I need an updated stats
public Stats ResultStats
{
get { return resultStats; }
}
private readonly ObservableCollection<Result> results = new ObservableCollection<Result>();
public ObservableCollection<Result> Results
{
get
{
return results;
}
}
public ResultGroup()
{
this.resultStats = new Stats();
this.results.CollectionChanged += new NotifyCollectionChangedEventHandler(CollectionChanged);
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
//It works ok on add.
Stats lastResultStat = this.results[this.results.Count - 1].Stat;
if (resultStats.Max < lastResultStat.Max)
resultStats.Max = lastResultStat.Max;
if (resultStats.Min > lastResultStat.Min)
resultStats.Min = lastResultStat.Min;
resultStats.Mean = (resultStats.Mean * (this.results.Count - 1) + lastResultStat.Mean) / this.results.Count;
}
else if (e.Action == NotifyCollectionChangedAction.Reset)
{
this.resultStats = StatsFactory();
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
//Need to find the stats here
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
//Need to find the stats here
}
}
private Stats StatsFactory()
{
Stats dataStats = new Stats();
dataStats.Max = float.MinValue;
dataStats.Min = float.MaxValue;
dataStats.Mean = 0;
return dataStats;
}
}
public class Result
{
private float[] data;
//Another class will fill data and set the Stats (max, min, mean)
public float[] Data
{
get { return data; }
}
public Result(int lenght)
{
this.data = new float[lenght];
}
private Stats stat;
public Stats Stat
{
get { return stat; }
set { stat = value; }
}
}
public class Stats
{
public float Max { get; set; }
public float Min { get; set; }
public float Mean { get; set; }
}
When removing an item, you need only loop through all items, when the removed item equals the current min/max.
When replacing an item, you need only loop through all items, when the removed item equals the current min/max and the new item is greater/smaller.
Have you tried to use LINQ operators like Min, Max and Average inside CollectionChanged?
Have you tried to use DB for this ?
as DB has indexing which can help. Also have a look at KDB or SAP's HANA which has Vertical/Column based DB which makes seeping through millions of rows in milliseconds.
Maybe simple file based DB like SqlLite will help. (that should help with reducing memory usage as well if you are dealing with large amounts of data)
I think you can cache max, min values when you first initialize a collection, then you can compare new values with cached value.
I can suggest the next algorithm: if i had a huge list of values I would split it on the ranges and make a collection for each range. For each collection I would have a cached mean value that would be recalculated when a collection would be changed. When I add new value(or change) I would see the stats of the element and find the collection with needed range. In this situation we get a stats as additional index and we must find max and min values only in the certain collections(first, last). The mean value we could get from mean values of all collections. Max, Min values we can cache too in the first and last collections.
//Better have a custom collection with the required properties inside the collection and then have the linq on top of the collection to store the aggregate values...
public class ObserCol: ObservableCollection<int>
{
private int _maxValue = 0;
public ObserCol() {
base.CollectionChanged +=new NotifyCollectionChangedEventHandler(CollectionChanged);
}
public int MaxValue{
get {
return _maxValue;
}
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
//Can use Linq to get the Max or Other Aggregate values..
}
else if (e.Action == NotifyCollectionChangedAction.Reset)
{
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
}
}
}
Related
So I have an item class, an inventory class and the UIController scripts. When the player goes over the item on the field the player should have the item added to the inventory, if he/she has the item it should increase the amount by one.
The issue I'm having is the numbers on the UI are random and sometimes items add amounts other times they just use a new slot. I have added the three scripts I mentioned above.
public Item()
{
amount = 0;
}
public int GetItemAmount()
{
return amount;
}
public void IncreaseAmount(int amt)
{
amount += amt;
}
public void SpawnItem()
{
Instantiate(prefab);
}
public Inventory()
{
inventory = new List<Item>();
}
// Add item to inventory. if item is already in inventory, increase amount.
public void AddItem(Item item)
{
if (inventory.Count == 0)
{
inventory.Add(item);
}
else
{
for (int i = 0; i < GetInventory().Count; i++)
{
if (this.inventory[i] == item)
{
inventory[i].IncreaseAmount(1);
break;
}
else
{
inventory.Add(item);
}
break;
}
}
}
void Start()
{
player = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>();
inventoryItems = CreateInventoryItemUI(itemSlotImage);
}
private void Update()
{
var playerItems = player.inventory.GetInventory();
for (int i = 0; i <= playerItems.Count ; i++)
{
//Debug.Log("The value of i is : " + i);
if (playerItems[i] != null)
{
inventoryItems[i].GetComponentInChildren<Image>().sprite = playerItems[i].imageIcon;
var amountUI = inventoryItems[i].transform.Find("ItemSlotAmount").GetComponent<Text>();
amountUI.gameObject.SetActive(true);
amountUI.text = playerItems[i].GetItemAmount().ToString();
Debug.Log("The items being added will be: " + amountUI.text);
inventoryItems[i].gameObject.SetActive(true);
}
}
}
I think I just another set of eyes. I debugged and checked reference of the increase amount method and it's only used in the inventory script.
Any ideas would be very helpful.
You are checking if the added Item is exactly the same object as one in the list, as in the same reference. I'm assuming this is probably not what you want, please post the part of the code calling AddItem otherwise.
Two suggestions:
First, add an Id property to Item and compare if the item being added has the same Id, instead of comparing the references.
Second, this would probably be cleaner and more efficient if you used a Dictionary instead of a List for storing the items. Give it a try!
I need to make a loop that iterates through all objects in the list and extracting the value of a specific index and return an Int with the total amount.
I will try to explain as throughly as possible.
public class CostEachActivity
{
public string activityID { get; set; }
public int accountNr { get; set; }
public int amount { get; set; }
}
This is my class which the list contains objects from.
I will send down a string of activityID which I have to return the total cost of from all accounts.
this is the list which contains all the objects(the count is 5 atm)
List<CostEachActivity> dka = new List<CostEachActivity>();
EDIT: This is the working method.
int sum = 0;
List<CostEachActivity> templist = new List<CostEachActivity>();
foreach (CostEachActivity cea in dka)
{
if (cea.activityID == activityID)
templist.Add(cea);
}
foreach(var item in tempList)
{
sum += item.Amount;
}
The Problem
Your inner loop, besides not compiling, should be outside the main loop. After finding all the items you want to sum it (or in a different way to already sum it as you find the matching item.
Also, seeing that you opened a {} after the if and nested the second foreach in it - That scope happens for every item. You if statement, if returns true will execute the adding to the tempList and then, no matter what occurred in the if the second foreach will take place.
if (cea.activityID == activityID) // If this is true
templist.Add(cea); // Then this executes
{ // And this happens in any case
foreach (cea.amount = sum + amount)
}
Possible Solutions
Doing it the way you started:
int sum = 0;
List<CostEachActivity> templist = new List<CostEachActivity>();
foreach (CostEachActivity cea in dka)
{
if (cea.activityID == activityID)
templist.Add(cea);
}
foreach(var item in tempList)
{
sum += item.Amount;
}
Sum the values as you go:
int sum = 0;
foreach (CostEachActivity cea in dka)
{
if (cea.activityID == activityID)
sum += cea.Amount;
}
And IMO the best way: Using linq you can do this:
var sum = dka.Where(item => item.activityID == activityID)
.Sum(item => item.amount);
If I got this right you could do something like:
public int GetSum(string activityId)
{
return dka.Where(cea => cea.activityId == activityId).Sum(cea => cea.amount)
}
Or with the new C# 6.0 Expression-bodied function members:
public int GetSome(string activityId) =>
dka.Where(cea => cea.activityId == activityId)
.Sum(cea => cea.amount);
Use Linq
public int GetSum(string activityID)
{
return dka.Where(d => d.activityID == activityID).Sum(d => d.amount);
}
I have a class from a EF db context which I have displayed in a datagrid based on an ObservableCollection. The user can edit the the grid and this all displays fine.
However I now need to send the data back to the database. I do not want to send all the items in the collection to my save method, so can I find only the items that have been have change in the collection?
just as an idea (not professing this to be an ideal solution) i have run into a similar issue, looked around for potential solutions and none of those were exactly what i wanted.
i had to pass a collection to WPF DataGrid and it seemed to complain about using List, hence i turned to ObservableCollection
i did not want to work directly with the EF context for multiple reasons primarily because i wanted to grab items and pass them to intermediate transaction factory to be processed (business logic).
so decided to stick with ObservableCollection and instead make slight modification to the ViewModel since this i was free to do it.
my model ended up to look like this:
internal class databaseItemModel
{
int _id;
string _description;
decimal _price;
decimal _quantity;
decimal _cost;
bool _modified;
public databaseItemModel()
{
_modified = false;
}
public int id { get { return _id; } }
public bool modified { get { return _modified; } }
public string description { get { return _description; } set { _description = value; _modified = true; } }
public decimal price { get { return _price; } set { _price = value; _modified = true; } }
public decimal quantity { get { return _quantity; } set { _quantity = value; _modified = true; } }
public decimal cost { get { return _cost; } set { _cost = value; _modified = true; } }
public bool selected { get; set; }
public void setId(int _idvalue)
{
_id = _idvalue;
}
public decimal value
{
get { return price * quantity; }
}
public void setDescription(string _descr)
{
_description = _descr;
}
public void setPrice(decimal _pr)
{
_price = _pr;
}
public void setQuantity(decimal _qty)
{
_quantity = _qty;
}
public void setCost(decimal _cst)
{
_cost = _cst;
}
}
Basically, the plain idea behind it is that i would use functions to populate data rather than using properties direct and then pass the item to ObservableCollection which then would become the source for the DataGrid.ItemsSource
since DataGrid/ObservableCollection would work with properties - modified objects would be marked as modified and i would then be able to pick up the collection on exit and collect the modified items.
hope this is helpful.
You can use NotifyCollectionChangedAction to detect which items has been changed in the ObservableCollection
However, just Jens said, the best way would be let the EF handle it for you.
Cheers.
ObservableCollection<int> listOfObject = new ObservableCollection<int>() { 1, 2, 3, 4};
listOfObject.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(
delegate (object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
Console.WriteLine($"{e.NewItems[0]} just been added to the list at index = {e.NewStartingIndex}");
}
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)
{
Console.WriteLine($"Replace item {e.OldItems[0]} with {e.NewItems[0]}");
}
}
);
listOfObject.Add(1);
listOfObject[2] = 3;
listOfObject[3] = 1;
Output:
1 just been added to the list at index = 4
Replace item 3 with 3
Replace item 4 with 1
I am trying to find all the zones that contain 2 or more zone members where the search term is a string value. Here is the code I have. In the FindCommmonZones method when I try to cast the result of an Intersect to an ObservableCollection I get a run-time on an invalid cast. The question is, is there a better way to do this? The string array that is the paramter for FindCommonZones() can be any count of strings. StackOverflow had some other similar posts but none really answered my question - it looked like they all pertained more to SQL.
Some code:
public class Zone
{
public List<ZoneMember> MembersList = new List<ZoneMember>();
private string _ZoneName;
public string zoneName{ get{return _ZoneName;} set{_ZoneName=value;} }
public Zone ContainsMember(string member)
{
var contained = this.MembersList.FirstOrDefault(m => m.MemberWWPN.
Contains(member) || m.MemberAlias.Contains(member));
if (contained != null) { return this; }
else { return null; }
}
}
public class ZoneMember
// a zone member is a member of a zone
// zones have ports, WWPNs, aliases or all 3
{
private string _Alias = string.Empty;
public string MemberAlias {get{return _Alias;} set{_Alias = value; } }
private FCPort _Port = null;
public FCPort MemberPort { get { return _Port; } set { _Port = value; } }
private string _WWPN = string.Empty;
public string MemberWWPN { get { return _WWPN; } set { _WWPN = value; } }
private bool _IsLoggedIn;
public bool IsLoggedIn { get { return _IsLoggedIn; } set { _IsLoggedIn = value; } }
private string _FCID;
public string FCID {get{return _FCID;} set{ _FCID=value; } }
}
private ObservableCollection<ZoneResult> FindCommonZones(string[] searchterms)
{
ObservableCollection<ZoneResult> tempcollection =
new ObservableCollection<ZoneResult>();
//find the zones for the first search term
tempcollection = this.FindZones(searchterms[0]);
//now search for the rest of the search terms and compare
//them to existing result
for (int i = 1; i < searchterms.Count(); i++ )
{
// this line gives an exception trying to cast
tempcollection = (ObservableCollection<ZoneResult>)tempcollection.
Intersect(this.FindZones(searchterms[i]));
}
return tempcollection;
}
private ObservableCollection<ZoneResult> FindZones(string searchterm)
// we need to track the vsan where the zone member is found
// so use a foreach to keep track
{
ObservableCollection<ZoneResult> zonecollection = new ObservableCollection<ZoneResult>();
foreach (KeyValuePair<int, Dictionary<int, CiscoVSAN>> fabricpair in this.FabricDictionary)
{
foreach (KeyValuePair<int, CiscoVSAN> vsanpair in fabricpair.Value)
{
var selection = vsanpair.Value.ActiveZoneset.
ZoneList.Select(z => z.ContainsMember(searchterm)).
Where(m => m != null).OrderBy(z => z.zoneName);
if (selection.Count() > 0)
{
foreach (Zone zone in selection)
{
foreach (ZoneMember zm in zone.MembersList)
{
ZoneResult zr = new ZoneResult(zone.zoneName,
zm.MemberWWPN, zm.MemberAlias, vsanpair.Key.ToString());
zonecollection.Add(zr);
}
}
}
}
}
return zonecollection;
}
Intersect is actually Enumerable.Intersect and is returning an IEnumerable<ZoneResult>. This is not castable to an ObservableCollection because it isn't one - it is the enumeration of the intersecting elements in both collections.
You can, however create a new ObservableCollection from the enumeration:
tempcollection = new ObservableCollection<ZoneResult>(tempcollection
.Intersect(this.FindZones(searchterms[i]));
Depending on how many elements you have, how ZoneResult.Equals is implemented, and how many search terms you expect, this implementation may or may not be feasable (FindZones does seem a little overly-complicated with O(n^4) at first glance). If it seems to be a resource hog or bottleneck, it's time to optimize; otherwise I would just leave it alone if it works.
One suggested optimization could be the following (incorporating #Keith's suggestion to change ContainsMember to a bool) - although it is untested, I probably have my SelectManys wrong, and it really largely amounts to the same thing, you hopefully get the idea:
private ObservableCollection<ZoneResult> FindCommonZones(string[] searchterms)
{
var query = this.FabricDictionary.SelectMany(fabricpair =>
fabricpair.Value.SelectMany(vsanpair =>
vsanpair.Value.ActiveZoneSet.ZoneList
.Where(z=>searchterms.Any(term=>z.ContainsMember(term)))
.SelectMany(zone =>
zone.MembersList.Select(zm=>new ZoneResult(zone.zoneName, zm.MemberWWPN, zm.MemberAlias, vsanpair.Key.ToString()))
)
)
.Distinct()
.OrderBy(zr=>zr.zoneName);
return new ObservableCollection<ZoneResult>(query);
}
I have a WinForms application which uses a list box to display list of items. My application hangs whent the number of items in the listbox exceeds some 150 items. Is this the property of ListBox control that it can hold only so many items? If so, I would request you to provide a solution to this problem.
Thanks,
Rakesh.
It all depends on what you are binding, if you are binding simple key value pairs you can instantly bind 10k easy. You might want to try adding the items in a loop instead of binding to see if there is a certain item it hangs on.
for (int i = 0; i < 10000; i++)
{
listBox1.Items.Add("item:" + i.ToString());
}
You can back your listbox by a larger dataset and use a paging mechanism or you can add an event listener for SizeChanged and disable adding when it reaches your max.
First tip, always..
SuspendLayout();
// fill your lists
ResumeLayout();
Second tip, use AddRange when possible.
Third, and it may be overkill, create your own ListBox...
public class LimitedListBox : ListBox
{
private int _maxItems = 100;
public LimitedListBox()
{
SetItems(new LimitedObjectCollection(this, _maxItems));
}
public int MaxItems
{
get { return _maxItems; }
set { _maxItems = value; }
}
/// <summary>
/// This is the only 'bug' - no design time support for Items unless
/// you create an editor.
/// </summary>
public new LimitedObjectCollection Items
{
get
{
if (base.Items == null)
{
SetItems(new LimitedObjectCollection(this, _maxItems));
}
return (LimitedObjectCollection) base.Items;
}
}
private void SetItems(ObjectCollection items)
{
FieldInfo info = typeof (ListBox).GetField("itemsCollection",
BindingFlags.NonPublic | BindingFlags.Instance |
BindingFlags.GetField);
info.SetValue(this, items);
}
#region Nested type: LimitedObjectCollection
public class LimitedObjectCollection : ObjectCollection
{
private int _maxItems;
public LimitedObjectCollection(ListBox owner, int maxItems)
: base(owner)
{
_maxItems = maxItems;
}
public LimitedObjectCollection(ListBox owner, ObjectCollection value, int maxItems)
: base(owner)
{
_maxItems = maxItems;
AddRange(value);
}
public LimitedObjectCollection(ListBox owner, object[] value, int maxItems)
: base(owner)
{
_maxItems = maxItems;
AddRange(value);
}
public int MaxItems
{
get { return _maxItems; }
set { _maxItems = value; }
}
public new int Add(object item)
{
if (base.Count >= _maxItems)
{
return -1;
}
return base.Add(item);
}
public new void AddRange(object[] items)
{
int allowed = _maxItems - Count;
if (allowed < 1)
{
return;
}
int length = allowed <= items.Length ? allowed : items.Length;
var toAdd = new object[length];
Array.Copy(items, 0, toAdd, 0, length);
base.AddRange(toAdd);
}
public new void AddRange(ObjectCollection value)
{
var items = new object[value.Count];
value.CopyTo(items, 0);
base.AddRange(items);
}
}
#endregion
}
Why don't you just parse your database something like this.
int Total = yourarray.GetLength(0);
Then all you have to do is this in your new array.
double [] new = double[Total];
array.copy(Total,new);
Now you have an array thats dynamic. Anytime your database grows it automatically populates
the new array.
Or if you can do a select count statement on your database you can get the total number or rows and then pipe that to a string. Then you use the string to make control the array. Hope this helps
I just got an "out of memory" error message when filling a list box. The problem was not anything to do with too many items. There was a bug in my code and the items in the list box were returning null in the ToString() method. So the Dot Net error message was wrong and confusing.