I want to be able to get the nearest x-tens-place number above and below any number using C#
For example, if I have a 4-digit number and I want all the closes number above and below ending and set of 2-digit number like 30, 50, 80, or 00 then
2126 => 2100 and 2130
2146 => 2130 and 2150
2183 => 2180 and 2200
I want to be able to do this below 1 too, like if my set of levels are 0.0030, 0.0050, 0.0080 and 0.0000 then if I had the following numbers
1.0026 => 1.0000 and 1.0030
1.0046 => 1.0030 and 1.0050
1.0083 => 1.0080 and 1.0100
The purpose of this is to calculate hi/lo ranges around a given asset price and a set of range values.
Because this isn't really anything to do with 10's rounding you have to specify the number of digits that you want to truncate with and then iterate over the "set point" values to find the two closest points to the given input.
This is what I came up with:
Func<double, double[], int, double> lower = (x, sps, d) =>
sps
.Select(sp => sp + Math.Truncate(Math.Pow(10.0, d) * x) / Math.Pow(10.0, d))
.Where(v => v <= x)
.Last();
Func<double, double[], int, double> upper = (x, sps, d) =>
sps
.Select(sp => sp + Math.Truncate(Math.Pow(10.0, d) * x) / Math.Pow(10.0, d))
.Where(v => v >= x)
.First();
My input data is:
var data = new []
{
new
{
setpoints = new double[] { 0, 30, 50, 80, 100 },
digits = -2,
values = new double[] { 2126, 2146, 2183 },
},
new
{
setpoints = new [] { 0.0, 0.003, 0.005, 0.008, 0.01 },
digits = 2,
values = new [] { 1.0026, 1.0046, 1.0083 },
},
};
The results were calculated as:
var results =
data
.SelectMany(
x => x.values,
(x, v) => new
{
value = v,
lower = lower(v, x.setpoints, x.digits),
upper = upper(v, x.setpoints, x.digits)
});
The results I got were as expected:
Related
A sequence of positive integers integerList1 and integerList2 are given. All values in each sequence are different.
Get a set (list of NumberPair values) of all value pairs that satisfy the following conditions:
the first element of the pair belongs to the sequence integerList1,
the second belongs to
integerList2
both elements end with the same digit.
The NumberPair type includes
Value 1, Value 2 fields.
The resulting NumberPair list must be sorted in ascending order
by the first field, and if they are equal, by the second.
Here is an example:
integerList1: new[] { 1, 12, 4, 5, 78 }
integerList2: new[] { 1, 42, 75, 65, 8, 97 }
Expected result:
expected: new[]
{
new NumberPair{Item1 = 1, Item2 = 1},
new NumberPair{Item1 = 5, Item2 = 65},
new NumberPair{Item1 = 5, Item2 = 75},
new NumberPair{Item1 = 12, Item2 = 42},
new NumberPair{Item1 = 78, Item2 = 8}
}
I tried to solve like this
var lastDigitsGroups1 = integerList1.GroupBy(num => num % 10).ToDictionary(kvp => kvp.Key, kvp => kvp.ToList());
var lastDigitsGroups2 = integerList2.GroupBy(num => num % 10).ToDictionary(kvp => kvp.Key, kvp => kvp.ToList());
var intersection = lastDigitsGroups1.Keys.Intersect(lastDigitsGroups2.Keys);
foreach (var item in intersection)
{
var np = new NumberPair { Item1 = lastDigitsGroups1[item].FirstOrDefault(), Item2 = lastDigitsGroups2[item].FirstOrDefault() };
yield return np;
}
However, it should be done only using LINQ and even with one LINQ query.
Join both lists by keys as below:
var result = (from a in integerList1
join b in integerList2 on (a % 10) equals (b % 10)
select new NumberPair { Item1 = a, Item2 = b }
)
.OrderBy(x => x.Item1)
.ThenBy(x => x.Item2)
.ToList();
Or
var result = integerList1.Join(integerList2,
x => x % 10,
y => y % 10,
(x, y) => new { x, y })
.Select(x => new NumberPair { Item1 = x.x, Item2 = x.y })
.OrderBy(x => x.Item1)
.ThenBy(x => x.Item2)
.ToList();
Demo # .NET Fiddle
I honestly don't understand your approach, it does not seem to do what you have mentioned in your conditions. If i just take them as requirement, use Enumerable.Zip:
var result = integerList1.Zip(integerList2, (i1, i2) => new NumberPair{Item1 = i1, Item2 = i2} )
.OrderBy(np => np.Item1)
.ThenBy(np => np.Item2);
I need to find most common digit in array of ints, i would also like to have the highest index(number) of them, so if there is input like [11, 22, 33] then it will return 3 instead of 1. How can I achieve that in easy way?
static uint mostCommonDigit(uint[] n)
{
uint[] numbersFrequency = new uint[10];
foreach(uint i in n)
{
uint a = i;
if (a != 0)
{
while (a>0)
{
uint d = a % 10;
a = a / 10;
numbersFrequency[d] += 1;
}
}
}
uint max = numbersFrequency.Max();
int index = Array.IndexOf(numbersFrequency, max);
return (uint)index;
}
You can use this linq to get your position:
List<int> iii = new List<int> { 11, 22, 33 };
int yyy2 = iii.IndexOf(iii.Last(y => y.ToString().GroupBy(c => c).Select(c => c.Count()).Max() == iii.Select(x => x.ToString().GroupBy(c => c).Select(c => c.Count()).Max()).Max())) + 1;
You could convert each element of the list to a string and concatenate them. Then, you could count the occurences of each character in that string. By sorting by character count and then by character value, higher characters will be sorted first if they occur with the same frequency:
char MostCommonDigit(int[] list)
{
return list.Aggregate("", (i, j) => $"{i}{j}")
.GroupBy(c => c)
.Select(
g => new {
Char = g.Key,
Count = g.Count()
})
.OrderByDescending(x => x.Count)
.ThenByDescending(x => x.Char)
.First().Char;
}
So
Console.WriteLine(MostCommonDigit(new [] { 11, 22, 33 }));
Console.WriteLine(MostCommonDigit(new [] { 111, 22, 33 }));
prints
3
1
As far as "easy" way, here is another LINQ alternative :
static uint mostCommonDigit(uint[] n) =>
(uint)string.Concat(n).GroupBy(c => c).Max(g => (g.Count(), g.Key - '0')).Key
string.Concat converts the array to string (for example "112233").
GroupBy groups the characters in the string by character (for example '1' => ['1', '1'], '2' => ['2', '2']).
The Max part is similar to ordering by the number of items in each group, then by the key of each group, and then getting the last item, but it avoids the sorting. The - '0' part converts the character key to integer.
It is probably few times slower than your solution due to the overhead from LINQ, but the difference will be in milliseconds and not noticable for such small arrays.
I am searching a best performance method to group and count sequences with sorting using LINQ. I will be processing files even bigger than 500 MBs so performance is most important key in that task.
List<int[]> num2 = new List<int[]>();
num2.Add(new int[] { 35, 44 });
num2.Add(new int[] { 200, 22 });
num2.Add(new int[] { 35, 33 });
num2.Add(new int[] { 35, 44 });
num2.Add(new int[] { 3967, 11 });
num2.Add(new int[] { 200, 22 });
num2.Add(new int[] { 200, 2 });
The result have to be like this:
[35, 44] => 2
[200, 22] => 2
[35, 33] => 1
[35, 44] => 1
[3967, 11] => 1
[200, 2 ] => 1
I have done something like this:
Dictionary<int[], int> result2 = (from i in num2
group i by i into g
orderby g.Count() descending
select new { Key = g.Key, Freq = g.Count() })
.ToDictionary(x => x.Key, x => x.Freq);
SetRichTextBox("\n\n Second grouping\n");
foreach (var i in result2)
{
SetRichTextBox("\nKey: ");
foreach (var r in i.Key)
{
SetRichTextBox(r.ToString() + " ");
}
SetRichTextBox("\n Value: " + i.Value.ToString());
}
But it is not working properly. Any help?
For arrays of length 2, this will work.
num2.GroupBy(a => a[0])
.Select(g => new { A0 = g.Key, A1 = g.GroupBy(a => a[1]) })
.SelectMany(a => a.A1.Select(a1 => new { Pair = new int[] { a.A0, a1.Key }, Count = a1.Count() }));
I think that should give you optimal performance; you could also try an .AsParallel() clause after your first Select statement.
This strategy (grouping successively by the n-th element of the arrays) generalises to arrays of arbitrary length:
var dim = 2;
var tuples = num2.GroupBy(a => a[0])
.Select(g => new Tuple<int[], List<int[]>>(new [] { g.Count(), g.Key }, g.Select(a => a.Skip(1).ToArray()).ToList()));
for (int n = 1; n < dim; n++)
{
tuples = tuples.SelectMany(t => t.Item2.GroupBy(list => list[0])
.Select(g => new Tuple<int[], List<int[]>>(new[] { g.Count() }.Concat(t.Item1.Skip(1)).Concat(new [] { g.Key }).ToArray(), g.Select(a => a.Skip(1).ToArray()).ToList())));
}
var output = tuples.Select(t => new { Arr = string.Join(",", t.Item1.Skip(1)), Count = t.Item1[0] })
.OrderByDescending(o => o.Count)
.ToList();
which generates an output of
Arr = "35, 44", Count = 2
Arr = "200, 22", Count = 2
Arr = "35, 33", Count = 1
Arr = "200, 2", Count = 1
Arr = "3967, 11", Count = 1
in your example. I'll let you test it for higher dimensions. :)
You should be able to parallelise these queries without too much difficulties, as the successive groupings are independent.
You can do something like this:
var results = from x in nums
group x by new { a = x[0], b = x[1] } into g
orderby g.Count() descending
select new
{
Key = g.Key,
Count = g.Count()
};
foreach (var result in results)
Console.WriteLine(String.Format("[{0},{1}]=>{2}", result.Key.a, result.Key.b, result.Count));
The trick is to come up with a way to compare the values in the array, instead of the arrays themselves.
The alternative (and possibly better option) would be to transform your data from int[] to some custom type, override the equality operator on that custom type, then just group x by x into g, but if you're really stuck with int[] then this works.
What is the best way to apply SelectMany to get a cross join of three or more sequences using only extension methods? Is there any other way to get a cross join?
Test Data
var a = Enumerable.Range(11, 2);
var b = Enumerable.Range(21, 2);
var c = Enumerable.Range(31, 2);
Expected Result
X Y Z
11 21 31
11 21 32
11 22 31
11 22 32
12 21 31
12 21 32
12 22 31
12 22 32
What I tried
Here's the code that works but I wonder if there's any alternative that'd be easier to read and understand:
var d = a
.SelectMany(rb => b
.SelectMany(rc => c, (y, z) => new { Y = y, Z = z}),
(x, yz) => new { X = x, Y = yz.Y, Z = yz.Z });
The equivalent query expression is good but not what I'm looking for:
var e = from x in a
from y in b
from z in c
select new { X = x, Y = y, Z = z };
You can simplify (even if not much) your SelectMany query in this way:
var res = a.SelectMany(X => b.SelectMany(Y => c.Select(Z => new { X, Y, Z })));
You could use a Join that projects keys that always match.
var e = a.Join(b, x => true, y => true, (x, y) => new { A = x, B = y })
.Join(c, x => true, y => true, (x, y) => new { x.A, x.B, C = y });
Admittedly, it's probably less efficient than your SelectMany version.
I am still not sure if it's ok to make a custom method. But here it is anyway:
public static IEnumerable<IEnumerable<T>> CrossJoin<T>(params IEnumerable<T>[] sequences)
{
IEnumerable<IEnumerable<T>> result = new[] { Enumerable.Empty<T>() };
foreach (var sequence in sequences)
{
result = result.SelectMany(i => sequence.Select(s => i.Concat(new[] { s })));
}
return result;
}
If you add such a method, then the code that matters will become very readable:
var d = CrossJoin(
Enumerable.Range(11, 2),
Enumerable.Range(21, 2),
Enumerable.Range(31, 2)
);
Result:
Console.WriteLine("X Y Z");
foreach( var item in d ) {
Console.WriteLine(String.Join( ",", item ));
}
/*
X Y Z
11,21,31
11,21,32
11,22,31
11,22,32
12,21,31
12,21,32
12,22,31
12,22,32
*/
I have a table (HallPlan) with fields:
Row, Seat, Width, Height, X, Y
And grouped collection by Y:
var ordered = hallPlans.GroupBy(s => s.Y).OrderBy(o => o.Key);
now I want to select max X from all minimum X for each group. For example ordered contains three groups, we have:
first group contains 3 records with X coordinates: 15, 50, 80;
second group contains: 16, 50,80;
and last group contains: 15, 40, 60;
select all min from each groupes: 15, 16, 15
and select max from the above: 16.
How can I do this with LINQ?
Thanks.
You can use the Min and Max functions.
class HallPlan { public int X { get; set; } }
...
var groups = new[]{
new []{new HallPlan{X=15},new HallPlan{X=50},new HallPlan{X=80}},
new []{new HallPlan{X=16},new HallPlan{X=50},new HallPlan{X=80}},
new []{new HallPlan{X=15},new HallPlan{X=40},new HallPlan{X=60}}
};
var maxmin = groups.Select(g => g.Min(h => h.X)).Max(); // result is 16
groups.Select(g => g.Min(h => h.X)) will select the minimun HallPlan.X from each group, and .Max() will then return the biggest value of those.
var allMinX = ordered.Select(g => g.Min(s => s.X));
var minMax = allMinX.Max();