Take a group of numbers put them in 3 new groups evenly - c#

To explain in more detail. I need to take a bunch of numbers and place them in classes/groups. Lets say I have 100 numbers. I need to divide that by the number of classes (n) where n = 3 and place them in three groups with 33, 33, 34 numbers respectively. or if (n) = 4 then it would be 4 classes of 25, 25, 25, 25. They also need to stay grouped from highest to lowest.
I have searched and saw a few things relating to LINQ to do this but I haven't wrapped my head around it.
I figured I could put all the numbers in a list, then find the total number in the index divide it by the number of classes to find out how many need to go into each class. My problem comes in is how to pull the numbers out of the list and place them in there respective groups while maintaining there grouping highest to lowest. Result desired for 3 classes with 15 numbers.
List<int> test = new List<int> { 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86 };
int total_indexes = test.Count + 1;
float classes = (total_indexes / 3);
Classes would equal 5 so it would look like this below
Class A:
100
99
98
97
96
Class B:
95
94
93
92
91
Class C:
90
89
88
87
86

Ok so I wrote this piece of code to give you the result you want:
public List<int[]> Do(int[] numbers, int groupCount)
{
numbers = numbers.OrderByDescending(x => x).ToArray();
var result = new List<int[]>();
var itemsCountInEachGroup = numbers.Length / groupCount;
var remainingCount = numbers.Length % groupCount;
var iterateCount = groupCount;
for (int i = 0; i < iterateCount; i++)
{
var skip = i * itemsCountInEachGroup;
//Last iterate
if (i == iterateCount - 1)
{
var n = numbers.Skip(skip).Take(itemsCountInEachGroup + remainingCount).ToArray();
result.Add(n);
}
else
{
var n = numbers.Skip(skip).Take(itemsCountInEachGroup).ToArray();
result.Add(n);
}
}
return result;
}
Example =>
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
var res = Do(numbers, 5);

This solution is probably not very optimized, and is likely to be improved.
int groupNb = 3, elementNb = 100;
//Populating elements with pseudo-random numbers for demonstration
Random r = new Random();
List<int> elements = new List<int>();
for (int i = 0; i < elementNb; i++)
elements.Add(r.Next(0, 100));
//The groups
List<int>[] groups = new List<int>[groupNb];
//Classifying elements in groups
int currentGroup = 0;
foreach (int value in elements.OrderByDescending(x => x))
{
if (groups[currentGroup] == null)
groups[currentGroup] = new List<int>();
groups[currentGroup].Add(value);
currentGroup = ++currentGroup % groupNb;
}

I did the answer with a PowerShell
open PowerShell ISE
and write the following script:
$Numbers = 100,94,91,90,89,85,84,81,79,74,74,70,95,92,83
$SortedNumbers = $Numbers | sort -Descending
$NumberofClasses = 3
$Countofnumbersinclase = $Numbers.Count / $NumberofClasses
$x = 0
$y = 0
For ($i = 0 ; $i -lt $NumberofClasses ;$i++){
$y = $i+$Countofnumbersinclase-1+$y
$Clasno = $i+1
Write-host "class No $Clasno is " $SortedNumbers[$X..$Y]
$x = $y+1
}
The result as following:
class No 1 is 100 95 94 92 91
class No 2 is 90 89 85 84 83
class No 3 is 81 79 74 74 70
I think exactly s you want and you can add any numbers or any no of classes and it will works

If all groups (with the only exception of the last one) should have equal number of items you can try Linq OrderBy followed by GroupBy:
Code:
using System.Linq;
...
private static List<T>[] Classify<T>(List<T> source, int count)
where T : IComparable<T> {
int size = source.Count / count;
return source
.OrderBy(item => item)
.Select((item, index) => new { item, index })
.GroupBy(pair => Math.Clamp(pair.index / size, 0, count - 1),
pair => pair.item)
.Select(group => group.ToList())
.ToArray();
}
If your C# version doesn't have Math.Clamp you can implement it as
private static int Clamp(int value, int min, int max) {
return value < min ? min :
value > max ? max :
value;
}
Demo:
// Let's split "count" items into "classes" classes
int count = 10;
int classes = 4;
List<int> demo = Enumerable
.Range(1, count)
.ToList();
var result = Classify(demo, classes);
string report = string.Join(Environment.NewLine, result
.Select(list => $"{list.First()} - {list.Last()} ({list.Count} items) : {string.Join(", ", list)}"));
Console.Write(report);
Outcome:
1 - 2 (2 items) : 1, 2
3 - 4 (2 items) : 3, 4
5 - 6 (2 items) : 5, 6
7 - 10 (4 items) : 7, 8, 9, 10

