Is it possible to group make this with LINQ? - c#

I have the following method which returns an IList<IGrouping<bool, int>>, I was wondering if it was possible to do this using LINQ.(Test case beneath I wrote to see what should happen, small numbers (<=100) should have a 'yes' boolean and big numbers (>100) should have a 'no' boolean.
public IList<IGrouping<bool, int>> GroupSmallAndBigNumbers(int[] numbers)
{
}
[Test]
public void NumbersSmallerThanOrEqualTo100AndBiggerNumbersCanBeGroupedUsingGroupBy()
{
//Arrange
int[] numbers = { 5, 700, 15, 108, 25, 28, 100 };
int[] expectedSmallNumbers = { 5, 15, 25, 28, 100};
int[] expectedBigNumbers = { 700, 108 };
//Act
var results = _examples.GroupSmallAndBigNumbers(numbers);
//Assert
Assert.That(results.Count, Is.EqualTo(2));
var smallNumbers = results[0];
Assert.That(smallNumbers, Is.EquivalentTo(expectedSmallNumbers));
var bigNumbers = results[1];
Assert.That(bigNumbers, Is.EquivalentTo(expectedBigNumbers));
}

You could group by checking if each number is greater than 100.
public IList<IGrouping<bool, int>> GroupSmallAndBigNumbers(int[] numbers)
=> numbers.GroupBy(x => x >= 100).ToList();

I think a Dictionary would work better (and easier) for you. Using that, you can easily do something like this:
int[] numbers = { 5, 700, 15, 108, 25, 28, 100 };
Dictionary<int, bool> dict = new Dictionary<int, bool>();
foreach (int number in numbers)
{
if (number > 100)
dict.Add(number, true);
else
dict.Add(number, false);
}

Related

C# parallelize loop

I have the following c# code:
int[] ns = new int[4] { 100, 500, 1000, 5000 };
int[] ks = new int[5] { 5, 10, 15, 80, 160 };
int[] rs = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for(int n = 0; n<ns.Length; n++)
{
for(int k = 0; k < ks.Length; k++)
{
for (int r = 0; r < rs.Length; r++)
{
RunProg(ns[n], ks[k], rs[r]);
}
}
}
where RunProg takes a relatively substantial amount of time. I would like to parallelize this code. What is the most straight forward way to parallelize this in C#?
I have not tried any method yet. I was trying to decide between the different functionalities available in C#
A simple way to parallelize this is to produce a sequence representing all combinations of inputs, then transforming this sequence to a sequence of thread pool tasks representing the result. This final sequence can simply be passed to Task.WhenAll to await the completion of all tasks.
class Program
{
static void Main(string[] args)
{
int[] ns = new int[4] { 100, 500, 1000, 5000 };
int[] ks = new int[5] { 5, 10, 15, 80, 160 };
int[] rs = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var inputs = ns
.SelectMany(n => ks.Select(k => new { n, k }))
.SelectMany(x => rs.Select(r => new { x.n, x.k, r }));
var tasks = inputs.Select(x => Task.Run(() => RunProg(x.n, x.k, x.r)));
var results = Task.WhenAll(tasks).Result;
}
static int RunProg(int n, int k, int r)
{
Thread.Sleep(1000);
return n + k + r;
}
}
You can also Parallel.ForEach over the inputs collection, as mentioned in the other answers.
Parallel.ForEach(inputs, x => RunProg(x.n, x.k, x.r));
There is a Parallel.For method:
int[] ns = new int[4] { 100, 500, 1000, 5000 };
int[] ks = new int[5] { 5, 10, 15, 80, 160 };
int[] rs = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Parallel.For(0, ns.Length, n =>
{
Parallel.For(0, ks.Length, k =>
{
Parallel.For(0, rs.Length, r =>
{
RunProg(ns[n], ks[k], rs[r]);
});
});
});
This should work provided that the order in which the RunProg method is called is not important, and that the method itself is thread-safe.
Use Parallel.For() for the loops: https://msdn.microsoft.com/de-de/library/dd460713(v=vs.110).aspx
This works if the iterations are not dependent on each other.

Sorting a list in ascending and descending order

I have a collection of integers e.g.
INPUT EXAMPLE
4,7,9,8,20,56,78,34,2,76,84,98
I need to sort this list in a way that any number til 20 will be sorted in ascending order and above 20 will be sorted in descending order. So output will be :
OUTPUT EXAMPLE
2,4,7,8,9,20,98,84,78,76,56,34
I wrote a comparer for it. but now trying for more clean approach may be by using the existing tools like orderby.
You can do that using two sort groups:
list.OrderBy(i => i <= 20 ? i : int.MaxValue) // sort numbers less than 20 ascending; put numbers greater than 20 at the end
.ThenByDescending(i => i) // sort remaining numbers descending
You can trivially do this using a custom comparer:
public class MyCustomComparer : IComparer<int>
{
private readonly int _cutOffPointInclusive;
public MyCustomComparer(int cutOffPointInclusive)
{
_cutOffPointInclusive = cutOffPointInclusive;
}
public int Compare(int x, int y)
{
if (x <= _cutOffPointInclusive || y <= _cutOffPointInclusive)
{
return x.CompareTo(y);
}
else
{
return y.CompareTo(x);
}
}
}
This sorts ascendingly when either value to compare is lower than or equal to the cutoff point (both to push the greater values to the top and to sort the values up till the cutoff point ascendingly), and descendingly when both are greater than the cutoff point (to actually sort those greater values descendingly).
Tested using:
var testData = new List<int>{ 4,7,9,8,20,56,78,34,2,76,84,98 };
testData.Sort(new MyCustomComparer(20));
foreach (var i in testData)
{
Console.WriteLine(i);
}
Output:
2
4
7
8
9
20
98
84
78
76
56
34
See also http://ideone.com/YlVH8i. So I don't really think this isn't "clean", but just fine.
Why don't use two steps?
var bellow = originallist.Where(i => i <= 20).OrderBy(i);
var above= originallist.Where(i => i > 20).OrderByDescending(i);
var sorted = bellow.Concat(above).ToList();
int[] a = { 4, 7, 9, 8, 20, 56, 78, 34, 2, 76, 84, 98 };
var b = a.OrderBy(i => i > 20 ? int.MaxValue - i : i);
If possible, I recommend sorting in-place. For example ( can be improved )
Array.Sort(a, (i1, i2) => (i1 > 20 ? int.MaxValue - i1 : i1) - (i2 > 20 ? int.MaxValue - i2 : i2));
[Test]
public void SortTill20AscRestDesc()
{
var src = new[] {4, 7, 9, 8, 20, 56, 78, 34, 2, 76, 84, 98};
var expected = new[] {2, 4, 7, 8, 9, 20, 98, 84, 78, 76, 56, 34};
var result = src
.Select(
i => new
{
IsAbove20 = i > 20,
Value = i
}
)
.OrderBy(e => e.IsAbove20)
.ThenBy(e => e.IsAbove20 ? int.MaxValue : e.Value)
.ThenByDescending(e => e.Value)
.Select(e=>e.Value);
Assert.That(result.SequenceEqual(expected), Is.True);
}

Generate random number from two List<>

i want to generate random number from two list. i want to create a function where i pass how much random number from two list.
List<int> integers = new List<int>() { 54, 23, 76, 123, 93, 7, 3489 };
List<int> value2 = new List<int>() { 1, 3, 4, 6, 8, 17, 40 };
i want my result = List<int> result = {54,40,123,17,3,1,3489,76...etc}
When i run again the set of result will be change.
Presently i am using this function that return List
public static List<int> GenerateRandom(int count)
{
// generate count random values.
HashSet<int> candidates = new HashSet<int>();
while (candidates.Count < count)
{
// May strike a duplicate.
candidates.Add(random.Next(1,30));
}
// load them in to a list.
List<int> result = new List<int>();
result.AddRange(candidates);
// shuffle the results:
int i = result.Count;
while (i > 1)
{
i--;
int k = random.Next(i + 1);
int value = result[k];
result[k] = result[i];
result[i] = value;
}
return result;
}
i am calling the function
List<int> vals = GenerateRandom(20);
But i want that the random number from two List<> List<int> integers and List<int> value2 . so how can i do this .
You can do something like this:
var result =
integers.Concat(value2)
.OrderBy(x => random.Next())
.Take(count)
.ToList();
You could write a general-purpose function to give you a random ordering of any number of sequences, like so:
public static IReadOnlyCollection<T> InRandomOrder<T>(Random rng, params IEnumerable<T>[] lists)
{
return lists
.SelectMany(x => x)
.OrderBy(y => rng.Next())
.ToList();
}
You can then pass as many lists as you like and get the contents back in a fully randomised order:
var list1 = new[] {1, 2, 3, 4, 5};
var list2 = new[] {6, 7, 8};
var list3 = new[] {9, 0};
Random rng = new Random();
for (int i = 0; i < 10; ++i)
{
var randomisedFirst5 = InRandomOrder(rng, list1, list2, list3).Take(5);
Console.WriteLine(string.Join(", ", randomisedFirst5));
}
There's a less efficient approach you can use that avoids the need for an instance of Random, but you should only use this for short lists or where you really don't care about performance. It uses Guid.NewGuid() to generate random numbers:
public static IReadOnlyCollection<T> InRandomOrder<T>(params IEnumerable<T>[] lists)
{
return lists
.SelectMany(x => x)
.OrderBy(y => Guid.NewGuid())
.ToList();
}
Even the more efficient approach isn't the fastest. A faster way would be to use reservoir sampling to take the first N items that you want, and put them into an array which you shuffle using Knuth. That would make it a lot faster, at the expense of more complicated code - meaning you should only do it the fast way if it's really needed.
If what you want is to select a number that exists either in list A or B, randomly, you can do:
List<int> integers = new List<int>() { 54, 23, 76, 123, 93, 7, 3489 };
List<int> value2 = new List<int>() { 1, 3, 4, 6, 8, 17, 40 };
List<int> allInOne = new List<int>(integers.Concat(value2));
Random r = new Random(DateTime.Now.Millisecond);
/********************************
For demonstration purposes
********************************/
for(int i = 0; i < 5; i++)
{
var randomListIndex = r.Next(0, allInOne.Count - 1);
Console.WriteLine(allInOne[randomListIndex]);
}
Use KeyValuePair
static void Main(string[] args)
{
List<KeyValuePair<int, int>> results = GenerateRandom(100);
}
static List<int> integers = new List<int>() { 54, 23, 76, 123, 93, 7, 3489 };
static List<int> value2 = new List<int>() { 1, 3, 4, 6, 8, 17, 40 };
static Random random = new Random();
public static List<KeyValuePair<int,int>> GenerateRandom(int count)
{
List<KeyValuePair<int,int>> result = new List<KeyValuePair<int,int>>();
for(int i = 0; i < count; i++)
{
int firstValue = integers[random.Next(0, integers.Count - 1)];
int seconValue = value2[random.Next(0, value2.Count - 1)];
result.Add(new KeyValuePair<int,int>(firstValue,seconValue));
}
return result;
}​
I actually made a library a while back that handles some of this stuff : Underscore.cs
It's a nuget package so easy to install, the code to shuffle or take a sample randomly of two lists is :
var ls1 = GenerateRandom(10);
var ls2 = GenerateRandom(20);
var mixer = ls1.Concat(ls2).ToList();
//if you want all of the items shuffled use shuffle
var result = _.List.Shuffle(mixer);
//or if you want a subset randomly sorted use sample
result = _.List.Sample(mixer);

How to group a list of integers based on moving range?

I have a
list<int> = {14, 24, 56,189,909,1000};
I want to collapse (group?) them by a range such that the ints that fall within the range of each other are collapse into one value.
So the results should be for range = 100
{14,24,56} //since they 24 falls within 100 of 14 and 56 falls within 100 of 24
{189}
{909, 1000} //since they fall within 100 of each other
I know this is possible using a linq group by but I am stumped by the syntax.
I have looked at this answer but cannot figure out what to use for the ranges, since I have only one range i.e. 100.
int[] values = {100, 110, 120, 130, 140, 150, 160, 170};
int[] ranges = {115, 145, 180};
var query = from value in values
group value by ranges.Where(x => value >= x)
.DefaultIfEmpty()
.Last();
foreach (var group in query)
{
Console.WriteLine("{0}: {{{1}}}", group.Key,
string.Join(", ", group));
}
Your best option is using a plain old for loop instead of linq:
var l = new[] { 14, 24, 56, 189, 909, 1000 };
var groups = new List<List<int>>();
groups.Add(new List<int>());
groups[0].Add(l[0]);
for (int i = 1; i < l.Length; i++)
{
if (l[i] - l[i - 1] > 100)
{
groups.Add(new List<int>());
}
groups[groups.Count - 1].Add(l[i]);
}
Edit: This may not apply, especially with the additional requirement in the comment as it 1) starts each group only at one point and 2) would place 150 only in the first group.
I would probably write it as so because of the "dynamic" range (and I'm not sure how Group By could be used without an equally involved bucket process). This function requires that the input is already sorted.
IEnumerable<IEnumerable<int>> GroupByStartingRange (IEnumerable<int> src) {
int? maybeStart;
while ((maybeStart = src.FirstOrDefault() != null) {
if (maybeStart.HasValue) {
var start = maybeStart.Value;
yield return src.TakeWhile(x => x <= start + 100)
src = src.SkipWhile(x => x <= start + 100);
}
}
}
Assuming your ranges are ordered:
int[] values = { 100, 110, 120, 130, 140, 150, 160, 170 };
int[] ranges = { 115, 145, 180 };
var groups = values.GroupBy(x => ranges.First(r => x <= r));

C# Split time List into time ranges

I am looking to extract ranges from an list of integers using linq:
for example I am looking to split the following list:
List<int> numberList = new List<int>() { 30, 60, 90, 120, 150, 180, 270, 300, 330 };
into a list of integer ranges that will look like:
{ 30, 180 }
{ 270, 330 }
ie: where the next seq is greater than 30
another example :
List<int> numberList = new List<int>() { 30, 60, 120, 150, 270, 300, 330 };
into a list of integer ranges that will look like:
{ 30, 60 }
{ 120, 150 }
{ 270, 330 }
I have tried with for loops to find the best way possible however I don't
know where to start trying to use a linq query to do this.
You could write a method to handle the split:
IEnumerable<IList<int>> SplitValues(IList<int> input, int difference = 30)
{
List<int> results = new List<int>();
int last = input.First();
foreach(var value in input)
{
if (value - last > difference)
{
yield return new[] {results.First(), results.Last()};
results = new List<int>();
}
results.Add(value);
last = value;
}
yield return new[] {results.First(), results.Last()};
}
This matches your specifications as described, returning:
{ 30, 60 }
{ 120, 150 }
{ 270, 330 }
Note that a single value within the collection without a range will be duplicated. For example, { 30, 120, 150 } will return:
{ 30, 30 }
{ 120, 150 }
You can do this in one linq statement:
var numberList = new List<int>() { 30, 60, 120, 150, 270, 300, 330 };
var section = 0;
var result = numberList
.Select( (x, i) => new {value = x, section = (i == 0 ? 0 : ((x - numberList[i - 1]) > 30 ? ++section : section))})
.GroupBy(x => x.section)
.Select(x => x.Select(v => v.value).ToList()).ToList();
Well. There are many ways to do so and all have their pros and cons.
So here's yet another solution, hope it will be helpful to someone.
public static IEnumerable<TSource[]> ToRanges<TSource>(
this IEnumerable<TSource> source, Func<TSource, TSource, TSource, bool> isNear)
{
List<TSource[]> result = source./*OrderBy(value => value).*/Aggregate(
new List<TSource[]> { new[] { source.First(), source.First() } },
(ranges, currentValue) => {
TSource[] currentRange = ranges.Last();
TSource previousValue = currentRange[1];
if (isNear(currentRange[0], previousValue, currentValue))
currentRange[1] = currentValue;
else
ranges.Add(new[] { currentValue, currentValue});
return ranges;
}
);
return result;
}
Example usage:
List<int> numbers = new List<int>() { 30, 60, 90, 120, 150, 180, 270, 300, 330 };
// split by max difference
numberList.ToRanges(
(first, previous, current) => current - previous <= 30).ToArray();
// { 30, 180 }
// { 270, 330 }
// split by max range
numberList.ToRanges(
(first, previous, current) => current - first <= 90).ToArray();
// { 30, 120 }
// { 150, 180 }
// { 270, 330 }
In addition, you can split not only integers but also, for example, words by their first letter. Or DateTime/TimeSpan. Or whatever you want.
You must use LINQ? If not, what about:
List<int> numberList = new List<int>() { 30, 60, 120, 150, 270, 300, 330 };
Dictionary<int, int> result = new Dictionary<int, int>();
int lastStart = numberList.First();
for(int i=1; i < numberList.Count; i++)
{
if(numberList[i] >= lastStart + 30)
{
result.Add(lastStart, numberList[i]);
if (i == numberList.Count - 1) break;
lastStart = numberList[i + 1];
i++;
}
}
foreach (var item in result)
{
Console.WriteLine("{{{0}, {1}}}", item.Key, item.Value);
}
You can use TakeWhile and add the result to another list
void SplitByRange()
{
List<int> numberList = new List<int>() { 30, 60, 120, 150, 270, 300, 330 };
IEnumerable<int> aux = new List<int>();
int n = numberList.First();
int skip = 0;
List<List<int>> output = new List<List<int>>();
while ((aux = numberList.Skip(skip).TakeWhile(o => { bool r = (o - n) <= 30; n = o; return r; })).Count() > 0)
{
output.Add(aux.ToList());
skip += aux.Count();
}
}
At the end numberList will be empty and output will be a list of lists.
output[0] // { 30, 60 }
...
The current code will required at least 1 element on the list, and if you have
{ 30, 100 }
It will return as two lists with 1 element in each
{ 30 }
{ 100 }
Try this:
private static List<int[]> GetGroups(List<int> numberList)
{
List<List<int>> groups = new List<List<int>>();
numberList.Zip(numberList.Skip(1), (a, b) =>
{
if ((b - a) == 30)
{
if (groups.Count == 0)
groups.Add(new List<int>());
groups[groups.Count - 1].Add(a);
}
else if (a == b)
{
groups[groups.Count - 1].Add(a);
}
else
{
groups[groups.Count - 1].Add(a);
groups.Add(new List<int>());
}
return a;
}).ToList();
groups[groups.Count - 1].Add(numberList.Last());
return groups.Select(g => new[] { g.First(), g.Last() }).ToList();
}
Sample usage:
//List<int> numberList = new List<int>() { 30, 60, 90, 120, 150, 180, 270, 300, 330 };
List<int> numberList = new List<int>() { 30, 60, 120, 150, 270, 300, 330 };
var result = GetGroups(numberList);

Categories

Resources