I have a collection
private ObservableCollection<ContentItemViewModel> _contentTree;
public ObservableCollection<ContentItemViewModel> ContentTree
{
get { return _contentTree; }
}
class ContentItemViewModel has property:
private string _published;
public string Published
{
get
{
return _published;
}
set
{
_published = value;
NotifyPropertyChanged("Published");
}
}
that is - node.Published= Convert.ToDateTime(date.Value).ToString("dd MMM yyyy", new DateTimeFormatInfo());
I need to sort ContentTree collection by date? how can I do this?
I do not know if there is any better way but you can do the following. The idea here is to create an ordered list and first foreach item in the ordered list, removing and re-adding the related item from content tree.
var tempList = _contentTree.OrderBy(p => DateTime.Parse(p.DateAndTime));
tempList.ToList().ForEach(q =>
{
_contentTree.Remove(q);
_contentTree.Add(q);
});
Or you can employ Comparison;
Comparison<ContentItemViewModel> comparison = new Comparison<ContentItemViewModel>(
(p,q) =>
{
DateTime first = DateTime.Parse(p.DateAndTime);
DateTime second = DateTime.Parse(q.DateAndTime);
if (first == second)
return 0;
if (first > second)
return 1;
return -1;
});
List<ContentItemViewModel> tempList = _contentTree.ToList();
tempList.Sort(comparison);
_contentTree = new ObservableCollection<ContentItemViewModel>(tempList);
you can use this:
ConceptItems = new ObservableCollection<DataConcept>(ConceptItems.OrderBy(i => i.DateColumn));
Change it to list ToList();
Sort it using OrderBy or...
Make a new ObservableCollection using that list.
Related
I have a simple class Item:
public class Item
{
public int Start { get; set;}
public int Stop { get; set;}
}
Given a List<Item> I want to split this into multiple sublists of contiguous elements. e.g. a method
List<Item[]> GetContiguousSequences(Item[] items)
Each element of the returned list should be an array of Item such that list[i].Stop == list[i+1].Start for each element
e.g.
{[1,10], [10,11], [11,20], [25,30], [31,40], [40,45], [45,100]}
=>
{{[1,10], [10,11], [11,20]}, {[25,30]}, {[31,40],[40,45],[45,100]}}
Here is a simple (and not guaranteed bug-free) implementation that simply walks the input data looking for discontinuities:
List<Item[]> GetContiguousSequences(Item []items)
{
var ret = new List<Item[]>();
var i1 = 0;
for(var i2=1;i2<items.Length;++i2)
{
//discontinuity
if(items[i2-1].Stop != items[i2].Start)
{
var num = i2 - i1;
ret.Add(items.Skip(i1).Take(num).ToArray());
i1 = i2;
}
}
//end of array
ret.Add(items.Skip(i1).Take(items.Length-i1).ToArray());
return ret;
}
It's not the most intuitive implementation and I wonder if there is a way to have a neater LINQ-based approach. I was looking at Take and TakeWhile thinking to find the indices where discontinuities occur but couldn't see an easy way to do this.
Is there a simple way to use IEnumerable LINQ algorithms to do this in a more descriptive (not necessarily performant) way?
I set of a simple test-case here: https://dotnetfiddle.net/wrIa2J
I'm really not sure this is much better than your original, but for the purpose of another solution the general process is
Use Select to project a list working out a grouping
Use GroupBy to group by the above
Use Select again to project the grouped items to an array of Item
Use ToList to project the result to a list
public static List<Item[]> GetContiguousSequences2(Item []items)
{
var currIdx = 1;
return items.Select( (item,index) => new {
item = item,
index = index == 0 || items[index-1].Stop == item.Start ? currIdx : ++currIdx
})
.GroupBy(x => x.index, x => x.item)
.Select(x => x.ToArray())
.ToList();
}
Live example: https://dotnetfiddle.net/mBfHru
Another way is to do an aggregation using Aggregate. This means maintaining a final Result list and a Curr list where you can aggregate your sequences, adding them to the Result list as you find discontinuities. This method looks a little closer to your original
public static List<Item[]> GetContiguousSequences3(Item []items)
{
var res = items.Aggregate(new {Result = new List<Item[]>(), Curr = new List<Item>()}, (agg, item) => {
if(!agg.Curr.Any() || agg.Curr.Last().Stop == item.Start) {
agg.Curr.Add(item);
} else {
agg.Result.Add(agg.Curr.ToArray());
agg.Curr.Clear();
agg.Curr.Add(item);
}
return agg;
});
res.Result.Add(res.Curr.ToArray()); // Remember to add the last group
return res.Result;
}
Live example: https://dotnetfiddle.net/HL0VyJ
You can implement ContiguousSplit as a corutine: let's loop over source and either add item into current range or return it and start a new one.
private static IEnumerable<Item[]> ContiguousSplit(IEnumerable<Item> source) {
List<Item> current = new List<Item>();
foreach (var item in source) {
if (current.Count > 0 && current[current.Count - 1].Stop != item.Start) {
yield return current.ToArray();
current.Clear();
}
current.Add(item);
}
if (current.Count > 0)
yield return current.ToArray();
}
then if you want materialization
List<Item[]> GetContiguousSequences(Item []items) => ContiguousSplit(items).ToList();
Your solution is okay. I don't think that LINQ adds any simplification or clarity in this situation. Here is a fast solution that I find intuitive:
static List<Item[]> GetContiguousSequences(Item[] items)
{
var result = new List<Item[]>();
int start = 0;
while (start < items.Length) {
int end = start + 1;
while (end < items.Length && items[end].Start == items[end - 1].Stop) {
end++;
}
int len = end - start;
var a = new Item[len];
Array.Copy(items, start, a, 0, len);
result.Add(a);
start = end;
}
return result;
}
I've got a problem with removing duplicates at runtime from my list of object.
I would like to remove duplicates from my list of object and then set counter=counter+1 of base object.
public class MyObject
{
MyObject(string name)
{
this.counter = 0;
this.name = name;
}
public string name;
public int counter;
}
List<MyObject> objects_list = new List<MyObject>();
objects_list.Add(new MyObject("john"));
objects_list.Add(new MyObject("anna"));
objects_list.Add(new MyObject("john"));
foreach (MyObject my_object in objects_list)
{
foreach (MyObject my_second_object in objects_list)
{
if (my_object.name == my_second_object.name)
{
my_object.counter = my_object.counter + 1;
objects_list.remove(my_second_object);
}
}
}
It return an error, because objects_list is modified at runtime. How can I get this working?
With a help of Linq GroupBy we can combine duplicates in a single group and process it (i.e. return an item which represents all the duplicates):
List<MyObject> objects_list = ...
objects_list = objects_list
.GroupBy(item => item.name)
.Select(group => { // given a group of duplicates we
var item = group.First(); // - take the 1st item
item.counter = group.Sum(g => g.counter); // - update its counter
return item; // - and return it instead of group
})
.ToList();
The other answer seem to be correct, though I think it will do scan of the whole list twice, depending on your requirement this might or might not be good enough. Here is how you can do it in one go:
var dictionary = new Dictionary<string, MyObject>();
foreach(var obj in objects_list)
{
if(!dictionary.ContainsKey(obj.name)
{
dictionary[obj.name] = obj;
obj.counter++;
}
else
{
dictionary[obj.name].counter++;
}
}
Then dictionary.Values will contain your collection
I have a list of objects with two properties (Start and End). I need to be able to take items whose times fall within a variable (config option) tolerance level and combine them.
Example:
Tolerance: 1 hour
Item A: Start = 1pm, End = 2pm
Item B: Start 2:30pm, End = 4pm
Since the tolerance is one hour, I need to be able to 'combine' these two into a single timespan or other like object with, in this example, the following stats:
Start = 1pm, End 4pm
A sample class that I am using for testing follows. The production class has two like properties, along with several others.
public class TimeTest
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
I guess my confusion point is if there is an elegant way of doing this in LINQ. I'm still wrestling with how to compare a list item to another list item and iterate through the list that way.
When you want to iterate a sequence and use some criteria to sometimes combine consecutive items into a single item you can use an iterator block to keep state about the "previous" item while iterating:
static class EnumerableExtensions
{
public static IEnumerable<Item> Combine(this IEnumerable<Item> items)
{
using (var enumerator = items.GetEnumerator())
{
if (!enumerator.MoveNext())
yield break;
var previous = enumerator.Current;
while (enumerator.MoveNext())
{
var next = enumerator.Current;
if (TryCombine(previous, next, out var combined))
{
previous = combined;
continue;
}
yield return previous;
previous = next;
}
yield return previous;
}
}
}
You will have to implement TryCombine to apply your logic. Based on your requirements something like this should work for you:
private static bool TryCombine(Item item1, Item item2, out Item combinedItem)
{
if (item2.Start - item1.End > TimeSpan.FromHours(1))
{
combinedItem = default;
return false;
}
combinedItem = new Item { Start = item1.Start, End = item2.End };
return true;
}
Since this method is an extension method you can use it like this:
var combinedItems = items.Combine();
For more flexibility you could provide the 1 hour threshold as a TimeSpan parameter to the method and also perhaps use some more descriptive names instead of Item and Combine that makes more sense in your domain.
It sounds to me like a simple Min/Max situation.
List<TimeTest> times = new List<TimeTest>();
//... Fill list
if(times.Count == 0) return;
int min = Int32.MaxValue;
int max = Int32.MinValue;
foreach(var item in times)
{
min = Math.Min(item.Start.Hour, min);
max = Math.Max(item.End.Hour, max);
}
Then initialize a 1pm - 4pm construct as you see fit as min and max will contain your start and end times.
Another way to achieve this, using Aggregate.
For Input
var list = new List<Interval>
{
new Interval{Start=new DateTime(2019,1,1,13,0,0), End=new DateTime(2019,1,1,14,0,0)},
new Interval{Start=new DateTime(2019,1,1,14,30,0), End=new DateTime(2019,1,1,16,0,0)},
new Interval{Start=new DateTime(2019,1,1,16,0,0), End=new DateTime(2019,1,1,17,0,0)},
new Interval{Start=new DateTime(2019,1,1,19,0,0), End=new DateTime(2019,1,1,20,0,0)},
new Interval{Start=new DateTime(2019,1,1,22,0,0), End=new DateTime(2019,1,1,23,0,0)},
new Interval{Start=new DateTime(2019,1,1,23,40,0), End=new DateTime(2019,2,1,2,0,0)},
};
Where Interval is defined as
public class Interval
{
public DateTime Start{get;set;}
public DateTime End{get;set;}
}
You can
var result = MergeAndList(list);
Where MergeAndList is defined as
IEnumerable<Interval> MergeAndList(IEnumerable<Interval> intervals)
{
var ret = new List<Interval>(intervals);
int lastCount=0;
do
{
lastCount = ret.Count;
ret = ret.Aggregate(new List<Interval>(),(agg, cur) =>
{
for (int i = 0; i < agg.Count; i++)
{
var a = agg[i];
if(a.End.AddHours(1) >= cur.Start)
{
agg[i] = new Interval{Start=a.Start, End=cur.End};
return agg;
}
else
{
agg[i] = new Interval{Start=a.Start, End=a.End};
}
}
agg.Add(cur);
return agg;
});
} while (ret.Count != lastCount);
return ret;
}
Output
Example in Fiddle
I have a class as follows :
Object1{
int id;
DateTime time;
}
I have a list of Object1. I want to cycle through another list of Object1, search for an Object1 with the same ID and replace it in the first list if the time value is later than the time value in the list. If the item is not in the first list, then add it.
I'm sure there is an elegant way to do this, perhaps using linq? :
List<Object1> listOfNewestItems = new List<Object1>();
List<Object1> listToCycleThrough = MethodToReturnList();
foreach(Object1 object in listToCycleThrough){
if(listOfNewestItems.Contains(//object1 with same id as object))
{
//check date, replace if time property is > existing time property
} else {
listOfNewestItems.Add(object)
}
Obviously this is very messy (and that's without even doing the check of properties which is messier again...), is there a cleaner way to do this?
var finalList = list1.Concat(list2)
.GroupBy(x => x.id)
.Select(x => x.OrderByDescending(y=>y.time).First())
.ToList();
here is the full code to test
public class Object1
{
public int id;
public DateTime time;
}
List<Object1> list1 = new List<Object1>()
{
new Object1(){id=1,time=new DateTime(1991,1,1)},
new Object1(){id=2,time=new DateTime(1992,1,1)}
};
List<Object1> list2 = new List<Object1>()
{
new Object1(){id=1,time=new DateTime(2001,1,1)},
new Object1(){id=3,time=new DateTime(1993,1,1)}
};
and OUTPUT:
1 01.01.2001
2 01.01.1992
3 01.01.1993
This is how to check:
foreach(var object in listToCycleThrough)
{
var currentObject = listOfNewestItems
.SingleOrDefault(obj => obj.Id == object.Id);
if(currentObject != null)
{
if (currentObject.Time < object.Time)
currentObject.Time = object.Time
}
else
listOfNewestItems.Add(object)
}
But if you have large data, would be suggested to use Dictionary in newest list, time to look up will be O(1) instead of O(n)
You can use LINQ. Enumerable.Except to get the set difference(the newest), and join to find the newer objects.
var listOfNewestIDs = listOfNewestItems.Select(o => o.id);
var listToCycleIDs = listToCycleThrough.Select(o => o.id);
var newestIDs = listOfNewestIDs.Except(listToCycleIDs);
var newestObjects = from obj in listOfNewestItems
join objID in newestIDs on obj.id equals objID
select obj;
var updateObjects = from newObj in listOfNewestItems
join oldObj in listToCycleThrough on newObj.id equals oldObj.id
where newObj.time > oldObj.time
select new { oldObj, newObj };
foreach (var updObject in updateObjects)
updObject.oldObj.time = updObject.newObj.time;
listToCycleThrough.AddRange(newestObjects);
Note that you need to add using System.Linq;.
Here's a demo: http://ideone.com/2ASli
I'd create a Dictionary to lookup the index for an Id and use that
var newItems = new List<Object1> { ...
IList<Object1> itemsToUpdate = ...
var lookup = itemsToUpdate.
Select((i, o) => new { Key = o.id, Value = i }).
ToDictionary(i => i.Key, i => i.Value);
foreach (var newItem in newitems)
{
if (lookup.ContainsKey(newitem.ID))
{
var i = lookup[newItem.Id];
if (newItem.time > itemsToUpdate[i].time)
{
itemsToUpdate[i] = newItem;
}
}
else
{
itemsToUpdate.Add(newItem)
}
}
That way, you wouldn't need to reenumerate the list for each new item, you'd benefit for the hash lookup performance.
This should work however many times an Id is repeated in the list of new items.
I am using List in C#. Code is as mentioned below:
TestCase.cs
public class TestCase
{
private string scenarioID;
private string error;
public string ScenarioID
{
get
{
return this.scenarioID;
}
set
{
this.scenarioID = value;
}
}
public string Error
{
get
{
return this.error;
}
set
{
this.error = value;
}
}
public TestCase(string arg_scenarioName, string arg_error)
{
this.ScenarioID = arg_scenarioName;
this.Error = arg_error;
}
}
List I am createing is:
private List<TestCase> GetTestCases()
{
List<TestCase> scenarios = new List<TestCase>();
TestCase scenario1 = new TestCase("Scenario1", string.Empty);
TestCase scenario2 = new TestCase("Scenario2", string.Empty);
TestCase scenario3 = new TestCase("Scenario1", string.Empty);
TestCase scenario4 = new TestCase("Scenario4", string.Empty);
TestCase scenario5 = new TestCase("Scenario1", string.Empty);
TestCase scenario6 = new TestCase("Scenario6", string.Empty);
TestCase scenario7 = new TestCase("Scenario7", string.Empty);
scenarios.Add(scenario1);
scenarios.Add(scenario2);
scenarios.Add(scenario3);
scenarios.Add(scenario4);
scenarios.Add(scenario5);
scenarios.Add(scenario6);
scenarios.Add(scenario7);
return scenarios;
}
Now I am iterating through the list. I want to find the how many duplicate testcases are there in a list with same ScenarioID. Is there any way to solve it using Linq or any inbuilt method for List?
Regards,
Priyank
Try this:
var numberOfTestcasesWithDuplicates =
scenarios.GroupBy(x => x.ScenarioID).Count(x => x.Count() > 1);
As a first idea:
int dupes = list.Count() - list.Distinct(aTestCaseComparer).Count();
To just get the duplicate count:
int duplicateCount = scenarios.GroupBy(x => x.ScenarioID)
.Sum(g => g.Count()-1);
var groups = scenarios.GroupBy(test => test.ScenarioID)
.Where(group => group.Skip(1).Any());
That will give you a group for each ScenarioID that has more than one items. The count of the groups is the number of duplicate groups, and the count of each group internally is the number of duplicates of that single item.
Additional note, the .Skip(1).Any() is there because a .Count() in the Where clause would need to iterate every single item just to find out that there is more than one.
Something like this maybe
var result= GetTestCases()
.GroupBy (x =>x.ScenarioID)
.Select (x =>new{x.Key,nbrof=x.Count ()} );
To get total number of duplicates, yet another:
var set = new HashSet<string>();
var result = scenarios.Count(x => !set.Add(x.ScenarioID));
To get distinct duplicates:
var result = scenarios.GroupBy(x => x.ScenarioID).Count(x => x.Skip(1).Any());