Related

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

How to remove a duplicate number from an array?

Hi I'm working on this simple program that takes 5 numbers from user as long as the numbers are greater than 10 and less than 100. My goal is to remove duplicates numbers an ONLY show the NOT DUPLICATE numbers. Let's say if I enter 23 , 23, 40, 56 , 37 I should only output 40 , 56 , 37. Please help me on this. Thanks in advance. Here's my code:
static void Main(string[] args)
{
int[] arr = new int[5];
for (int i = 0; i < 5; i++)
{
Console.Write("\nPlease enter a number between 10 and 100: ");
int number = Convert.ToInt32(Console.ReadLine());
if (number > 10 && number <= 100)
{
arr[i] = number;
}
else {
i--;
}
}
int[] arr2 = arr.Distinct().ToArray();
Console.WriteLine("\n");
for (int i = 0; i < arr2.Length; i++)
{
Console.WriteLine("you entered {0}", arr2[i]);
}
Console.ReadLine();
}
One way is to group the elements based on input number and filter groups whose count is 1
int[] arr2 = arr.GroupBy(e=>e)
.Where(e=>e.Count() ==1)
.Select(e=>e.Key).ToArray();
Demo
I think you are looking for this:
int[] arr2 = arr.GroupBy(x => x)
.Where(dup=>dup.Count()==1)
.Select(res=>res.Key)
.ToArray();
Input Array : 23 , 23, 40, 56 , 37
Output Array : 40 , 56 , 37
How it Works:
arr.GroupBy(x => x) => give a collection of {System.Linq.GroupedEnumerable<int,int,int>} where x.Key gives you the unique elements.
.Where(dup=>dup.Count()==1)=> Extracts the the KeyValuePairs that contains Values count exactly equal to 1
.Select(res=>res.Key) => will collects the Keys from the above result
In your case, perhaps a combination of LINQ methods would be needed:
int[] arr2;
int[] nodupe = arr2.GroupBy(x => x).Where(y => y.Count() < 2).Select(z => z.Key).ToArray();

Check if string contains a successive pair of numbers

