init float array incrementing index value every x times with Linq - c#

What would be the most effective way to initialize a float array of size n incrementing it every x indices using linq
for instance if array increment is 5 starting on 10 and increment size is 5 array would look like
float[] x = {10, 10, 10, 10, 10, 15, 15, 15, 15, 15, 20, 20, 20, 20, 20, 25, 25, 25, 25, 25}

You can argue what is the "best" way. Bit the way how i would do it. At first i would create a helper method that helps you to generate Enumerables.
public static IEnumerable<T> Unfold<T>(T seed, Func<T, T> accumulator) {
var nextValue = seed;
while ( true ) {
yield return nextValue;
nextValue = accumulator(nextValue);
}
}
This is a general helper function. For example with
Unfold(1, x => x*2)
it creates you a new IEnumerable where every new number is the double of the previous number. You should use the .Take() or .TakeWhile() methods to limit the amount of generated numbers. If you for example just want the first 10 numbers with power of two
Unfold(1, x => x*2).Take(10).ToList()
You get a List with [1,2,4,8,16,32,64,128,256,512]
Something like Unfold() is in general helpfull to create arbitary Enumerables.
Now to create your list. You wanted to create a list that always add 5. and then every number gets repeated 5 times, and you want reapeat that 4 times. So your first step is
Unfold(10, x => x+5).Take(4)
It will create you an IEnumerable that contains [10, 15, 20, 25]. Now the next step is to repeat every number 5 times.
You can do this with Enumerable.Repeat(). The logic would be.
Go through your list
Create a new Enumerable from every number with Enuerable.Repeat
Flatening every Enumerable to a single Enumerable
That logic above is exactly what SelectMany() does. So the solutions is
var nums = Unfold(10, x => x+5).Take(4).SelectMany(x => Enumerable.Repeat(x, 5));
Now nums is the following list [10, 10, 10, 10, 10, 15, 15, 15, 15, 15, 20, 20, 20, 20, 20, 25, 25, 25, 25, 25]

How about this:
var n = 20;
var start = 10;
var step = 5;
var increment = 5;
var x = Enumerable
.Range(0, n)
.Select(y => (float)start + increment * (y / step))
.ToArray();
Note that although I like Linq very much, I must agree with the Robert Harvey that a loop would probably be more legible in this case.

Linq probably isn't your best choice for this problem. That being said, something like this, perhaps?
var groupCount = 4;
var elementsPerGroup = 5;
var increment = 5;
var a = Enumerable
.Range(0, groupCount)
.Select(i => Enumerable
.Range(0, elementsPerGroup)
.Select(r => (float)(10 + increment * i)).ToArray())
.SelectMany(i => i)
.ToArray();

Related

How to get the even item positions in a list with LINQ

I have a list like this
List<double> points1 = new List<double>
{
10, 20, 30, 40, 50, 60, 70, 80, 90, 100
};
How can I get the even positions with LINQ in order to get a list like this
20,40,60,80,100
I know how to do it with a for loop, but I want this in a single line with LINQ
points1.Where((value, idx) => idx % 2 != 0);

Convert all numbers to positives in a List of integers

If I have a list of both positive and negative integers:
var values = new List<int> { -30, -20, -10, 0, 10, 20, 30 };
How do I convert all the values to positive numbers?
var values = new List<int> { 30, 20, 10, 0, 10, 20, 30 };
I know I could use intValue = intValue * -1 but that would only convert the negatives to positives and vice versa. Besides, if possible I would like to do this using LINQ.
Use Math.Abs:
var positives = values.Select(i => Math.Abs(i)).ToList();
Or the shortened form using method group syntax (as mentioned by #CommuSoft in the comments):
var positives = values.Select(Math.Abs).ToList();
values.Select(Math.Abs).ToList();
Or
values.Select(n => n < 0 ? -n : n).ToList();
Or (fastest way)
values.Select(n => n & int.MaxValue).ToList();

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));

Sort list based on multiple conditions

