I am trying to get the number of how many times player.userID shows in a list but I can't seem to figure out how, I have searched the internet for an hour now.
class ConfigData
{
[JsonProperty(PropertyName = "Ban")]
public uint ban = 3;
[JsonProperty(PropertyName = "Kick")]
public uint kick = 2;
[JsonProperty(PropertyName = "Banned Message")]
public string kickMessage = "You are banned";
}
class StoredData
{
public List<ulong> Reports = new List<ulong>();
public List<ulong> Banned = new List<ulong>();
public List<ulong> Kicked = new List<ulong>();
}
void OnPlayerConnected(BasePlayer player)
{
int count = storedData.Reports.Count(reportedID => reportedID == player.userID);
Puts($"{player.userID} has {count} reports");
if (count >= configData.ban)
{
storedData.Banned.Add(player.userID);
SaveData();
return;
}
if (storedData.Banned.Contains(player.userID))
{
Network.Net.sv.Kick(player.net.connection, rust.QuoteSafe(configData.kickMessage));
return;
}
else
{
return;
}
}
I can post the full code if needed.
The Count method accepts a lambda that tells it the condition on which to count.
Since your reports are just a list of user IDs, all you need to do is:
int count = storedData.Reports.Count(reportedID => reportedID == player.userID);
if (count >= configData.ban) // NOTE: changed this to >=
{
...
}
As a side note, if it was a list of objects instead, and you wanted to compare to a property named someProperty, then it would be:
report => report.someProperty == player.userID.
A lambda is just a shorthand for a function; the part before the => is the parameter list (here, it just accepts a single parameter - the current element of storedData.Reports). The parameter name is arbitrary (your choice).
The part behind the => is the function body, with implicit return, so
reportedID == player.userID is like
{ return reportedID == player.userID; }.
The Count method basically walks through the IDs in Reports, and for each one, it asks the lambda if it should count it or not, by passing that ID to the lambda, and checking if the lambda returns true or false.
P.S.
In your code it says:
if (!storedData.Banned.Contains(player.userID)) { /* kick */ }
Are you sure you want to kick the players that are not in the banned list? Check the logic that relates to kicking/banning (maybe do some tests), it doesn't look quite right to me.
Related
This is for a .net core 3.1 application, using Blazor for front end.
I have a list of dates that I need to take, and then build an "analysis" report using those dates. The analysis basically needs to look at the dates and tell me how many dates fall on each day of the week, and also how many dates are in each month of the year. This fis for an Human Resources application where they are tracking employee absences.
I have a List that I've built, and I'm passing it into a method that then performs an analysis.
public class DateHourResponseModel
{
public DateTime Date { get; set; }
public int Hours { get; set; }
}
I figured I could then take that list, and create a list of "Responses", each for a different day of the week, and month of the year. I figured for "Type" I could just use the DateTime.DayOfWeek, DateTime.Month and get an into (0-6 for day of week, 1-12 for month) which is why I have the "IsMonth" bool on there.
public class AbsenceAnalysisResponse
{
public int Type { get; set; }
public bool IsMonth { get; set; }
public int Count { get; set; }
public int Hours { get; set; }
}
And This class, which is just a list of the above:
public class AbsenceAnalysis
{
public List<AbsenceAnalysisResponse> Responses { get; set; }
}
My question is: Is there a way to build this analysis report by doing a foreach on the list of dates that I start with? I haven't figured out a way to create this list without doing something like this:
var analysisResponses = new List<AbsenceAnalysisResponse>
{
new AbsenceAnalysisResponse
{
Type = 0,
IsMonth = false,
Count = dateHourModel.Count(x => (int)x.Date.DayOfWeek == 0),
Hours = dateHourModel.Where(x => (int)x.Date.DayOfWeek == 0).Sum(x => x.Hours)
},
I feel like an idiot because I know there has got to be a more elegant way of doing this, and maybe the problem is how I'm approaching it. I have the analysis working and displaying on the front end but I absolutely hate how I'm creating the list of "responses". I'm still pretty green behind the ears with this, and I don't know if I just haven't been searching the right questions online or what, but I haven't found anything where someone is doing something similar. Thanks for any help, and please let me know if there is any information I need to provide.
Something like this should work:
var models = new List<DateHourResponseModel>(); //Should be a filled list, not empty like this
var modelMap = new Dictionary<(int, bool), AbsenceAnalysisResponse>();
foreach (DateHourResponseModel dateHour in models)
{
Add(type: (int)dateHour.Date.DayOfWeek, isMonth: false);
Add(type: dateHour.Date.Month, isMonth: true);
void Add(int type, bool isMonth)
{
(int, bool) key = (type, isMonth);
//Try to get existing responses
if(!modelMap.TryGetValue(key, out AbsenceAnalysisResponse response))
{
//Create if first time adding to it
modelMap[key] = response = new AbsenceAnalysisResponse
{
Type = type,
IsMonth = isMonth,
Count = 0,
Hours = 0
};
}
response.Count += 1;
response.Hours += dateHour.Hours;
}
}
//convert to list, can order if needed with .OrderBy()
List<AbsenceAnalysisResponse> analysisResponses = modelMap.Values.ToList();
The idea would be to loop through each model and add/modify to the list. Instead of searching through the list, a dictionary can be used with a unique key. In this case type + isMonth, in tuple form, but it could also be a string "{type}-{isMonth}" or something. If the key is not found then it will be created, otherwise just modified. At the end, turn the Dictionary into a list of its values. There are other ways of doing this, but this should be a good approach
Just wanted to post another solution I found while tinkering with this. I really liked Gekctek's answer, and used that initially. I think this one might be slightly more readable? Happy to receive any feedback.
private AbsenceAnalysis GetAbsenceAnalysisFromData(List<DateHourResponseModel> dateHourModel)
{
var analysis = new List<AbsenceAnalysisResponse>();
foreach (var data in dateHourModel)
{
var week = analysis.FirstOrDefault(x =>
x.Type == (int)data.Date.DayOfWeek && x.IsMonth == false);
var month = analysis.FirstOrDefault(x =>
x.Type == (int)data.Date.Month && x.IsMonth == true);
if (week == null)
{
week = new AbsenceAnalysisResponse
{
Count = 0,
Hours = 0,
IsMonth = false,
Type = (int)data.Date.DayOfWeek
};
analysis.Add(week);
}
if (month == null)
{
month = new AbsenceAnalysisResponse
{
Count = 0,
Hours = 0,
IsMonth = true,
Type = data.Date.Month
};
analysis.Add(month);
}
week.Count += 1;
week.Hours += data.Hours;
month.Count += 1;
month.Hours += data.Hours;
}
I have two ObservableCollection<Model>, each model has 10 properties and there are approximately 30 objects within both collections in the beggining. They basicaly work like this: initialy there are saved the same objects in both OCs, where one is the original and the other one is where changes are happening. Basicaly I would need the first one just to see if changes have been made to compare the values. So far I have come up with
list1.SequenceEquals(list2);
but this only works if i add a new object, it does not recognize changes in the actual properties. Is there a fast way this could be done or I need to do foreach for every object and compare individual properties one by one? Because there may be more than 30 objects to compare values. Thanks.
Is there a fast way this could be done or I need to do foreach for every object and compare individual properties one by one?
If by "fast" you mean "performant" then comparing property-by property is probably the fastest way. If by "fast" you mean "less code to write" then you could use reflection to loop through the properties and compare the values of each item.
Note that you'll probably spend more time researching, writing, and debugging the reflection algorithm that you would just hand-coding the property comparisons.
A simple way to use the built-in Linq methods would be do define an IEqualityComparer<Model> that defines equality of two Model objects:
class ModelEqualityComparer : IEqualityComparer<Model>
{
public bool Equals(Model m1, Model m2)
{
if(m1 == null || 2. == null)
return false;
if (m1.Prop1 == m2.Prop1
&& m1.Prop2 == m2.Prop2
&& m1.Prop3 == m2.Prop3
...
)
{
return true;
}
else
{
return false;
}
}
public int GetHashCode(Model m)
{
int hCode = m.Prop1.GetHashCode();
hCode = hCode * 23 + ^ m.Prop2.GetHashCode();
hCode = hCode * 23 + ^ m.Prop32.GetHashCode();
...
return hCode;
}
}
I think you can compare them defining a custom IEqualityComparer<T>, and using the overload of IEnumerable.SequenceEqualsthat supports a custom comparer: Enumerable.SequenceEqual<TSource> Method (IEnumerable<TSource>, IEnumerable<TSource>, IEqualityComparer<TSource>)more info about it here: http://msdn.microsoft.com/it-it/library/bb342073(v=vs.110).aspx
I'll post here an usage example from that page in case it goes missing:
Here is how to define a IEqualityComparer<T>
public class Product
{
public string Name { get; set; }
public int Code { get; set; }
}
// Custom comparer for the Product class
class ProductComparer : IEqualityComparer<Product>
{
// Products are equal if their names and product numbers are equal.
public bool Equals(Product x, Product y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the products' properties are equal.
return x.Code == y.Code && x.Name == y.Name;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(Product product)
{
//Check whether the object is null
if (Object.ReferenceEquals(product, null)) return 0;
//Get hash code for the Name field if it is not null.
int hashProductName = product.Name == null ? 0 : product.Name.GetHashCode();
//Get hash code for the Code field.
int hashProductCode = product.Code.GetHashCode();
//Calculate the hash code for the product.
return hashProductName ^ hashProductCode;
}
}
Here's how to use it:
Product[] storeA = { new Product { Name = "apple", Code = 9 },
new Product { Name = "orange", Code = 4 } };
Product[] storeB = { new Product { Name = "apple", Code = 9 },
new Product { Name = "orange", Code = 4 } };
bool equalAB = storeA.SequenceEqual(storeB, new ProductComparer());
Console.WriteLine("Equal? " + equalAB);
/*
This code produces the following output:
Equal? True
*/
I have a list that contains 3 items, two of type_1, and one of type_2. I want to return a second list that contains the type and number of that type that exists. When stepping through the breakpoints set at the foreach loop, the IF statement is never true. I assume there is something wrong with my attempt to use Contains() method.
The output should be something like:
type_1 2
type_2 1
Instead, it evaluates as:
type_1 1
type_1 1
type_2 1
Is my use of Contains() not correct?
public List<item_count> QueryGraphListingsNewAccountReport()
List<item> result = new List<items>();
var type_item1 = new item { account_type = "Type_1" };
var type_item2 = new item { account_type = "Type_1" };
var type_item3 = new item { account_type = "Type_2" };
result.Add(type_item1);
result.Add(type_item2);
result.Add(type_item3);
//Create a empty list that will hold the account_type AND a count of how many of that type exists:
List<item_count> result_count = new List<item_count>();
foreach (var item in result)
{
if (result_count.Contains(new item_count { account_type = item.account_type, count = 1 } ) == true)
{
var result_item = result_count.Find(x => x.account_type == item.account_type);
result_item.count += 1;
result_count.Add(result_item);
}
else
{
var result_item = new item_count { account_type = item.account_type, count = 1 };
result_count.Add(result_item);
}
}
return result_count;
}
public class item
{
public string account_type { get; set; }
}
public class item_count
{
public int count {get; set;}
public string account_type { get; set; }
}
I think your problem is that you don't want to use contains at all. You are creating a new object in your contains statement and, obviously, it isn't contained in your list already because you only just created it. The comparison is comparing references, not values.
Why not just use the find statement that you do in the next line instead? If it returns null, then you know there isn't an item already with that type.
So you could do something like this:
var result_item = result_count.Find(x => x.account_type == item.account_type);
if (result_item != null)
{
result_item.count++;
// note here you don't need to add it back to the list!
}
else
{
// create your new result_item here and add it to your list.
}
Note: Find is o(n), so this might not scale well if you have a really large set of types. In that case, you might be better off with Saeed's suggestion of grouping.
You can do:
myList.GroupBy(x=>x.type).Select(x=>new {x.Key, x.Count()});
If you want use for loop, it's better to use linq Count function to achieve this, If you want use Contains you should implement equal operator as the way you used.
I am trying to parse a rather long log file and creating a better more manageable listing of issues.
I am able to read and parse out the individual log line by line, but what I need to do is display only unique entries, as some errors occur more often than others and are always recorded with identical text.
What I was going to try to do was create a Dictionary object to hold each unique entry and as I work through the log file, search the Dictionary object to see if the same values are already in there.
Here is a crude sample of the code I have (a work in progress, I hope I have all syntax right) that does not work. For some reason this script never sees any distinct entries (if statement never passes):
string[] rowdta = new string[4];
Dictionary<string[], int> dict = new Dictionary<string[], int>();
int ctr = -1;
if (linectr == 1)
{
ctr++;
dict.Add(rowdta, ctr);
}
else
{
foreach (KeyValuePair<string[], int> pair in dict)
{
if ((pair.Key[1] != rowdta[1]) || (pair.Key[2] != rowdta[2])| (pair.Key[3] != rowdta[3]))
{
ctr++;
dict.Add(rowdta, ctr);
}
}
}
Some sample data:
First line
rowdta[0]="ErrorType";
rowdta[1]="Undefined offset: 0";
rowdta[2]="/url/routesDisplay2.svc.php";
rowdta[3]="Line Number 5";
2nd line
rowdta[0]="ErrorType";
rowdta[1]="Undefined offset: 0";
rowdta[2]="/url/routesDisplay2.svc.php";
rowdta[3]="Line Number 5";
3rd line
rowdta[0]="ErrorType";
rowdta[1]="Undefined variable: fvmsg";
rowdta[2]="/url/processes.svc.php";
rowdta[3]="Line Number 787";
So, with this, the Dictionary will have 2 items in it, first line and 3rd line.
I have also tried this with the following which nalso does not find any variations in the log file text.
if (!dict.ContainsKey(rowdta)) {}
Can someone please help me get this syntax right? I am just a newbie at C# but this should be relatively straightforward. As always, I am thinking that this should be enough information to get the conversation started. If you want/need more detail, please let me know.
Either create a wrapper for your strings which implements IEquatable.
public class LogFileEntry :IEquatable<LogFileEntry>
{
private readonly string[] _rows;
public LogFileEntry(string[] rows)
{
_rows = rows;
}
public override int GetHashCode()
{
return
_rows[0].GetHashCode() << 3 |
_rows[2].GetHashCode() << 2 |
_rows[1].GetHashCode() << 1 |
_rows[0].GetHashCode();
}
#region Implementation of IEquatable<LogFileEntry>
public override bool Equals(Object obj)
{
if (obj == null)
return base.Equals(obj);
return Equals(obj as LogFileEntry);
}
public bool Equals(LogFileEntry other)
{
if(other == null)
return false;
return _rows.SequenceEqual(other._rows);
}
#endregion
}
Then use that in your dictionary:
var d = new Dictionary<LogFileEntry, int>();
var entry = new LogFileEntry(rows);
if( d.ContainsKey(entry) )
{
d[entry] ++;
}
else
{
d[entry] = 1;
}
Or create a custom comparer similar to that proposed by #dasblinkenlight and use as follows
public class LogFileEntry
{
}
public class LogFileEntryComparer : IEqualityComparer<LogFileEntry>{ ... }
var d = new Dictionary<LogFileEntry, int>(new LogFileEntryComparer());
var entry = new LogFileEntry(rows);
if( d.ContainsKey(entry) )
{
d[entry] ++;
}
else
{
d[entry] = 1;
}
The reason that you see the problem is that an array of strings cannot be used as a key in a dictionary without supplying a custom IEqualityComparer<string[]> or writing a wrapper around it.
EDIT Here is a quick and dirty implementation of a custom comparer:
private class ArrayEq<T> : IEqualityComparer<T[]> {
public bool Equals(T[] x, T[] y) {
return x.SequenceEqual(y);
}
public int GetHashCode(T[] obj) {
return obj.Sum(o => o.GetHashCode());
}
}
Here is how you can use it:
var dd = new Dictionary<string[], int>(new ArrayEq<string>());
dd[new[] { "a", "b" }] = 0;
dd[new[] { "a", "b" }]++;
dd[new[] { "a", "b" }]++;
Console.WriteLine(dd[new[] { "a", "b" }]);
The problem is that array equality is reference equality. In other words, it does not depend on the values stored in the array, it depends only on the identity of the array.
Some solutions
use Tuple to hold the row data
use an anonymous type to hold the row data
create a custom type to hold the row data, and, if it is a class, override Equals and GetHashCode.
create a custom implementation of IEqualityComparer to compare the arrays according to their values, and pass that to the dictionary when you create it.
I have a simple domain object:
class FavoriteFood
{
public string Name;
public int Ordinal;
}
I want to have a collection of this domain object that maintains the correct ordinal. For example, given 4 favorite foods:
Name: Banana, Ordinal: 1
Name: Orange, Ordinal: 2
Name: Pear, Ordinal: 3
Name: Watermelon, Ordinal: 4
If I change Pear's ordinal to 4 it should shift Watermelon's ordinal down to 3.
If I add a new favorite food (Strawberry) with ordinal 3 it should shift Pear up to 4 and Watermelon up to 5.
If I change Pear's ordinal to 2 it should shift Orange up to 3.
If I change Watermelon's ordinal to 1, Banana would bump up to 2, Orange would bump up to 3, and Pear would bump up to 4.
What's the best way to accomplish this?
UPDATE: The name property of the domain object is dynamic and based on user input. The object has to have this Ordinal property because a user can change the order in which their favorite foods are displayed. This ordinal value is saved in a database and when populating the structure I cannot guarantee the items are added in order of their ordinals.
The trouble I am running into is when the underlying domain object is changed, there isn't a good way of updating the rest of the items in the list. For example:
var favoriteFoods = new List<FavoriteFood>();
var banana = new FavoriteFood { Name = "Banana", Ordinal = 1};
favoriteFoods.Add(banana);
favoriteFoods.Add(new FavoriteFood { Name = "Orange", Ordinal = 2 });
banana.Ordinal = 2;
// at this point both Banana and Orange have the same ordinal in the list. How can we make sure that Orange's ordinal gets updated too?
So far I have tried doing the following which works :
class FavoriteFood : INotifyPropertyChanging
{
public string Name;
public int Ordinal
{
get { return this.ordinal; }
set
{
var oldValue = this.ordinal;
if (oldValue != value && this.PropertyChanging != null)
{
this.PropertyChanging(new FavoriteFoodChangingObject { NewOrdinal = value, OldOrdinal = oldValue }, new PropertyChangingEventArgs("Ordinal"));
}
this.ordinal = value;
}
}
internal struct FavoriteFoodChangingObject
{
internal int NewOrdinal;
internal int OldOrdinal;
}
// THIS IS A TEMPORARY WORKAROUND
internal int ordinal;
public event PropertyChangingEventHandler PropertyChanging;
}
public class FavoriteFoodCollection : IEnumerable<FavoriteFood>
{
private class FavoriteFoodOrdinalComparer : IComparer<FavoriteFood>
{
public int Compare(FavoriteFood x, FavoriteFood y)
{
return x.Ordinal.CompareTo(y.Ordinal);
}
}
private readonly SortedSet<FavoriteFood> underlyingList = new SortedSet<FavoriteFood>(new FavoriteFoodOrdinalComparer());
public IEnumerator<FavoriteFood> GetEnumerator()
{
return this.underlyingList.GetEnumerator();
}
public void AddRange(IEnumerable<FavoriteFood> items)
{
foreach (var i in items)
{
this.underlyingList.Add(i);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private void UpdateOrdinalsDueToRemoving(FavoriteFood item)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal > item.Ordinal))
{
i.ordinal--;
}
}
public void Remove(FavoriteFood item)
{
this.underlyingList.Remove(item);
this.UpdateOrdinalsDueToRemoving(item);
}
public void Add(FavoriteFood item)
{
this.UpdateOrdinalsDueToAdding(item);
this.underlyingList.Add(item);
item.PropertyChanging += this.item_PropertyChanging;
}
private void item_PropertyChanging(object sender, PropertyChangingEventArgs e)
{
if (e.PropertyName.Equals("Ordinal"))
{
var ordinalsChanging = (FavoriteFood.FavoriteFoodChangingObject)sender;
this.UpdateOrdinalsDueToEditing(ordinalsChanging.NewOrdinal, ordinalsChanging.OldOrdinal);
}
}
private void UpdateOrdinalsDueToEditing(int newOrdinal, int oldOrdinal)
{
if (newOrdinal > oldOrdinal)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal <= newOrdinal && x.Ordinal > oldOrdinal))
{
//i.Ordinal = i.Ordinal - 1;
i.ordinal--;
}
}
else if (newOrdinal < oldOrdinal)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal >= newOrdinal && x.Ordinal < oldOrdinal))
{
//i.Ordinal = i.Ordinal + 1;
i.ordinal++;
}
}
}
private void UpdateOrdinalsDueToAdding(FavoriteFood item)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal >= item.Ordinal))
{
i.ordinal++;
}
}
}
This works alright, but the use of the internal Ordinal field is a strange workaround. It's needed so that the PropertyChangingEvent wont be infinitely raised.
Just use a List<string>:
List<string> foods = new List<string> { "Banana", "Orange", "Pear" };
int ordinalOfOrange = foods.IndexOf("Orange");
It's not a good idea to 'store' that ordinal if it has to change the way you describe.
Sounds like you want a SortedList. Add each item using it's Ordinal as the key.
I'd do something like the following:
public class FavoriteFoods
{
StringComparer comparer ;
List<string> list ;
public FavoriteFoods()
{
this.list = new List<string>() ;
this.comparer = StringComparer.InvariantCultureIgnoreCase ;
return ;
}
public void Add( string food , int rank )
{
if ( this.list.Contains(food,comparer ) ) throw new ArgumentException("food") ;
this.list.Insert(rank,food) ;
return ;
}
public void Remove( string food )
{
this.list.Remove( food ) ;
return ;
}
public void ChangeRank( string food , int newRank )
{
int currentRank = this.list.IndexOf(food) ;
if ( currentRank < 0 ) throw new ArgumentOutOfRangeException("food") ;
if ( newRank < 0 ) throw new ArgumentOutOfRangeException("newRank") ;
if ( newRank >= this.list.Count ) throw new ArgumentOutOfRangeException("newRank") ;
if ( newRank != currentRank )
{
this.Remove(food) ;
this.Add( food , newRank ) ;
}
return ;
}
public int GetRank( string food )
{
int rank = this.list.IndexOf(food) ;
if ( rank < 0 ) throw new ArgumentOutOfRangeException("food");
return rank ;
}
public IEnumerable<string> InRankOrder()
{
foreach ( string food in this.list )
{
yield return food ;
}
}
}
Let me restate your problem.
You have a collection of strings. You have a collection of ordinals.
You want to be able to quickly look up the ordinal of a string. And the string of an ordinal. You'd also like to be able to insert a string with a given ordinal. And change the ordinal of a string.
There are two ways to go. The first, simple, approach is to store a collection of the strings in order, with knowledge of their ordinal. You can scan the list in time O(n). You can also lookup, insert, move, and delete in time O(n) each. If you don't actually care about performance then I would strongly suggest going this way.
If you do care about performance, then you'll need to build a custom data structure. The simplest idea is to have two trees. One tree stores the strings in alphabetical order, and tells you where in the other tree the string is. The other tree stores the strings in order of the ordinals, and stores counts of how much stuff is in various subtrees.
Now here are your basic operations.
Insert. Insert in the second tree at the correct position (if you choose to move anything else in the process, updating those things in the first tree), then insert the string in the first tree.
Lookup by string. Search the first tree, find where it is in the second tree, walk back in the second tree to find its ordinal.
Lookup by ordinal. Search the second tree, find the string.
Delete. Delete from both trees.
Move ordinal. Remove from the second tree in the old position. Insert into the second tree in the new position. Update all appropriate nodes in the first tree.
For the simple version you can just use trees. If you want to get fancy, you can look up B-Trees, Red-Black trees and other types of self-balancing trees, then pick one of those.
If you program this correctly you can guarantee that all operations take time O(log(n)). However there will be a lot of constant overhead, and for small collections the effort to be clever may be a loss relative to the simple approach.