I have one list. I want check if list[i] contains string "6 1". But this code thinks 6 13 24 31 35 contains "6 1". Its false.
6 13 24 31 35
1 2 3 6 1
stringCheck = "6 1";
List<string> list = new List<string>();
list.Add("6 13 24 31 35");
list.Add("1 2 3 6 1");
for (int i=0; i<list.Count; i++)
{
if (list[i].Contains(stringCheck)
{
// its return me two contains, but in list i have one
}
}
But this code thinks 6 13 24 31 35 contains "6 1". Its false. […]
List<string> list = new List<string>();
list.Add("6 13 24 31 35");
list.Add("1 2 3 6 1");
No, it's true because you are dealing with sequences of characters, not sequences of numbers here, so your numbers get treated as characters.
If you really are working with numbers, why not reflect that in the choice of data type chosen for your list?:
// using System.Linq;
var xss = new int[][]
{
new int[] { 6, 13, 24, 31, 35 },
new int[] { 1, 2, 3, 6, 1 }
};
foreach (int[] xs in xss)
{
if (xs.Where((_, i) => i < xs.Length - 1 && xs[i] == 6 && xs[i + 1] == 1).Any())
{
// list contains a 6, followed by a 1
}
}
or if you prefer a more procedural approach:
foreach (int[] xs in xss)
{
int i = Array.IndexOf(xs, 6);
if (i >= 0)
{
int j = Array.IndexOf(xs, 1, i);
if (i + 1 == j)
{
// list contains a 6, followed by a 1
}
}
}
See also:
Finding a subsequence in longer sequence
Find sequence in IEnumerable<T> using Linq

How can I get the coordinates of a location on the grid?

How can I get the coordinates of a location on the grid with the number of one box?
1 2 3 4 5 6
------------------------------
1| 1 2 3 4 5 6
2| 7 8 9 10 11 12
3| 13 14 15 16 17 18
4| 19 20 21 22 23 24
5| 25 26 27 28 29 30
6| 31 32 33 34 35 36
i.e: if I have the number 15, the coordinates are x=3;y=3
I tried developing a function, but it's not working, does someone have an idea?
Thanks you for your help
UPDATE (Formula was wrong):
y = (myNumber - 1) / 6 + 1;
x = (myNumber - 1) % 6 + 1;
UPDATE (Explanation):
Every row contains 6 elements. We define x as remainder when dividing by 6:
x ~ myNumber % 6
and add +1 as the definition range is [1;6].
x ~ myNumber % 6 + 1
But the last element in the row is divisible by 6 without remainder. To consider this we subtract 1 from myNumber before applying the modulo operator:
x = (myNumber - 1) % 6 + 1
e.g. myNumber = 1 => x = 1; myNumber = 6 => x = 6; myNumber = 7 => x = 1; myNumber = 12 => x = 6;
The number of rows is called y and is proportional to the integer division by 6:
y ~ myNumber / 6
But again we we have to consider that we are not starting at 0 but at 1:
y ~ myNumber / 6 + 1
And again there is the 'left shift' as the last element of each row can be divided by 6 without remainder. So we subtract 1 from myNumber before dividing to reflect this:
y = (myNumber - 1) / 6 + 1
You could use a jagged array:
private static readonly int[][] matrix = new int[6][];
// ...
matrix[0] = new int[] { 1, 2, 3, 4, 5, 6 };
matrix[1] = new int[] { 7, 8, 9, 10, 11, 12 };
matrix[2] = new int[] { 13, 14, 15, 16, 17, 18 };
matrix[3] = new int[] { 19, 20, 21, 22, 23, 24 };
matrix[4] = new int[] { 25, 26, 27, 28, 29, 30 };
matrix[5] = new int[] { 31, 32, 33, 34, 35, 36 };
and Linq to find the x and y values:
int num = 15;
var matches = matrix
.Select((yArr, index) => new { yArr, yPos = index + 1 })
.Where(y => y.yArr.Contains(num))
.Select(y => new
{
X = (y.yArr.Select((x, i) => new { x, i })
.First(x => x.x == num).i) + 1,
Y = y.yPos,
});
if(matches.Any())
{
var firstMatch = matches.First();
int x = firstMatch.X;
int y = firstMatch.Y;
}
DEMO

LINQ to find the closest number that is greater / less than an input

Suppose I have this number list:
List<int> = new List<int>(){3,5,8,11,12,13,14,21}
Suppose that I want to get the closest number that is less than 11, it would be 8
Suppose that I want to get the closest number that is greater than 13 that would be 14.
The numbers in list can't be duplicated and are always ordered. How can I write Linq for this?
with Linq assuming that the list is ordered I would do it like this:
var l = new List<int>() { 3, 5, 8, 11, 12, 13, 14, 21 };
var lessThan11 = l.TakeWhile(p => p < 11).Last();
var greaterThan13 = l.SkipWhile(p => p <= 13).First();
EDIT:
As I have received negative feedback about this answer and for the sake of people that may see this answer and while it's accepted don't go further, I explored the other comments regarding BinarySearch and decided to add the second option in here (with some minor change).
This is the not sufficient way presented somewhere else:
var l = new List<int>() { 3, 5, 8, 11, 12, 13, 14, 21 };
var indexLessThan11 = ~l.BinarySearch(10) -1;
var value = l[indexLessThan11];
Now the code above doesn't cope with the fact that the value 10 might actually be in the list (in which case one shouldn't invert the index)! so the good way is to do it:
var l = new List<int>() { 3, 5, 8, 11, 12, 13, 14, 21 };
var indexLessThan11 = l.BinarySearch(10);
if (indexLessThan11 < 0) // the value 10 wasn't found
{
indexLessThan11 = ~indexLessThan11;
indexLessThan11 -= 1;
}
var value = l[indexLessThan11];
I simply want to note that:
l.BinarySearch(11) == 3
//and
l.BinarySearch(10) == -4;
Use Array.BinarySearch - no need for LINQ or visiting on average half the elements to find your target.
There are also a variety of SortedXXX classes that may be suitable for what you're doing [that will have such efficient O(log N) searches built-in]
You can do this using a binary search. If your searching for 11, well obviously you'll get the index your after. If you search for 10 and use the bitwise complement of the result, you'll get the closest match.
List<int> list = new List<int>(){3,5,8,11,12,13,14,21};
list.Sort();
int index = list.BinarySearch(10);
int found = (~index)-1;
Console.WriteLine (list[found]); // Outputs 8
The same goes searching in the other direction
int index = list.BinarySearch(15);
Console.WriteLine("Closest match : " + list[+~index]); // Outputs 21
Binary searches are also extremely fast.
closest number below 11:
int someNumber = 11;
List<int> list = new List<int> { 3, 5, 8, 11, 12, 13, 14, 21 };
var intermediate = from i in list
where i < someNumber
orderby i descending
select i;
var result = intermediate.FirstOrDefault();
closest number above 13:
int someNumber = 13;
List<int> list = new List<int> { 3, 5, 8, 11, 12, 13, 14, 21 };
var intermediate = from i in list
where i > someNumber
orderby i
select i;
var result = intermediate.FirstOrDefault();
This is my answer
List<int> myList = new List<int>() { 3, 5, 8, 11, 12, 13, 14, 21 };
int n = 11;
int? smallerNumberCloseToInput = (from n1 in myList
where n1 < n
orderby n1 descending
select n1).First();
int? largerNumberCloseToInput = (from n1 in myList
where n1 > n
orderby n1 ascending
select n1).First();
var list = new List<int> {14,2,13,11,5,8,21,12,3};
var tested = 11;
var closestGreater = list.OrderBy(n => n)
.FirstOrDefault(n => tested < n); // = 12
var closestLess = list.OrderByDescending(n => n)
.FirstOrDefault(n => tested > n); // = 8
if (closestGreater == 0)
System.Diagnostics.Debug.WriteLine(
string.Format("No number greater then {0} exists in the list", tested));
if (closestLess == 0)
System.Diagnostics.Debug.WriteLine(
string.Format("No number smaler then {0} exists in the list", tested));
Here is my way hope this helps somebody!
List<float> list = new List<float> { 4.0f, 5.0f, 6.0f, 10.0f, 4.5f, 4.0f, 5.0f, 6.0f, 10.0f, 4.5f, 4.0f, 5.0f, 6.0f, 10.0f };
float num = 4.7f;
float closestAbove = list.Aggregate((x , y) => (x < num ? y : y < num ? x : (Math.Abs(x - num)) < Math.Abs(y - num) ? x : y));
float closestBelow = list.Aggregate((x , y) => (x > num ? y : y > num ? x : (Math.Abs(x - num)) < Math.Abs(y - num) ? x : y));
Console.WriteLine(closestAbove);
Console.WriteLine(closestBelow);
This means you dont have to order the list
Credit: addapted from here: How to get the closest number from a List<int> with LINQ?
The Expanded Code
float closestAboveExplained = list.Aggregate((closestAbove , next) => {
if(next < num){
return closestAbove;
}
if(closestAbove < num){
return next;
}
else{
if(Math.Abs(closestAbove - num) < Math.Abs(next - num)){
return closestAbove;
}
}
return next;
});
You can use a query for this such as:
List<int> numbers = new List<int>() { 3, 5, 8, 11, 12, 13, 14, 21 };
List<int> output = (from n in numbers
where n > 13 // or whatever
orderby n ascending //or descending
select n).ToList();

Categories

Resources