Mixing LINQ with async (getting payload from a seed) - c#

I have a collection of seeds
var seeds = new [] {1, 2, 3, 4};
From each seed I want to run an async method that performs some calculation with the seed:
async Task<int> Calculation(int seed);
My goal is to perform a select like this:
var results = from seed in seeds
let calculation = await Calculation(seed)
select new { seed, calculation };
Unfortunately, this syntax isn't allowed using LINQ.
How can I make the "results" variable contain both the seed and the calculation?
(I would appreciate any answer, but specially if it's using System.Reactive's Observable)

Here's an Rx solution:
var seeds = new [] {1, 2, 3, 4};
var results = Observable.ToObservable(seeds)
.SelectMany(async i => new { seed = i, calculation = await Calculation(i)})
.ToEnumerable();

You could do the following using WhenAll static method:
var r= await Task.WhenAll(seeds.Select(async seed =>new {
Seed= seed,
Result = await Calculation(seed)
}
)
);

Change your async function to return both the calculated number and the given seed:
public static async Task<Output> Calculation(int seed)
{
return new Output { Seed = seed, Result = 0 };
}
public class Output
{
public int Seed { get; set; }
public int Result { get; set; }
}
Then use the linq to return a Task[] on which you can WaitAll or WhenAll: (WaitAll vs WhenAll)
var seeds = new[] { 1, 2, 3, 4 };
var tasks = seeds.Select(Calculation);
var results = await Task.WhenAll(tasks);
foreach (var item in results)
Console.WriteLine($"seed: {item.Seed}, result: {item.Result}");

Related

How to group the Similar value array in c#

class Program
{
static void Main(string[] args)
{
int[] arr1 = { 1, 2, 3, 4 };
int[] arr2 = { 1, 2, 3 };
int[] arr3 = { 1, 2, 3 };
int[] arr4 = { 1, 2, 3, 4 };
int[] arr5 = { 1, 2, 3, 4 };
int[] arr6 = { 1, 2, 3, 4, 5 };
Order Order1 = new Order { OrderId = 1, Deatils = arr1 };
Order Order2 = new Order { OrderId = 2, Deatils = arr2 };
Order Order3 = new Order { OrderId = 3, Deatils = arr3 };
Order Order4 = new Order { OrderId = 4, Deatils = arr4 };
Order Order5 = new Order { OrderId = 5, Deatils = arr5 };
Order Order6 = new Order { OrderId = 6, Deatils = arr6 };
// I want to Output like this based on same values in details object.
string similarOrderDetailsOrderIds_1 = "1,4,5";
string similarOrderDetailsOrderIds_2 = "2,3";
string similarOrderDetailsOrderIds_3 = "5";
}
}
public class Order
{
public int OrderId { get; set; }
public int[] Deatils { get; set; }
}
I want to output like this because OrderId-1,4,5 have the same values in Details.
string similarOrderDetailsOrderIds_1 = "1,4,5";
string similarOrderDetailsOrderIds_2 = "2,3";
string similarOrderDetailsOrderIds_3 = "5";
This extension method is a generic generalization of the solution provided by Michael Liu here which orders elements prior to hash code generation and will work for any T which itself correctly implements GetHashCode() and Equals():
static class Extensions
{
public static int GetCollectionHashCode<T>(this IEnumerable<T> e)
{
return ((IStructuralEquatable)e.OrderByDescending(x => x).ToArray())
.GetHashCode(EqualityComparer<T>.Default);
}
}
You can utilize it like so:
var orders = new[] { Order1, Order2, Order3, Order4, Order5, Order6 };
var groups = orders
.Where(x => x.Deatils != null)
.GroupBy(x => x.Deatils.GetCollectionHashCode())
.Select(x => x.Select(y => y.OrderId));
Note: Deatils typo above is intentional as per OP.
You could use LINQ as the other answer provided or create a helper method to group them for you.
public static IEnumerable<IEnumerable<T>> Aggregate<T>(IEnumerable<T> ungrouped, Func<T, T, bool> Expression)
{
// create a place to put the result
List<List<T>> groupedItems = new();
// create a mutable storage to keep track which ones we shouldn't compare
List<T> mutableBag = ungrouped.ToArray().ToList();
// n^2 isn't great but it was easy to implement, consider creating a hash from all elements and use those instead of manual comparisons
foreach (var left in ungrouped)
{
// create a place to store any similar items
List<T> similarItems = new();
// compare element to every remaining element, if they are similar add the other to the similar list
foreach (var right in mutableBag)
{
if (Expression(left, right))
{
similarItems.Add(right);
}
}
// if we didn't find any similar items continue and let GC collect similar items
if (similarItems.Count == 0)
{
continue;
}
// since we found items remove the from the mutable bag so we don't have duplicate entries
foreach (var item in similarItems)
{
mutableBag.Remove(item);
}
// add the similar items to the result
groupedItems.Add(similarItems);
}
return groupedItems;
}
public static bool Compare<T>(T[] Left, T[] Right)
{
// this compare method could be anything I would advise against doing member comparison like this, this is simplified to provide how to use the Aggregate method and what the CompareMethod might look like for comlpex types
if (Left.Length != Right.Length)
{
return false;
}
foreach (var item in Left)
{
if (Right.Contains(item) is false)
{
return false;
}
}
return true;
}
For the given example
Order Order1 = new Order { OrderId = 1, Deatils = arr1 };
Order Order2 = new Order { OrderId = 2, Deatils = arr2 };
Order Order3 = new Order { OrderId = 3, Deatils = arr3 };
Order Order4 = new Order { OrderId = 4, Deatils = arr4 };
Order Order5 = new Order { OrderId = 5, Deatils = arr5 };
Order Order6 = new Order { OrderId = 6, Deatils = arr6 };
Order[] arrs = { Order1, Order2, Order3, Order4, Order5, Order6 };
We could use this new helper method like this:
var groupedItems = Aggregate(arrs, (left, right) => Compare(left.Deatils, right.Deatils));