I have a list of integer lists, like that:
A -> 10 10 1 1 1
B -> 10 9 9 7 6
...
I would like to sort them based on how many 10s they have, then on how many 9s, 8s, 7s, and so on untile the 1s
So in the example above A should be better than B because even if it has less total points, it has two 10s instead of only 1.
Code should be generic because I don't know how many numbers will be available for each case (sometimes 10, sometimes 5, or even only 3).
I developed something like that:
lists.OrderByDescending(a => a.Where(b => b == 10).Count()).
ThenByDescending(a => a.Where(b => b == 9).Count()).
and so on, but this is not generic...
I hope the question is clear... thank you very much!
You can create query which orders lists by count of 10s, then compose query by adding additional orderings for numbers from 9 to 1:
var query = lists.OrderByDescending(l => l.Count(x => x == 10));
for (int i = 9; i >= 1; i--)
query = query.ThenByDescending(l => l.Count(x => x == i));
For these sample lists:
var lists = new[] {
new[] { 10, 9, 9, 8, 7 },
new[] { 10, 9, 9, 7, 6 },
new[] { 10, 10, 1, 1, 1 }
};
Result will be:
[10, 10, 1, 1, 1]
[10, 9, 9, 8, 7]
[10, 9, 9, 7, 6]
It's simple, but not very efficient. If you need better performance, then consider creating custom comparer. Here is sample with comparer which uses zipped ordered sequences to check if all items in sequences are same, or get first item which is different:
public class CustomComparer : Comparer<IList<int>>
{
public override int Compare(IList<int> x, IList<int> y)
{
var comparisons = x.Zip(y, (a,b) => a.CompareTo(b));
foreach(var comparison in comparisons)
{
if (comparison != 0)
return comparison;
}
return x.Count.CompareTo(y.Count);
}
}
NOTE: If items in lists are not ordered, then you should sort them before zipping:
var comparisons =
x.OrderByDescending(i => i)
.Zip(y.OrderByDescending(i => i), (a,b) => a.CompareTo(b));
It works very simple. Consider two lists:
[10, 9, 9, 8, 7, 5]
[10, 9, 9, 7, 6]
It will create pairs of items in corresponding positions:
{10,10}, {9,9}, {9,9}, {8,7}, {7,6}
Then items in each pair will be compared one by one, until first mismatch will be found:
0, 0, 0, 1 (four comparisons only)
That means first list has more 8s than second one. Usage:
var query = lists.OrderByDescending(i => i, new CustomComparer());
Result is same.
The following comparer
public class Comparer : IComparer<IEnumerable<int>>
{
public int Compare(IEnumerable<int> a, IEnumerable<int> b)
{
var aOrdered = a.OrderByDescending(i => i).Concat(new[] { int.MinValue });
var bOrdered = b.OrderByDescending(i => i).Concat(new[] { int.MinValue });
return a.Zip(b, (i, j) => i.CompareTo(j)).FirstOrDefault(c => c != 0);
}
}
lets you order you lists of lists like so
var result = lists.OrderByDescending(i => i, new Comparer());
without iterating through each list ten times counting individual elements.
This compares the lists and returns conventional comparison result - 1, 0, or -1 is returned depending on whether one value is greater than, equal to, or less than the other.
static int CompareLists(List<int> a, List<int> b)
{
var grpA = a.GroupBy(p => p).ToDictionary(k=>k.Key,v=>v.Count());
var grpB = b.GroupBy(p => p).ToDictionary(k=>k.Key,v=>v.Count());
for (int i = 10; i >= 0; i--)
{
int countA = grpA.ContainsKey(i) ? grpA[i] : 0;
int countB = grpB.ContainsKey(i) ? grpB[i] : 0;
int comparison = countA.CompareTo(countB);
if (comparison != 0)
return comparison;
}
return 0;
}
First we convert the lists into dictionary of number->amount of occurences.
Then we iterate through numbers from 10 to 0 and compare the number of occurences. If the result is 0, then we go to another number.
If you have List<List<int>> to sort, just use list.Sort(CompareLists) as in:
List<int> d = new List<int> { 10, 6, 6 };
List<int> b = new List<int> { 10, 9, 9 };
List<int> a = new List<int> { 10, 10, 1, 1, 1 };
List<int> c = new List<int> { 10, 7, 7 };
List<int> e = new List<int> { 9, 3, 7 };
List<int> f = new List<int> { 9, 9, 7 };
List<List<int>> list = new List<List<int>>() { a, b, c, d, e, f };
list.Sort(CompareLists);

Reordering an array in a customized way

I have an array of items that prints to pdf in the following order.
Lets say for eg:
lines = {1, 2, 3,
4, 5, 6,
7, 8, 9,
10}
is the content of my array.
However I want to change the order of the items in the array to
{1, 4, 7,
2, 5, 8,
3, 6, 9,
10}
Then I pass this array to my print engine. Basically if there are more than 3 items in the array, my new code should reorder it.
Could somebody help me figuring out the logic for that.
Thanks
Order the lines by the modulus of the line index with the number of rows.
public static ICollection<T> Sort<T>(ICollection<T> lines, int columns)
{
var rows = lines.Count/columns;
if (rows == 0)
{
return lines;
}
return lines.Select((line, i) => new {line, i})
.OrderBy(item => item.i < columns*rows ? item.i%rows : rows)
.Select(item => item.line)
.ToList();
}
Edit: Alternatively you can use an iterator method and the list's indexer instead of LINQ:
public static IEnumerable<T> Sort<T>(IList<T> lines, int columns)
{
var rows = lines.Count/columns;
for (var i = 0; i < lines.Count; i++)
{
var index = rows > 0 && i < columns*rows
? (i%columns)*rows + i/columns
: i;
yield return lines[index];
}
}
Assuming "for linear array assuming every 9 elements form 3x3 matrix transpose each subsequence, keep remainder as-is":
// assuming T[] items;
var toTranspose = (items.Count() / 9) * 9;
var remap = new int[]{1, 4, 7, 2, 5, 8, 3, 6, 9 };
var result = Enumerable.Range(0, toTranspose)
.Select(pos => items[(pos / 9) * 9 + (remap[pos % 9] - 1)])
.Concat(items.Skip(toTranspose)
.ToArray();
Summary of code:
get number of items that need to be moved (which is number of groups of 9 items int numberOfGroup = Count()/9;, multiplied by group size)
have custom transformation in remap array (note that indexes copied as-is from sample and actually off-by-one hence -1 in computing index)
for each element index under toTranspose get source element from corresponding group and apply transformation with remap.
finally Concat the remainder.
Notes:
one can easily provide custom transformation or inline transposition if needed.
can't apply transformation to the last partial group as elements will have to go to non-existent positions.

Categories

Resources