Related
I have incoming objects of the same type, but if An Object property IsThrottlable is set to false regardless of the ID I DON'T want to throttle it but if IsThrottlable is set to true I would like to throttle the object every 3 seconds by ID. So if an object with the same ID comes in 50 times with 3 seconds I would like to send the HTTPSend for the last Object.
namespace BoatThrottle
{
class MData
{
public int ID { get; set; }
public bool IsThrottlable { get; set; }
public string Description { get; set; }
}
class Program
{
static void Main(string[] args)
{
Random rand = new Random();
while (true)
{
var data = GenerateRandomObj(rand);
SendData(data);
Task.Delay(rand.Next(100, 2000));
}
}
static MData GenerateRandomObj(Random rand)
{
return new MData() { ID = rand.Next(1, 20), Description = "Notification....", IsThrottlable = (rand.Next(2) == 1) };
}
static void SendData(MData mData)
{
if (mData.IsThrottlable)
{
_doValues.OnNext(mData);
var dd = ThrottledById(DoValues);
var observable =
dd
.Throttle(TimeSpan.FromMilliseconds(3000.0))
.ObserveOn(Scheduler.ThreadPool.DisableOptimizations());
_subscription =
observable
.ObserveOn(Scheduler.ThreadPool.DisableOptimizations())
.Subscribe(y =>
{
HTTPSend(y);
});
}
else
{
// MData object coming in IsThrottlable set to false always send this data NO throttling
HTTPSend(mData);
}
}
private static IDisposable? _subscription = null;
public static IObservable<MData> ThrottledById(IObservable<MData> observable)
{
return observable.Buffer(TimeSpan.FromSeconds(3))
.SelectMany(x =>
x.GroupBy(y => y.ID)
.Select(y => y.Last()));
}
private static readonly Subject<MData> _doValues = new Subject<MData>();
public static IObservable<MData> DoValues { get { return _doValues; } }
static void HTTPSend(MData mData)
{
Console.WriteLine("===============HTTP===>> " + mData.ID + " " + mData.Description + " " + mData.IsThrottlable);
}
}
}
EDIT:
e.g ALL received within 3 seconds
MData ID = 1, IsThrottlable = False, Description = "Notify"
MData ID = 2, IsThrottlable = True, Description = "Notify1"
MData ID = 2, IsThrottlable = True, Description = "Notify2"
MData ID = 9, IsThrottlable = False, Description = "Notify2"
MData ID = 2, IsThrottlable = True, Description = "Notify3"
MData ID = 2, IsThrottlable = True, Description = "Notify4"
MData ID = 3, IsThrottlable = True, Description = "Notify"
MData ID = 4, IsThrottlable = True, Description = "Notify"
MData ID = 5, IsThrottlable = True, Description = "Notify1"
MData ID = 5, IsThrottlable = True, Description = "Notify2"
MData ID = 8, IsThrottlable = True, Description = "Notify1"
MData ID = 8, IsThrottlable = True, Description = "Notify2"
MData ID = 8, IsThrottlable = True, Description = "Notify3"
MData ID = 8, IsThrottlable = True, Description = "Notify4"
MData ID = 8, IsThrottlable = True, Description = "Notify5"
MData ID = 8, IsThrottlable = True, Description = "Notify6"
Expected at the First 3 seconds:
MData ID = 1, IsThrottlable = False, Description = "Notify"
MData ID = 9, IsThrottlable = False, Description = "Notify2"
MData ID = 2, IsThrottlable = True, Description = "Notify4"
MData ID = 3, IsThrottlable = True, Description = "Notify"
MData ID = 4, IsThrottlable = True, Description = "Notify"
MData ID = 5, IsThrottlable = True, Description = "Notify2"
MData ID = 8, IsThrottlable = True, Description = "Notify6"
I decided to take your final implementation, as posted in your question, but it should be as an answer, and clean up the query for you in a way that is the most idiomatic Rx kind of way.
Here's my version of your code:
public MainWindow()
{
InitializeComponent();
Debug.Print("========================");
_subscription =
Observable
.Generate(0, x => true, x => x + 1,
x => new MData() { ID = Random.Shared.Next(1, 3), Description = "Notification....", IsThrottlable = Random.Shared.Next(2) == 1 },
x => TimeSpan.FromMilliseconds(Random.Shared.Next(100, 2000)))
.GroupBy(m => m.IsThrottlable)
.SelectMany(g =>
g.Key
? g.GroupBy(x => x.ID).SelectMany(g2 => g2.Throttle(TimeSpan.FromSeconds(3.0)))
: g)
.SelectMany(m => Observable.Start(() => HTTPSend(m)))
.Subscribe();
}
The final .SelectMany(m => Observable.Start(() => HTTPSend(m))) might need to be written as .Select(m => Observable.Start(() => HTTPSend(m))).Merge(1).
One way to do it is to group the sequence by the IsThrottlable property. This way you'll get a nested sequence that contains two subsequences, one containing the throttleable elements and one containing the non-throttleable elements. You can then transform each of the two subsequences accordingly, and finally use the SelectMany operator to flatten the nested sequence back to a flat sequence that contains the elements emitted by the two transformed subsequences.
The subsequence that contains the non-throttleable elements needs no transformation, so you can return it as is.
The subsequence that contains the throttleable elements needs to be grouped further by the ID property, producing even thinner subsequences that contain only throttleable elements having the same id. These are the sequences that need to be throttled:
IObservable<MData> throttled = source
.GroupBy(x => x.IsThrottlable)
.SelectMany(g1 =>
{
if (!g1.Key) return g1; // Not throttleable, return it as is.
return g1
.GroupBy(x => x.ID)
.SelectMany(g2 => g2.Throttle(TimeSpan.FromSeconds(3)));
});
At the end you'll get a flat sequence that contains both the throttleable and the non-throttleable items, with the throttleable items already throttled by id.
The SelectMany operator is essentially a combination of the Select+Merge operators.
I have an list of objects => class Example { int quantity; string name; string comment; } and I want to remove all duplicates and increase the quantity by the number of duplicates that have the same name and comment.
Example:
[
{quantity: 1, name: "Hello", comment: "Hello there"},
{quantity: 2, name: "Bye", comment: "Good bye"},
{quantity: 1, name: "Hi", comment: "Hi there"},
{quantity: 1, name: "Hello", comment: "Hello there"},
{quantity: 1, name: "Bye", comment: "Good bye"},
]
and the result it should be:
[
{quantity: 2, name: "Hello", comment: "Hello there"},
{quantity: 3, name: "Bye", comment: "Good bye"},
{quantity: 1, name: "Hi", comment: "Hi there"}
]
I want to remove all duplicates
You haven't defined when two example objects are "duplicates". I guess, that you mean to say, that if two Examples objects have the same values for properties Name and Comment, then they are duplicates.
Normally you can use one of the overloads of Enumerable.GroupBy to find Duplicates. Use the overload with a parameter resultSelector to define exactly what you want as result.
IEnumerable<Example> examples = ...
var result = examples.GroupBy(
// key: Name-Comment
example => new
{
Name = example.Name,
Comment = example.Comment,
}
// parameter resultSelector: for every Name/Comment combination and all
// Examples with this Name/Comment combination make one new example:
(nameCommentCombination, examplesWithThisNameCommentCombination) => new Example
{
Name = nameCommentCombination.Name,
Comment = nameCommentCombination.Comment,
// Quantity is the sum of all Quantities of all Examples with this
// Name/Comment combination
Quantity = examplesWithThisNameCommentCombination
.Select(example => example.Quantity)
.Sum(),
});
This will only work if you want exact string equality. Are "Hello" and "HELLO" equal? And how about "Déjà vu" and "Deja vu"? Do you want case insensitivity for Name and Comment? And how about diacritical characters?
If you want more than just plain string equality, consider to create an ExampleComparer class.
class ExampleComparer : EqualityComparer<Example>
{
... // TODO: implement
}
Usage would be:
IEnumerable<Example> examples = ...
IEqualityComparer<Example> comparer = ...
var result = examples.GroupBy(example => example, // key
// resultSelector:
(key, examplesWithThisKey) => new Example
{
Name = key.Name,
Comment = key.Comment,
Quantity = examplesWithThiskey.Sum(example => example.Quantity),
},
comparer);
Implement the ExampleComparer
class ExampleComparer : EqualityComparer<Example>
{
public static IEqualityComparer<Example> ByNameComment {get;} = new ExampleComparer;
private static IEqualityComparer<string> NameComparer => StringComparer.CurrentCultureIgnoreCase;
private static IEqualityComparer<string> CommentComparer => StringComparer.CurrentCultureIgnoreCase;
I chose for two separate string comparers, so if later you decide different comparison, for instance Name has to match exactly, then you only have to change it here.
public override bool Equals (Example x, Example y)
{
// almost every Equals method starts with the following three lines
if (x == null) return y == null; // true if both null
if (y == null) return false; // false, because x not null
if (Object.ReferenceEquals(x, y)) return true; // same object
// return true if both examples are considered equal:
return NameComparer.Equals(x.Name, y.Name)
&& CommentComparer.Equals(x.Comment, y.Comment);
}
public override int GetHashCode(Example x)
{
if (x == null) return 5447125; // just a number
return NameComparer.GetHashCode(x.Name)
^ CommentComparer.GetHashCode(x.Comment);
}
Note: this will also work if Name or Comment are null or empty!
I used operator ^ (XOR), because that gives a fairly good hash if there are only two fields to consider. If you think that the vast majority of Examples have unique names, consider to check only property Name:
return NameComparer.GetHashCode(x.Name);
Because method Equals uses the NameComparer and CommentComparer to check for equality, make sure you use the same Comparers to calculate the HashCode.
Here's what I'd do:
Example[] before = new Example[]
{
new Example { Quantity = 1, Name = "Hello", Comment = "Hello there" },
new Example { Quantity = 2, Name = "Bye", Comment = "Good bye" },
new Example { Quantity = 1, Name = "Hi", Comment = "Hi there" },
new Example { Quantity = 1, Name = "Hello", Comment = "Hello there" },
new Example { Quantity = 1, Name = "Bye", Comment = "Good bye" },
};
Example[] after =
before
.GroupBy(x => new { x.Name, x.Comment }, x => x.Quantity)
.Select(x => new Example { Quantity = x.Sum(), Name = x.Key.Name, Comment = x.Key.Comment })
.ToArray();
That gives:
Perhaps this version is a little clearer:
Example[] after =
before
.GroupBy(
x => new { x.Name, x.Comment },
(k, xs) => new Example
{
Quantity = xs.Sum(x => x.Quantity),
Name = k.Name,
Comment = k.Comment
})
.ToArray();
Or perhaps this:
Example[] after =
(
from x in before
group x.Quantity by new { x.Name, x.Comment } into xs
select new Example
{
Quantity = xs.Sum(x => x),
Name = xs.Key.Name,
Comment = xs.Key.Comment
}
).ToArray();
Here's a simple solution, which gives you the answer in the List tata but you can do .ToArray() if you wish.
public class Example
{
public int quantity;
public string name;
public string comment;
}
Example[] toto = new Example[]
{
new Example
{
quantity = 1,
name = "Hello",
comment = "Hello there"
},
new Example
{
quantity = 2,
name = "Bye",
comment = "Good bye"
},
new Example
{
quantity = 1,
name = "Hi",
comment = "Hi there"
},
new Example
{
quantity = 1,
name = "Hello",
comment = "Hello there"
},
new Example
{
quantity = 1,
name = "Bye",
comment = "Good bye"
}
};
List<Example> tata = new List<Example>();
foreach (Example exa in toto)
{
bool found = false;
foreach (Example exb in tata)
{
if (exb.name == exa.name && exb.comment == exa.comment)
{
exb.quantity += exa.quantity;
found = true;
break;
}
}
if (!found)
{
tata.Add(exa);
}
}
A good exercise would be to LINQ that!
I have a strange question :)
I have a object list looking like this:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
I would like to sort the list providing a "start id"
For example, if I provide "start id" 3, the result should look like this:
Id
Name
3
Patric
4
Theodor
1
Marcus
2
Mattias
I have no idea where to start, so I really need some help from you coding gods
The list is from a sql table, but it does not matter for me where the sort take place (in sql query or in c# code)
Try this:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
var start_id = 3;
var max_id = list.Max(y => y.Id);
var result =
from x in list
orderby (x.Id + max_id - start_id) % max_id
select x;
I get:
With LINQ to objects you can do something like that:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
var startId = 3;
var result = list
.GroupBy(i => i.Id >= startId ? 1 : 0) // split in two groups
.OrderByDescending(g => g.Key) // sort to have the group with startId first
.Select(g => g.OrderBy(i => i.Id)) // sort each group
.SelectMany(i => i) // combine result
.ToList();
Console.WriteLine(string.Join(", ", result.Select(i => i.Id))); // prints "3, 4, 1, 2"
You require 2 criteria to apply:
Order ascending by Id.
Return the Ids greater than threshold before the Ids less than threshold.
You can try:
var offset = 3;
var sorted1 = list
.OrderBy(item => item.Id < offset)
.ThenBy(item => item.Id);
The OrderBy condition yields true if Id is less than offset and false otherwise.
true is greater than false and therefore is returned later
A dirty way could also be:
var offset = 3;
var sorted2 = list
.OrderBy(item => unchecked((uint)(item.Id - offset)));
Here the offset is subtracted from Id and the result converted to unsigned int to make the negative values become very large positive ones. A little hacky. Might not work with queries against SQL providers.
Here's a toy Non-Linq Version
object[] ShiftList(int id)
{
var list = new dynamic[]
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
Span<dynamic> listSpan = list;
int indexFound = -1;
for (int i = 0; i < list.Length; i++)
{
if (listSpan[i].Id == id)
{
indexFound = i;
}
}
if (indexFound is -1)
{
return list;
}
var left = listSpan.Slice(0, indexFound);
var right = listSpan[indexFound..];
object[] objs = new object[list.Length];
Span<object> objSpan = objs;
right.CopyTo(objSpan);
left.CopyTo(objSpan[right.Length..]);
return objs;
}
Try using foreach and iterate over each object in your list:
foreach (var item in list)
{
}
from here you should be able to use some of the collection methods for a list to reorder your list.
In C#,I have List of Employee object. Employee class is
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
}
In List objected are sorted based on Employee.ID. I have an array of int which is basically Employee.ID which I want on top of the list and in list,order must remain same as in array.
If I hava input like this
List:
[
{ID:1,Name:A},
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
and Array: {2,3,1}
Then I want Output List:
[
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:1,Name:A},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
And I have done this
foreach (int i in a)
{
list = list.OrderBy(x => x.ID != i).ToList();
}
//a is array
//list is List
Any better Solution.Thanks in advance.
After you got your list sorted based on the ID just iterate the array and move the elements. In order to do this you need to first remove and then insert the item at the correct position.
for(int i = 0; i < myArray.Length; i++)
{
var e = myList.Single(x => x.Id == myArray[i]);
myList.Remove(e);
myList.Insert(i, e);
}
You may also want to use SingleOrDefault instead of Single to verify that myList even contains the element with the current id, e.g. when your array contains [2, 3, 101]
To add another version to the mix. The complete sorting can be done in one go:
list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? int.MaxValue : i; }).ToList();
where list is the EmployeeList and a the indices array. (NB, the for loop is not needed, the above should do both sortings).
Inside the OrderBy callback, if the id is not inside a, int.MaxValue is returned to place it after the ones inside the array (a.Length would work as well). OrderBy should maintain the original order of the enumeration (list) for those elements that return the same value.
PS, if you want to sort first by index inside a and the rest on the ids (not necessarily the original order), you can use the following (as long as a.Length + largest ID < int.MaxValue) : list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? a.Length + e.ID : i; }).ToList();
Here's a way to do it in pure LINQ, without changing the original sequence.
Broken into steps to see what's going on.
public static void Main()
{
var employeeList = new List<Employee>()
{
new Employee(){ ID= 1,Name= "A"},
new Employee() { ID= 2,Name= "B"},
new Employee() { ID= 3,Name= "AA"},
new Employee() { ID= 4,Name= "C"},
new Employee() { ID= 5,Name= "CD"},
new Employee() { ID= 100,Name= "Z"}
};
var orderByArray = new int[] { 2, 3, 1, 100, 5, 4 };
var sortPos = orderByArray.Select((i, index) => new { ID = i, SortPos = index });
var joinedList = employeeList.Join(sortPos, e => e.ID, sp => sp.ID, (e, sp) => new { ID = e.ID, Name = e.Name, SortPos = sp.SortPos });
var sortedEmployees = joinedList.OrderBy(e => e.SortPos).Select(e => new Employee { ID = e.ID, Name = e.Name });
}
Try this using LINQ:
List<Employee> employees = ...
int[] ids = ...
var orderEmployees = ids.Select(id => employees.Single(employee => employee.ID == id))
.Concat(employees.Where(employee => !ids.Contains(employee.ID)).ToList();
Foreach id in ids array we will grab the matching employee and we will concat to it all the employees that their id does not exist in ids array.
I like to use a special Comparer for that, it seems clearer to me, though a bit more code. It hides the complexity of the sort in the comparer class, and then you can just call it with :
theList.OrderBy(x => x.id, new ListOrderBasedComparer(sortList));
It will sort according to any list passed to the comparer when instantiating, and will put elements not in the "known sort list" at the end.
You can of course adapt it to your special needs.
public class ListOrderBasedComparer: Comparer<int>
{
private List<int> sortList;
public ListOrderBasedComparer(List<int> sortList)
{
// if you want you can make constructor accept arrays and convert it
// (if you find that more convenient)
this.sortList = sortList;
}
public override int Compare(int x, int y)
{
var indexOfX = sortList.FindIndex(a => a == x);
var indexOfY = sortList.FindIndex(a => a == y);
// handle elements not in sortArray : if not in sort array always assume they should be "less than the others" and "equal between them".
if (indexOfX == -1 && indexOfY == -1) return 0;
if (indexOfY == -1) return -1;
if (indexOfX == -1) return 1;
// if elements are in sortArray (FindIndex returned other than -1), use usual comparison of index values
return indexOfX.CompareTo(indexOfY);
}
}
Example on how to use it, with Linq :
public class TestCompare
{
public void test ()
{
var myArray = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var myArray2 = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 0, name = "X" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 23, name = "Z"},
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var sortList = new List<int> { 2, 3, 1, 4, 5, 6 };
// good order
var mySortedArray = myArray.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
// good order with elem id 0 and 23 at the end
var mySortedArray2 = myArray2.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
}
}
public class MyClass
{
public int id;
public string name;
}
Let's say I have following data:
Time Status
10:00 On
11:00 Off
12:00 Off
13:00 Off
14:00 Off
15:00 On
16:00 On
How could I group that using Linq into something like
[On, [10:00]], [Off, [11:00, 12:00, 13:00, 14:00]], [On, [15:00, 16:00]]
Create a GroupAdjacent extension, such as the one listed here.
And then it's as simple as:
var groups = myData.GroupAdjacent(data => data.OnOffStatus);
You could also do this with one Linq query using a variable to keep track of the changes, like this.
int key = 0;
var query = data.Select(
(n,i) => i == 0 ?
new { Value = n, Key = key } :
new
{
Value = n,
Key = n.OnOffFlag == data[i - 1].OnOffFlag ? key : ++key
})
.GroupBy(a => a.Key, a => a.Value);
Basically it assigns a key for each item that increments when the current item does not equal the previous item. Of course this assumes that your data is in a List or Array, otherwise you'd have to try a different method
Here is a hardcore LINQ solution by using Enumerable.Zip to compare contiguous elements and generate a contiguous key:
var adj = 0;
var t = data.Zip(data.Skip(1).Concat(new TimeStatus[] { null }),
(x, y) => new { x, key = (x == null || y == null || x.Status == y.Status) ? adj : adj++ }
).GroupBy(i => i.key, (k, g) => g.Select(e => e.x));
It can be done as.
Iterate over collection.
Use TakeWhile<Predicate> condition is text of first element of collection On or Off.
Iterate over the subset of from point one and repeat above step and concatenate string.
Hope it helps..
You could parse the list and assign a contiguous key e.g define a class:
public class TimeStatus
{
public int ContiguousKey { get; set; }
public string Time { get; set; }
public string Status { get; set; }
}
You would assign values to the contiguous key by looping through, maintaining a count and detecting when the status changes from On to Off and so forth which would give you a list like this:
List<TimeStatus> timeStatuses = new List<TimeStatus>
{
new TimeStatus { ContiguousKey = 1, Status = "On", Time = "10:00"},
new TimeStatus { ContiguousKey = 1, Status = "On", Time = "11:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "12:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "13:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "14:00"},
new TimeStatus { ContiguousKey = 3, Status = "On", Time = "15:00"},
new TimeStatus { ContiguousKey = 3, Status = "On", Time = "16:00"}
};
Then using the following query you can extract the Status and grouped Times:
var query = timeStatuses.GroupBy(t => t.ContiguousKey)
.Select(g => new { Status = g.First().Status, Times = g });