Blocking collection when collect results inside ActionBlock

I think in the test method the "results" collection variable has to be of type BlockingCollection<int> instead of List<int>. Prove it to me if I am wrong. I have taken this example from https://blog.stephencleary.com/2012/11/async-producerconsumer-queue-using.html
private static async Task Produce(BufferBlock<int> queue, IEnumerable<int> values)
{
foreach (var value in values)
{
await queue.SendAsync(value);
}
}
public async Task ProduceAll(BufferBlock<int> queue)
{
var producer1 = Produce(queue, Enumerable.Range(0, 10));
var producer2 = Produce(queue, Enumerable.Range(10, 10));
var producer3 = Produce(queue, Enumerable.Range(20, 10));
await Task.WhenAll(producer1, producer2, producer3);
queue.Complete();
}
[TestMethod]
public async Task ConsumerReceivesCorrectValues()
{
var results = new List<int>();
// Define the mesh.
var queue = new BufferBlock<int>(new DataflowBlockOptions { BoundedCapacity = 5, });
//var consumerOptions = new ExecutionDataflowBlockOptions { BoundedCapacity = 1, };
var consumer = new ActionBlock<int>(x => results.Add(x), consumerOptions);
queue.LinkTo(consumer, new DataflowLinkOptions { PropagateCompletion = true, });
// Start the producers.
var producers = ProduceAll(queue);
// Wait for everything to complete.
await Task.WhenAll(producers, consumer.Completion);
// Ensure the consumer got what the producer sent.
Assert.IsTrue(results.OrderBy(x => x).SequenceEqual(Enumerable.Range(0, 30)));
}
Since ActionBlock<T> restricts its delegate to one-execution-at-a-time by default (MaxDegreeOfParallelism of 1), it is not necessary to use BlockingCollection<T> instead of List<T>.
The test in your code passes just fine for me, as expected.
If ActionBlock<T> were passed an option with a higher MaxDegreeOfParallelism, then you would need to protect the List<T> or replace it with a BlockingCollection<T>.

LINQ OrderBy - Custom

