I have a big array of 700 groups. I need to sort the array by specific rules:
We need to sort by company name
Hierarchy depends on the number of points in the groups.Name
There are a lot of records with same company name, but the first one to show from specific company must contain ".All". After this record, we need to put all others with the same name ordered by "1."
Specific case when there is a position directly applied to the company
Example:
groups[0].CompanyName = "Acompany"
groups[1].CompanyName = "Acompany"
groups[2].CompanyName = "Acompany"
groups[3].CompanyName = "Acompany"
groups[4].CompanyName = "Acompany"
groups[5].CompanyName = "Bcompany"
groups[6].CompanyName = "Bcompany"
groups[7].CompanyName = "Bcompany"
groups[0].Name = "Acompany.All" //(root)
groups[1].Name = "D.Acompany.example" //this is the specific case (leaf)
groups[2].Name = "Acompany.ABC"//(group)
groups[3].Name = "D.Acompany.ABC.PrimaryTeacher" //(leaf)
groups[4].Name = "Acompany.ABC.Something"//(group)
groups[5].Name = "Bcompany.All" //(root)
groups[6].Name = "Bcompany.Sites"//(group)
groups[7].Name = "Bcompany.Sites.example" //(leaf)
The example shows how the array should look like after sort. It's really complicated, I hope I have managed to explain it.
For now I have achieved:
There are 2 problems :
1.D.A.1stFloor.Cleaner must be under A.1stFloor
2.D.B.Society.Worker must be under B.Society
My code for now :
Array.Sort(groups, (a, b) =>
{
if (a.CompanyName != b.CompanyName)
{
return a.CompanyName.CompareTo(b.CompanyName);
}
if (a.Name.Contains(".All"))
{
return -1;
}
if (b.Name.Contains(".All"))
return 1;
if (a.Name.StartsWith("D.") && a.Name.Count(x => x == '.') == 2)
return -1;
if (b.Name.StartsWith("D.") && b.Name.Count(x => x == '.') == 2)
return 1;
if (a.Name.Count(x => x == '.') == 1)
return -1;
if (b.Name.Count(x => x == '.') == 1)
return 1;
if (a.Name.StartsWith("D") && a.Name.Count(x => x == '.') == 3) //needs to be moved I guess
return -1;
if (b.Name.StartsWith("D") && b.Name.Count(x => x == '.') == 3)//needs to be moved I guess
return 1;
return a.Name.CompareTo(b.Name);
});
You construct each company as a tree, like the image below for ACompany, base on your rules and string processing functions:
Then you just need to use a Depth-First Tree traversal algorithm to get the order.
I cannot imagine how complicated your sorting rules is, sorry. But, I would like to suggest you to take advantage of existing sorting function.
Construct each group as an object, I believe that you've done that:
public class Company
{
public string CompanyName { get; set; }
public string Name { get; set; }
}
So, your group is just an array or list of Companies.
var group = new List<Company>();
Then, you implement your sorting rules in your defined comparer:
public class CoordinatesBasedComparer : IComparer<Company>
{
public int Compare(Company a, Company b)
{
//your sorting rules implemented here
}
}
Finally, you just call:
var comparer = new CoordinatesBasedComparer();
group.Sort(comparer);
Hope this helps.
Your rules are not entirely clear, but here's an example of sorting with a comparator:
Array.Sort(groups, (a, b) =>
{
// highest priority rule
if (a.CompanyName != b.CompanyName)
{
return a.CompanyName.CompareTo(b.CompanyName); // or a custom comparison
}
// more rules ordered by priority
if (a.Name == a.CompanyName + ".All")
return -1; // => "a" comes first
if (b.Name == b.CompanyName + ".All")
return 1; // => "b" comes first
// more rules...
// default rule
return a.Name.CompareTo(b.Name);
});
Related
I have a custom class called PairString
public class PairString: IComparer<PairString>
{
public string first;
public string second;
public PairString(string f, string s)
{
first = f;
second = s;
}
public int Compare([AllowNull] PairString x, [AllowNull] PairString y)
{
if (x == null || y == null) return -1;
var f = string.Compare(x.first, y.first);
var s = string.Compare(x.second, y.second);
return f == s ? s : f;
}
}
I want to create groups by count and then by lexical order of strings in that groups, from a list of input PairString List. Below method does the grouping right. But when I try to sort the groups in lexical order for equal count groups, it throws "Atleast one object must implement IComparer error"
public static List<string> MaxItemAssociatoinGroup(List<PairString> input)
{
if (input == null || input.Count == 0) return null;
List<SortedSet<string>> output = new List<SortedSet<string>>();
foreach (var item in input)
{
if (output.Any(x => x.Contains(item.first) || x.Contains(item.second)))
{
//Take the set containing one or two or both items
var set1 = output.FirstOrDefault(x => x.Contains(item.first));
var set2 = output.FirstOrDefault(x => x.Contains(item.second));
if (set1 == null)
set2.UnionWith(new SortedSet<string> { item.first, item.second });
else if (set2 == null)
set1.UnionWith(new SortedSet<string> { item.first, item.second });
else if (set1 != set2)
{
set1.UnionWith(set2);
output.Remove(set2);
}
}
else
output.Add(new SortedSet<string>(new List<string>() { item.first, item.second }));
}
var maxlistAssociation = output.OrderByDescending(x => x.Count).First();
return new List<string>(maxlistAssociation);
}
I am not sure how to achieve lexical order for same count groups,
Sample input is
new PairString("item3","item4"),
new PairString("item3","item6"),
new PairString("item5","item6"),
new PairString("item2","item8"),
new PairString("item8","item9"),
new PairString("item1","item2")
it groups into 2 groups of equal count {item3,item4,item5,item6} & {item1,item2,item8,item9} but returns {item3,item4,item5,item6} as its first in the list. but I want the second group as it contains the item that lexicographically first than first group. what am I missing here?
It appears that you're missing a method that will compare two SortedSet<string> objects and return the one which comes first lexically. One way to do this is to compare each item from one set with the corresponding one in the other set, and return the first non-equal comparison:
public class SortedSetComparer<T> : IComparer<SortedSet<T>> where T : IComparable<T>
{
public int Compare(SortedSet<T> x, SortedSet<T> y)
{
// Null checks
if (x == null) return y == null ? 0 : 1;
if (y == null) return -1;
var minCount = Math.Min(x.Count, y.Count);
// Compare each item from one set with the corresponding one in the other set
for (var i = 0; i < minCount; i++)
{
var result = x.ElementAt(i).CompareTo(y.ElementAt(i));
// Return the first non-equal result
if (result != 0) return result;
}
// If all the items were equal, return the comparison of the Count
return x.Count.CompareTo(y.Count);
}
}
Then we can order our results (after sorting by size) by passing an instance of this class to the ThenBy method:
var maxlistAssociation = output
.OrderByDescending(x => x.Count)
.ThenBy(x => x, new SortedSetComparer<string>())
.First();
Depending on the behavior you want from this method, we could also incorporate the ordering by Count into our comparison method, so that it puts the sets with the most items first, then sorts them alphabetically:
public class SortedSetComparer<T> : IComparer<SortedSet<T>> where T : IComparable<T>
{
public int Compare(SortedSet<T> x, SortedSet<T> y)
{
// Null checks
if (x == null) return y == null ? 0 : 1;
if (y == null) return -1;
// Compare the counts first, in descending order
var countComparison = x.Count.CompareTo(y.Count);
if (countComparison != 0) return countComparison * -1;
// Then compare each item from one set lecially
// with the corresponding one in the other set
return x.Select((item, index) =>
x.ElementAt(index).CompareTo(y.ElementAt(index)))
.FirstOrDefault(result => result != 0);
}
}
And now we only need one OrderBy clause:
var maxlistAssociation = output
.OrderBy(x => x, new SortedSetComparer<string>())
.First();
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
string Card = (Request.Params["Card"]);
DateTime Date = DateTime.Parse(Request.Params["Date"]);
using ( AttendanceContext db = new AttendanceContext())
{
lblEmpName.Text = db.users.Where(t => t.Card == Card).SingleOrDefault().EmployeeName;
lblDate.Text = Date.ToString("dd/MM/yyyy");
if (lblEmpName.Text == null)
{
lblEmpName.Text = "No Data";
}
if (lblDate.Text == null)
{
lblDate.Text = "No Date";
}
var firstArray = db.TimeoutJustification.Where(x => x.Date == Date && x.Card == Card && x.GeneralJustification != null).ToList();
var SecondArray = GetTimeOutData(Card, Date).OrderByDescending(t => t.Date).ToList();
///This is where i need to check both arrays
var filtered = firstArray.Except(SecondArray);
//var d = newTimeoutdata.Where(t => !newTimeoutdata.Contains(t.TimeOut.ToString())).ToList();
if(filtered!=null){
//This is where i will insert the unmatched array
db.TimeoutJustification.AddRange(filtered);
db.SaveChanges();
}
}
}
}
}
My first array contains 3 items that are identical to 3 of the items in my second array.
I need to get the 4th unmatched item from my second array only. keep in mind that I need to compare both arrays a using datetime TimeOut since date and card will always be the same in all arrays
You can use the Except method, something like this:
secondArray.Except(firstArray)
However you might also need to use a custom equality comparer for this purpose. I am not sure about your properties inside your class, you can do something like this for instance:
public class DistinctItemComparer : IEqualityComparer<yourClass>
{
public bool Equals(yourClass x, yourClass y)
{
return x.Id == y.Id &&
x.Date == y.Date &&
x.Card == y.Card;
}
public int GetHashCode(yourClass obj)
{
return obj.Id.GetHashCode() ^
obj.Date.GetHashCode() ^
obj.Card.GetHashCode();
}
}
And then using it like this:
secondArray.Except(firstArray, new DistinctItemComparer())
I want to verify that a string does not contain any duplicate characters (from a set of bad characters) in adjacent positions. Previous stack overflow answers on this subject seem to mostly be of the general form:
for(int i = 0; i < testString.Length-1; i++){
if(testString[i] == testString[i+1] && testString[i] == badChar){
//Handle rejection here
}
}
Is it possible to do this kind of verification/validation in LINQ? More generically: is it possible within LINQ to compare the value of each character in a string to the next character in a
testString.Any(c => /*test goes here*/) call?
Anytime you have a class that has Count (or equivalent) property and indexer, you can use Enumerable.Range as base for the LINQ query and perform inside an indexed access similar to the non LINQ code:
bool test = Enumerable.Range(0, testString.Length - 1).Any(i = >
testString[i] == testString[i + 1] && testString[i] == badChar)
You could use Pairwise from moreLINQ library:
if(testString.Pairwise((n, m) => new {n, m}).Any(x => x.n == x.m && x.n == badChar))
// do something
If you want to use pure LINQ you could hack it with Skip/Zip combination:
if(testString.Zip(testString.Skip(1), (n, m) => new {n, m})).Any(x => x.n == x.m && x.n == badChar))
// do something
But both these solutions will be much slower then for loop-based solution, so I'd advice against doing that.
How about the egregious misuse of the aggregate function? I like to think this answer is more of an example of what not to do, even if it is possible. A while and string.indexOf are probably the most appropriate to this problem.
var items = "ab^cdeef##gg";
var badChars = new[] {'^', '#', '~'};
var doesAdjacentDupeExist = false;
var meaninglessAggregate = items.Aggregate((last, current) =>
{
if (last == current && badChars.Contains(last))
{
doesAdjacentDupeExist = true;
};
return current;
});
This is not as clever, but it does work. It trades the setting of an outside variable inside the query (bad), for relying on index and elementAt (not great).
var items = "abcdefffghhijjk";
var badChars = new[] { 'f', 'h' };
var indexCieling = items.Count() - 1;
var badCharIndexes = items.Select((item, index) =>
{
if (index >= indexCieling)
{
return null as int?;
}
else
{
if (item == items.ElementAt(index + 1) && badChars.Contains(item))
{
return index as int?;
}
else
{
return null as int?;
}
}
});
var doesAdjacentDupeExist = badCharIndexes.Any(x => x.HasValue);
Alright, so I need to get the key value paired differences of two data rows. In short, I'm sending an email to let a user know they've made specific changes to their profile. I already know the rows are different because I'm using the SequenceEqual to determine that.
At the moment I've written and debugged the following code:
if (currentRow.ItemArray.SequenceEqual(updatedRow)) { return; }
var updates = currentRow.ItemArray
.Where((o, i) =>
{
if (o == null && updatedRow[i] == null) { return false; }
else if (o == null && updatedRow[i] != null) { return true; }
else if (o.Equals(updatedRow[i])) { return false; }
return true;
})
.Select((o, i) =>
{
return new AppServices.NotificationData
{
Key = updatedRow.Table.Columns[i].ColumnName,
Value = Convert.ToString(updatedRow[i])
};
}).ToList();
But there are two problems with this code:
It seems really inefficient to me because it's going through each value in the ItemArray and then building a key value pair if the values differ.
It doesn't actually work because the i sent into the Select isn't correct (e.g. if the second column changed, 1, the index sent into the Select is actually 0. Honestly, that makes sense, but I'm not sure exactly how to get what I want here.
CONSTRAINT: I'd like to use LINQ here.
NOTE: I'm only comparing two rows (i.e. it's not going to be going through a list of rows).
What is the appropriate LINQ statement for what I'm trying to do here?
UPDATE: It really feels like I just need to use:
currentRow.ItemArray.Intersect(updatedRow.ItemArray)
but the problem with that is I don't have any idea what field that is so I can't build a key value pair. In other words, I get back only the differences, but I've no clue what the index is so I can't go get a column name based off of those values.
Honestly you're not going to lose much code clarity by using a for loop.
public IEnumerable<AppServices.NotificationData> GetUpdates(DataRow currentRow, DataRow updatedRow)
{
if (currentRow.ItemArray.SequenceEqual(updatedRow)) yield break;
var length = currentRow.ItemArray.Length;
for(var i = 0; i < length; i++)
{
var currentCol = currentRow[i];
var updatedCol = updatedRow[i];
if (currentCol == null && updatedCol == null) continue;
else if (currentCol == null && updatedCol != null) continue;
else if (currentCol.Equals(updatedCol)) continue;
yield return new AppServices.NotificationData
{
Key = updatedRow.Table.Columns[i].ColumnName,
Value = Convert.ToString(updatedCol)
};
}
}
var updates = currentRow.ItemArray
.Select((o, i) => new { Row = o, Index = i })
.Where(r => (r.Row == null && updatedRow[r.Index] != null)
|| (r.Row != null && updatedRow[r.Index] != null
&& !r.Row.Equals(updatedRow[r.Index])))
.Select(r => new
{
Key = updatedRow.Table.Columns[r.Index].ColumnName,
Value = Convert.ToString(updatedRow[r.Index])
}).ToList();
In general, I consider using array index values in LINQ to be a "code smell", and this is a good example of why: the Where clause, in generating a new sequence of values, destroys the illusion that the Select clause is working on the same collection as before.
A quick hack to get around this right now (though I don't think it is quite yet the right solution), would be to swap your Where and Select clauses, essentially:
if (currentRow.ItemArray.SequenceEqual(updatedRow)) { return; }
var updates = currentRow.ItemArray
.Select((o, i) =>
{
if (o == null && updatedRow[i] == null || o.Equals(updatedRow[i])) { return null; }
else return new AppServices.NotificationData
{
Key = updatedRow.Table.Columns[i].ColumnName,
Value = Convert.ToString(updatedRow[i])
};
}).Where(o => o != null).ToList();
I have collection of items which are having one enum property list.
Original property looks like
public class Content {
List<State> States {get; set;}
}
where 'State' is enum with almost 15 options.
While iterating collection of Content objects, I want to check it States property has certain values like State.Important and State.Updated exists in States and set another string from it.
something like
if(item.States.Has(State.Important) && item.States.Has(State.Updated))
string toProcess = "Do";
How to do this using Linq or Lambda ?
This should work if you must use Linq:
if (item.States.Any(state => state == State.Important) && item.States.Any(state => state == State.Updated))
Otherwise just use Contains() like #ElRonnoco says.
(However if your states are flags (powers of 2), then this answer will be slightly different.)
The trouble with this approach is that it iterates over the collection fully twice if neither of the states are set. If this happens often, it will be slower than it could be.
You can solve it without linq in a single pass like so:
bool isUpdated = false;
bool isImportant = false;
foreach (var state in item.States)
{
if (state == State.Important)
isImportant = true;
else if (state == State.Updated)
isUpdated = true;
if (isImportant && isUpdated)
break;
}
if (isImportant && isUpdated)
{
// ...
}
This is unlikely to be an issue unless you have very large lists which often don't have either of the target states set, so you're probably best off using El Ronnoco's solution anyway.
If you have a lot of states to deal with, you could simplify things by writing an extension method like so:
public static class EnumerableExt
{
public static bool AllPredicatesTrueOverall<T>(this IEnumerable<T> self, params Predicate<T>[] predicates)
{
bool[] results = new bool[predicates.Length];
foreach (var item in self)
{
for (int i = 0; i < predicates.Length; ++i)
if (predicates[i](item))
results[i] = true;
if (results.All(state => state))
return true;
}
return false;
}
I had some difficulty coming up for a name for this. It will return true if for each predicate there is at least one item in the sequence for which the predicate is true. But that's a bit long for a method name... ;)
Then your example would become:
if (item.States.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))
Here's some sample code that uses it:
enum State
{
Unknown,
Important,
Updated,
Deleted,
Other
}
void run()
{
IEnumerable<State> test1 = new[]
{
State.Important,
State.Updated,
State.Other,
State.Unknown
};
if (test1.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))
Console.WriteLine("test1 passes.");
else
Console.WriteLine("test1 fails.");
IEnumerable<State> test2 = new[]
{
State.Important,
State.Other,
State.Other,
State.Unknown
};
if (test2.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))
Console.WriteLine("test2 passes.");
else
Console.WriteLine("test2 fails.");
// And to show how you can use any number of predicates:
bool result = test1.AllPredicatesTrueOverall
(
state => state == State.Important,
state => state == State.Updated,
state => state == State.Other,
state => state == State.Deleted
);
}
But perhaps the easiest is to write an extension method for IEnumerable<State> (if you only have the one state enum to worry about):
public static class EnumerableStateExt
{
public static bool AllStatesSet(this IEnumerable<State> self, params State[] states)
{
bool[] results = new bool[states.Length];
foreach (var item in self)
{
for (int i = 0; i < states.Length; ++i)
if (item == states[i])
results[i] = true;
if (results.All(state => state))
return true;
}
return false;
}
}
Then your original code will become:
if (item.States.AllStatesSet(State.Important, State.Updated))
and you can easily specify more states:
if (item.States.AllStatesSet(State.Important, State.Updated, State.Deleted))
You don't need Linq. I don't thinq
if(item.States.Contains(State.Important) && item.States.Contains(State.Updated))
string toProcess = "Do";
http://msdn.microsoft.com/en-us/library/bhkz42b3.aspx
List has a Contains method, so your code would be
if(item.States.Contains(State.Important) && item.States.Contains(State.Updated))
string toProcess = "Do";
I see no real benefit in using Linq or a lambda expression here...
You could go with
!(new List<States>{State.Important, State.Updated}.Except(item.States).Any());
It's not really shorter, but easier if you have a huge number of states to check.
As long as you want to check that the item has all states needed, you just have to add new States to the first list.
var res = (from items in item
where items.States.Has(State.Important) && items.States.Has(State.Updated)
select new { NewProcess = "Do" }).ToList();
foreach (var result in res)
{
string result = result.NewProcess
}
Try this
Maybe you could consider using your enum as a set of flags, i.e. you can combine multiple states without having a list:
[Flags]
public enum State
{
Important = 1,
Updated = 2,
Deleted = 4,
XXX = 8
....
}
public class Content
{
public State MyState { get; set; }
}
if ((myContent.MyState & State.Important) == State.Important
&& (myContent.MyState & State.Updated) == State.Updated)
{
// Important AND updated
}
Some sort of following implementation
Content obj = new Content();
obj.States = SomeMethod();
if(obj.States.Any(h => h == State.Important) && obj.States.Any(h => h == State.Updated))
{
string toProcess = "Do";
}