I have some data like
ID Sequence customIndex
1 1 0
2 2 0
3 3 2
4 4 1
5 5 0
I need to use sequence in order by when customIndex is zero other wise use customIndex.
So result should be ID in order of 1,2,4,3,5.
I need LINQ implementation using Lambda. I tried some solution but could not implement.
Posting duplicate and deleting previous one, because of wrong formatting the meaning of question got changed and I received bunch of negative votes.
Added code at dotnet fiddle:
https://stable.dotnetfiddle.net/fChl40
The answer is based on assumption, that CustomIndex is greater or equals to zero:
var result =
data.OrderBy(x => x.CustomIndex==0 ? x.Sequence :
data.Where(y => y.CustomIndex==0 && y.Sequence < x.Sequence)
.Max(y => (int?)y.Sequence))
.ThenBy(x => x.CustomIndex);
This is working for provided test data:
l.OrderBy(a => a.customIndex != 0 ?
list.Where(b => b.Sequence < a.Sequence && b.customIndex == 0)
.OrderByDescending(c => c.Sequence)
.FirstOrDefault()
.Sequence : a.Sequence)
.ThenBy(c=>c.customIndex )
.ToList();
The idea is to order non zero values by first preceding zero valued rows, and then by non zero values itself.
This is something I wanted:
public static void Main()
{
List<Data> data = new List<Data>();
data.Add(new Data{ Id=1, Sequence=1, CustomIndex=0});
data.Add(new Data{ Id=5, Sequence=5, CustomIndex=0});
data.Add(new Data{ Id=6, Sequence=6, CustomIndex=2});
data.Add(new Data{ Id=2, Sequence=2, CustomIndex=0});
data.Add(new Data{ Id=3, Sequence=3, CustomIndex=2});
data.Add(new Data{ Id=4, Sequence=4, CustomIndex=1});
data.Add(new Data{ Id=7, Sequence=7, CustomIndex=1});
int o = 0;
var result = data
.OrderBy(x=>x.Sequence).ToList()
.OrderBy((x)=> myCustomSort(x, ref o) )
;
result.ToList().ForEach(x=> Console.WriteLine(x.Id));
}
public static float myCustomSort(Data x, ref int o){
if(x.CustomIndex==0){
o = x.Sequence;
return x.Sequence ;
}
else
return float.Parse(o + "."+ x.CustomIndex);
}
Sample code: https://stable.dotnetfiddle.net/fChl40
I will refine it further
Based on your question and reply to my comment, I understand you need to clusterize the items' collection, then consider Sequence and CustomIndex on all items of each cluster.
Once clustered (split into blocks depending on a specific criterion) you can merge them back into a unique collection, but while doing that you can manipulate each cluster independently the way you need.
public static class extCluster
{
public static IEnumerable<KeyValuePair<bool, T[]>> Clusterize<T>(this IEnumerable<T> self, Func<T, bool> clusterizer)
{
// Prepare temporary data
var bLastCluster = false;
var cluster = new List<T>();
// loop all items
foreach (var item in self)
{
// Compute cluster kind
var bItemCluster = clusterizer(item);
// If last cluster kind is different from current
if (bItemCluster != bLastCluster)
{
// If previous cluster was not empty, return its items
if (cluster.Count > 0)
yield return new KeyValuePair<bool, T[]>(bLastCluster, cluster.ToArray());
// Store new cluster kind and reset items
bLastCluster = bItemCluster;
cluster.Clear();
}
// Add current item to cluster
cluster.Add(item);
}
// If previous cluster was not empty, return its items
if (cluster.Count > 0)
yield return new KeyValuePair<bool, T[]>(bLastCluster, cluster.ToArray());
}
}
// sample
static class Program
{
public class Item
{
public Item(int id, int sequence, int _customIndex)
{
ID = id; Sequence = sequence; customIndex = _customIndex;
}
public int ID, Sequence, customIndex;
}
[STAThread]
static void Main(string[] args)
{
var aItems = new[]
{
new Item(1, 1, 0),
new Item(2, 2, 0),
new Item(3, 3, 2),
new Item(4, 4, 1),
new Item(5, 5, 0)
};
// Split items into clusters
var aClusters = aItems.Clusterize(item => item.customIndex != 0);
// Explode clusters and sort their items
var result = aClusters
.SelectMany(cluster => cluster.Key
? cluster.Value.OrderBy(item => item.customIndex)
: cluster.Value.OrderBy(item => item.Sequence));
}
}
It ain't pretty, but it exemplifies what you were asking for, I think:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<Data> data = new List<Data>();
data.Add(new Data { Id = 1, Sequence = 1, CustomIndex = 0 });
data.Add(new Data { Id = 2, Sequence = 2, CustomIndex = 0 });
data.Add(new Data { Id = 3, Sequence = 3, CustomIndex = 2 });
data.Add(new Data { Id = 4, Sequence = 4, CustomIndex = 1 });
data.Add(new Data { Id = 5, Sequence = 5, CustomIndex = 0 });
//List of items where the sequence is what counts
var itemsToPlaceBySequence = data.Where(x => x.CustomIndex == 0).OrderBy(x => x.Sequence).ToList();
//List of items where the custom index counts
var itemsToPlaceByCustomIndex = data.Where(x => x.CustomIndex != 0).OrderBy(x => x.CustomIndex).ToList();
//Array to hold items
var dataSlots = new Data[data.Count];
//Place items by sequence
foreach(var dataBySequence in itemsToPlaceBySequence) {
dataSlots[dataBySequence.Sequence - 1] = dataBySequence ;
}
//Find empty data slots and place remaining items in CustomIndex order
foreach (var dataByCustom in itemsToPlaceByCustomIndex) {
var index = dataSlots.ToList().IndexOf(null);
dataSlots[index] = dataByCustom ;
}
var result = dataSlots.ToList();
result.ForEach(x => Console.WriteLine(x.Id));
var discard = Console.ReadKey();
}
public class Data
{
public int Id { get; set; }
public int Sequence { get; set; }
public int CustomIndex { get; set; }
}
}
The ordering you want to do (order partly on CustomIndex and partly on Sequence) doesn't work like that. But this should be close to what you want. Order first by CustomIndex, and then by Sequence.
var result = data.OrderBy(x => x.CustomIndex).ThenBy(x => x.Sequence);

Equality between two IEnumerable<IEnumerable<int>> objects

I don't think SequenceEqual is working between the two because the "middle" elements (IEnumerable<int>) aren't using SequenceEqual.
oneThingy.SequenceEqual(twoThingy)
Short of using String.Join on the middle elements, is there a way to get equality?
SequenceEqual tests using Equals; to use SequenceEquals you'll need to implement it yourself. Try using the Zip operator with sequence equals.
// example
var first = Enumerable.Range(1, 10).Select(i => Enumerable.Range(1, i));
var second = Enumerable.Range(1, 10).Select(i => Enumerable.Range(1, i));
bool nestedSequencesEqual =
// test if each sequence index is equal
first.Zip(second, (f, s) => f.SequenceEqual(s))
// ensure all like sequences are equal
.All(b => b);
// returns true
+1 for #BleuM937 answer.
As an another approach you can use the SequenceEqual overloads with equality comparer:
IEnumerable<IEnumerable<int>> one = new IEnumerable<int>[] { new int[] { 1 }, new int[] { 1, 2, 3 } };
IEnumerable<IEnumerable<int>> two = new IEnumerable<int>[] { new int[] { 1 }, new int[] { 1, 2, 3 } };
bool nestedSequencesEqual = one.SequenceEqual(two, new SequencesComparer<int>());
class SequencesComparer<T> : IEqualityComparer<IEnumerable<T>> {
public bool Equals(IEnumerable<T> x, IEnumerable<T> y) {
return x.SequenceEqual(y);
}
public int GetHashCode(IEnumerable<T> obj) {
return obj.GetHashCode();
}
}
The following code works for me...
public class IntList : List<int>, IEquatable<IntList>
{
public bool Equals(IntList other)
{
return this.SequenceEqual(other);
}
}
void Main()
{
List<IntList> list1 = new List<IntList>(2);
List<IntList> list2 = new List<IntList>(2);
var list11 = new IntList() {1, 2, 3};
list1.Add(list11);
var list21 = new IntList() {1, 2, 3};
list2.Add(list21);
var result = list1.SequenceEqual(list2);
Console.WriteLine(result);
}
Reference: http://msdn.microsoft.com/en-us/library/bb348567(v=vs.100).aspx

Using Linq DynamicQuerable for a Not IN sql type operation

I am using the Linq DynamicQuerable code. I have an array of integers and a string representing the field I want to use as the excluding filter.
For example
IQuerable GetItemsWithoutExcluded(IQuerable qry, string primaryKey, List<int> excludedItems) {
// psuedo code
return qry.Where("p=>! p.primaryKey in excludedItems");
}
Any idea how I would accomplish this using DynamicQuerable?
You can do this without dynamic-LINQ. Dynamic-LINQ is not a full fledged citizen of the framework. And by changing the way you accomplish this task you can use a more effective filter. The way you have it now the method needs to be called for each exclusion.
Try something like this instead
List<int> allKeys = new List<int>{1,2,3,4,5,6};
List<int> excluded = new List<int>{ 1, 2, 3 };
var query = from included in allKeys
where !excluded.Contains(included)
select included;
foreach (var item in query)
Console.WriteLine(item);
I would implement this like the following using ordinary LINQ:
public static IQueryable<T> Exclude<T>(this IQuerable<T> qry, Expression<Func<T, int>> keySelector, List<int> excludedItems)
{
var keyGroups = qry.GroupBy(keySelector);
var includedGroups = keyGroups.Where(g => !excludedItems.Contains(g.Key));
return includedGroups.SelectMany(g => g);
}
Then it can be used like so:
public class MyClass
{
public int Key { get; set; }
}
IQueryable<MyClass> source = // Get source data (DataContext/ObjectContext/ISession etc.)
var excludedKeys = new List<int> { 1, 3, 11 };
var result = source.Exclude(item => item.Key, excludedKeys);
Update for Dynamic LINQ
IQuerable GetItemsWithoutExcluded(IQuerable qry, string primaryKey, List<int> excludedItems)
{
if(excludedItems.Count == 0)
return qry;
var keyChecks = excludedItems.Select(i => String.Format("p.{0} != {1}", primaryKey, i));
var constraint = String.Join(" && ", keyChecks )
return qry.Where("p => " + constraint);
}

Categories

Resources