Function issues with sorting sizes - c#

So im writing a small little c# console app, which is supposed to imitate a shopping system of sorts.
All the products are sold in Packs (not individually), and the Pack sizes are like 4 items, 10 items or 15 items.
Given a Qty size of like 28, it should return a result of something like:
2 x 10 Packs + 2 x 4 Packs.
Or given a Qty size of like 25, it should return a result of something like:
1 x 15 Packs + 1 x 10 Packs
And the function needs to be efficient, so it returns the least amount of packs.
(obviously ignore scenerio's where it might be an Qty of 3 - as there are no pack sizes that small)
this is my code at the moment:
qty = 28, LargePack = 15, MediumPack = 10, SmallPack = 4
double total = 0.00;
int tmpQty = qty;
while (tmpQty != 0)
{
if ((tmpQty >= LargePack) && ((tmpQty % LargePack) % 1) == 0)
{
tmpQty -= LargePack;
lg += 1;
total =+ (lg * LargePrice);
}
else if ((tmpQty >= MediumPack))
{
tmpQty -= MediumPack;
md += 1;
total =+ (md * MediumPrice);
}
else if ((SmallPack !=0) && (tmpQty >= SmallPack) && (tmpQty < MediumPack))
{
tmpQty -= SmallPack;
sm += 1;
total =+ (sm * SmallPrice);
}
}
My idea here - because with a qty of like 28, i need it to skip the first IF statement, i thought i'd do a check of whether tmpQty (28) / LargePack (15) was an integer number - which it shouldn't be, then it would go to the 2nd IF statement. But for some reason the formula:
(tmpQty % LargePack) %1 == 0
always equals 0 - so its True... when it should be false.
Or if anyone has a better way to work out Pack sizes, im open to suggestions.
Note - Pack sizes aren't defined here, as various different products use the same sorting process.

Your biggest issue is you don't understand what % does. It doesn't return a decimal. It works like the "remainder" from gradeschool division. 7 % 5 will return 2. If you want to keep your same logic (expecting a percentage) then you need to divide but your variables will need to be doubles or floats. If not, C# will cast the result to an integer, which has no decimal. I hope that helps.

(Anything % 1) always returns 0. That's why your condition is not working.
Try this
(tmpQty % LargePack) >= 0

Use:
if ((tmpQty >= LargePack) && ((tmpQty % LargePack) == 0))
This will return true only for values that are exactly divisible by LargePack, so in this case 28 would fail and move on.

Here is a brute force approach. At first we need a method to generate all combinations of an array of sequences. It is called CartesianProduct, and an implementation is shown below:
public static IEnumerable<T[]> CartesianProduct<T>(IEnumerable<T>[] sequences)
{
IEnumerable<IEnumerable<T>> seed = new[] { new T[0] };
return sequences.Aggregate(
seed,
(accumulator, sequence) => accumulator
.SelectMany(_ => sequence, (product, i) => product.Append(i))
).Select(product => product.ToArray());
}
We will need this method because we need all possible combinations of pack sizes and quantities. For example for pack sizes [5, 8] and quantity 21, the combinations we want to produce are these:
0*5 + 0*8 = 0
0*5 + 1*8 = 8
0*5 + 2*8 = 16
1*5 + 0*8 = 5
1*5 + 1*8 = 13
1*5 + 2*8 = 21
2*5 + 0*8 = 10
2*5 + 1*8 = 18
2*5 + 2*8 = 26
3*5 + 0*8 = 15
3*5 + 1*8 = 23
3*5 + 2*8 = 31
4*5 + 0*8 = 20
4*5 + 1*8 = 28
4*5 + 2*8 = 36
The last step will be to select the best combination. Here is the method that makes all these to happen:
private static (int PackSize, int Quantity)[] GetBestCombination(
int[] packSizes, int quantity)
{
var sequences = packSizes
.Select(p => Enumerable.Range(0, (quantity / p) + 1)
.Select(q => p * q))
.ToArray();
var cartesianProduct = CartesianProduct(sequences);
int[] selected = null;
int maxValue = Int32.MinValue;
foreach (var combination in cartesianProduct)
{
int sum = combination.Sum();
if (sum > quantity) continue;
if (sum > maxValue)
{
maxValue = sum;
selected = combination;
}
}
if (selected == null) return null;
return selected.Zip(packSizes, (sum, p) => (p, sum / p)).ToArray();
}
Lets test it:
var bestCombo = GetBestCombination(new[] { 5, 8 }, 21);
foreach (var pair in bestCombo)
{
Console.WriteLine($"{pair.Quantity} x {pair.PackSize}");
}
Output:
1 x 5
2 x 8

Related

Get the amount of binary numbers that meet a criteria and are less than "x"

I am trying to get the count (just a number, not a list) of binary numbers that contain exactly 3 ones and that are less than 1000000000 ie: 10011, 100000011 and so on.
The code below works for integers, but how can I make it work with binary?
static void Main(string[] args)
{
int con = 0;
for (int i = 0; i < 1000000000; i++)
{
string test = i.ToString();
int count = test.Split('1').Length - 1;
if (count == 3)
{
con++;
}
}
Console.WriteLine(con);
}
And for your continued education, here's another way to solve the problem. We wish to know how many binary strings there are with exactly on on bits and off off bits.
There are some easy problems to solve in there. N(on, off) is equal to one if on and off are both zero, because the only solution is the empty string. And if either is zero then the answer is one, because the string that is all zeros or all ones is unique.
Let's start tabulating this in a table.
on
0 1 2 3 4 5
+---------------------
o 0| 1 1 1 1 1 1
f 1| 1
f 2| 1
3| 1
4| 1
5| 1
6| 1
Now what should go at (1, 1)? Well, the number of binary strings that have one on and one off bit is equal to the number of such strings that start with one, plus the number of such strings that start with zero. So we have:
N(1, 1) = N(1, 0) + N(0, 1) = 2
What about N(2, 1) ? Same deal:
N(2, 1) = N(1, 1) + N(2, 0) = 3
And we can see that similarly N(x, 1) = N(1, x) = x + 1. Fill in the array:
on
0 1 2 3 4 5
+---------------------
o 0| 1 1 1 1 1 1
f 1| 1 2 3 4 5 6
f 2| 1 3
3| 1 4
4| 1 5
5| 1 6
6| 1 7
in general for on, off not zero:
N(on, off) = N(on - 1, off) + N(on, off - 1)
which means that we can fill in this entire array by repeatedly applying this rule. Can you write a program which does so?
Once you do, you can simply read your answer out of the array at [6, 3].
Have you seen this pattern in this array before? It has a name. Hint: you probably have not seen it laid out as a square.
The simplest way to alter your code would be:
static void Main(string[] args)
{
int con = 0;
for (int i = 0; i < 512; i++)
{
string test = Convert.ToString(i, 2);
int count = test.Split('1').Length - 1;
if (count == 3)
{
con++;
}
}
Console.WriteLine(con);
}
This could be done as a pure mathematical equation though:
9! / (6!*3!) = 84
For your amusement and education, consider the following:
static IEnumerable<string> Combinations(int on, int off)
{
if (on == 0 && off == 0)
yield return "";
if (on > 0)
foreach(var s in Combinations(on - 1, off))
yield return "1" + s;
if (off > 0)
foreach(var s in Combinations(on, off - 1))
yield return "0" + s;
}
Study this implementation: it yields a sequence of binary strings with on bits on and off bits off. Do you see how it does so?
Plainly calling .Count() on this thing solves your problem, though such a solution is enormously less efficient than simply doing the math.
I present this for your study because a recursive enumerator such as this one is a powerful tool when investigating permutations and combinations.
A solution without resorting to string representations:
static int CountBits(int i)
{
var current = i;
var bits = 0;
while (current != 0)
{
if ((current & 1) == 1)
{
bits += 1;
}
current >>= 1;
}
return bits;
}
With this helper method, the count is easy:
var count = Enumerable.Range(0, 0b1000000000)
.Count(i => CountBits(i) == 3);
And the answer is 84.
Long code (grumpy style)
var con = 0;
for (var i = 0; i < 10000; i++)
{
var test = i.ToString();
if (test.Count(x => x == '1') == 3)
{
con++;
}
}
Console.WriteLine(con);
Shorter (more mature)
var con = Enumerable.Range(0, 10000)
.Select(x => $"{x:00000}")
.Count(x =>
x.Count(c => c == '1') == 3
);
Console.WriteLine(con);
PS: They both seem to have the same performance.

C# : Currency Rendering Solution

i have to find a solution for this task :
"Supermarkets are increasingly equipped with automatic cash registers. Most of these funds only accept payment by credit card, although a significant proportion of consumers still pay cash (with banknotes and coins).
One of the problems encountered with the cash payment is the rendering of money: how to make a given sum optimally, ie with the minimum number of coins and notes? This is a problem for every one of us every day, let alone the automatic cash registers.
In this exercise, i m asked to try to find an optimal solution to make change in a specific case: when an automatic cash register contains only 2 € coins, 5 € and 10 € banknotes.
To simplify the problem, we will consider that all these coins and bills are available in unlimited quantities.
Here are some examples of currency:
The return of money is expressed by a Currency object. This item has 3 properties: piece2, billet5 and billet10 which respectively store the number of coins of 2 €, 5 € tickets and 10 € tickets.
For example, if we take example 2 of the table (6 €), we should get a Money object with:
Piece2 worth 3 (3 pieces of 2 €)
Ticket5 is worth 0 (no ticket of 5 €)
Ticket10 is worth 0 (no ticket of 10 €)
Task : Implement the MonnaieOptimale(int s) method that returns a Currency object containing the coins and notes whose sum is s. If it is impossible to return the currency (as in example 1), return null.
To get it best the solution will always have to make change when possible and with the minimum number of coins and tickets."
Data: s is always a strictly positive integer (long) less than or equal to 9223372036854775807
the general structure of the solution is the following:
Suggestions ??
Firstly, simple greedy approach (always get the largest i.e.10) is not working here, as the system is not canonical, consider s = 11, you will get "Impossible" if you use coin 10, but it is possible with { 5, 2, 2, 2 }.
I have a slightly different idea here, using the parity of odd /even numbers:
If s is even, do the greedy approach but never uses coin 5
If s is odd, keep using coin 5 until s becomes even, then do step 1.
For eg: s = 18, solution = { 10, 2, 2, 2, 2 }. s = 23, solution = { 5, 10, 2, 2, 2, 2 }
Why it works, is because one can notice, for coin system { 2, 10 }, you can make any even s using greedy approach (it is canonical to even domain).
Plus the fact that for any odd number minus another odd number will make a even number, so for odd s we can use one coin 5 to make s even to reduce the problem.
From this logic, I think at most one 5 coin would be used for any s, and only when s = 1 or 3 the answer is "Impossible"
i solve it mannually like this:
public static Monnaie MonnaieOptimale(long s)
{
if (s<10)
{
if (s%2 =0) {Monnaie.billet10 = 0 ; Monnaie.billet5 = 0 ; Monnaie.piece2 = s/2 }
else if (s == 5) {Monnaie.billet10 = 0 ; Monnaie.billet5 = 1 ; Monnaie.piece2=0 }
else if (s == 7) {Monnaie.billet10 = 0 ; Monnaie.billet5 = 1 ; Monnaie.piece2=1 }
else Monnaie.billet10 = 0 ; Monnaie.billet5 = 1 ; Monnaie.piece2= 2
}
else
{
if (s%2 =0)
{
Monnaie.billet10 = Math.Floor(s/10);
if ((Monnaie.billet10 * 10) != s) {Monnaie.billet5 = 0 ; Monnaie.piece2= (s%10)/2 }
}
else
{
private lastDigit = s%10;
if (lastDigit ==1){Monnaie.billet10 = ((s/10)-1) ; Monnaie.billet5 = 0 ; Monnaie.piece2=3 }
else if (lastDigit ==3){Monnaie.billet10 = ((s/10)-1) ; Monnaie.billet5 = 0 ; Monnaie.piece2=4 }
else if (lastDigit ==5){Monnaie.billet10 = (s/10) ; Monnaie.billet5 = 1 ; Monnaie.piece2=0 }
else if (lastDigit ==7){Monnaie.billet10 = (s/10) ; Monnaie.billet5 = 1 ; Monnaie.piece2=1}
else {Monnaie.billet10 = (s/10) ; Monnaie.billet5 = 1 ; Monnaie.piece2=2}
}
}
}
using System;
public class Program {
public static void Main() {
int re10 = 0, re5 = 0, re2 = 0, s = 197;
while (s / 2 != 0) {
while (s / 5 != 0) {
while (s / 10 != 0) {
if (s % 10 % 5 == 0) {
re10 = s / 10;
s = s % 10;
}
else {
if (s / 10 > 1) {
re10 = s / 10 - 1;
s = s % 10 + 10;
}
break;
}
}
if (s % 5 % 2 == 0) {
re5 = s / 5;
s = s % 5;
} else
if (s / 5 > 1) {
re5 = s / 5 - 1;
s = s % 5 + 5;
}
break;
}
re2 = s / 2;
s = s % 2;
}
if(re5==3){re10+=1;re5-=2;}
Console.WriteLine("billet 10 :" + re10);
Console.WriteLine("billet 5 :" + re5);
Console.WriteLine("billet 2 :" + re2);
Console.WriteLine("Total : "re2 * 2 + re10 * 10 + re5 * 5);
}
}
billet 10 :18
billet 5 :3
billet 2 :1
Total : 197

Why aren't 10 and 100 considered Kaprekar numbers?

I'm trying to do the Modified Kaprekar Numbers problem (https://www.hackerrank.com/challenges/kaprekar-numbers) which describes a Kaprekar number by
Here's an explanation from Wikipedia about the ORIGINAL Kaprekar
Number (spot the difference!): In mathematics, a Kaprekar number for a
given base is a non-negative integer, the representation of whose
square in that base can be split into two parts that add up to the
original number again. For instance, 45 is a Kaprekar number, because
45² = 2025 and 20+25 = 45.
and what I don't understand is why 10 and 100 aren't Kaprekar numbers.
10^2 = 1000 and 10 + 00 = 10
Right?
So my solution
// Returns the number represented by the digits
// in the range arr[i], arr[i + 1], ..., arr[j - 1].
// If there are no elements in range, return 0.
static int NumberInRange(int[] arr, int i, int j)
{
int result = 0;
for(; i < j; ++i)
{
result *= 10;
result += arr[i];
}
return result;
}
// Returns true or false depending on whether k
// is a Kaprekar number.
// Example: IsKaprekar(45) = true because 45^2=2025 and 20+25=45
// Example: IsKaprekar(9) = false because the set of the split
// digits of 7^2=49 are {49,0},{4,9} and
// neither of 49+0 or 4+9 equal 7.
static bool IsKaprekar(int k)
{
int square = k * k;
int[] digits = square.ToString().Select(c => (int)Char.GetNumericValue(c)).ToArray();
for(int i = 0; i < digits.Length; ++i)
{
int right = NumberInRange(digits, 0, i);
int left = NumberInRange(digits, i, digits.Length);
if((right + left) == k)
return true;
}
return false;
}
is saying all the Kaprekar numbers between 1 and 100 are
1 9 10 45 55 99 100
whereas the "right" answer is
1 9 45 55 99
In 100+00 the right is 00, which is wrong because in a kaprekar number the right may start with zero (ex: 025) but cannot be entirely 0.
Therefore you can put a condition in the loop that
if(right==0)
return false;
The reason is because 10 x 10 = 100. Then you substring the right part with a length equals d = 2, that is digit count of original value (10), then the left part would be 1.
So l = 1 and r = 00, l + r = 1, that is not equals to 10.
The same for 100. 100 x 100 = 10000. l = 10, r = 000, so l + r = 10 not equal 100.
Here is my solution in JAVA.
static void kaprekarNumbers(int p, int q) {
long[] result = IntStream.rangeClosed(p, q).mapToLong(Long::valueOf)
.filter(v -> {
int d = String.valueOf(v).length();
Long sq = v * v;
String sqSt = sq.toString();
if (sqSt.length() > 1) {
long r = Long.parseLong(sqSt.substring(sqSt.length() - d));
long l = Long.parseLong(sqSt.substring(0, sqSt.length() - d));
return r + l == v;
} else return v == 1;
}).toArray();
if (result.length > 0) {
for (long l : result) {
System.out.print(l + " ");
}
} else {
System.out.println("INVALID RANGE");
}
}
How about something like this.
static bool IsKaprekar(int k)
{
int t;
for (int digits = new String(k).length(); digits > 0; digits--, t *= 10);
long sq = k * k;
long first = sq / t;
long second = sq % t;
return k == first + second;
}
find a number to divide and mod the square with in order to split it. This number should be a factor of 10 based on the number of digits in the original number.
calculate the square.
split the square.
compare the original to the sum of the splits.

Formula to produce 1 for positive integers and 0 otherwise

I have a function (f) the takes a number of items (n) and a number of columns (c) and returns the optimal layout as an array of items per column. I define optimal as being as square as possible. So f(4,4) would return [4,4,4,4], f(17,4) would return [5,4,4,4], and f(1,4) would return [1,0,0,0]. My function works correctly in all my tests, but I am looking to alter it. My desire to do this is not because I am looking increase performance. I just want to do this, because I am experimenting and want to learn different techniques.
Here is the code:
public static int[] f(int n, int c){
int[] a = new int[c];
if(c>0 && n>=0){
int opt = (n-(n%c))/c;
n = n - (opt*c);
for(int i = 0;i<a.Length;i++){
a[i] = opt;
if(n>0){
a[i]++;
n--;
}
}
}
return a;
}
The function works by first determining the optimal number of items per col:
int opt = (n-(n%c))/c;
So f(17,4) would yield 4, f(19,4) would also yield 4, and f(3,4) would yield 0. Then the reminder is calculated:
n = n - (opt*c);
I then loop through the array (of length c) and assign a[i] equal to the optimal value. Finally, if the reminder is greater than 0 I add 1 to a[i]. This equally distributes the reminder across the array. This is the part I would like to alter.
Instead of checking if(n>0) and adding 1 to the array is there a formula I could use that might look like:
a[i] = opt + n*?????;
So n*??? would always equal 1 if n is greater than 0 and 0 if n is 0 or less?
The simple answer to your question is to use an expression with the conditional operator:
a[i] = opt + (n > 0 ? 1 : 0);
(n > 0 ? 1 : 0) will be 1 if n is greater than 0, and 0 otherwise.
On that note, there is a clearer and more concise way to implement your algorithm.
Determine the total number of items that can be distributed evenly between the slots (call this average). This has the value n / c (using integer division).
Determine the remainder that would be left after those are evenly distributed (call this remainder). This has the value n % c.
Put the value average + 1 in the first remainder slots, and put average in the rest.
The implementation for this would be:
public static int[] Distribute(int total, int buckets)
{
if (total < 0) { throw new ArgumentException("cannot be less than 0", "total"); }
if (buckets < 1) { throw new ArgumentException("cannot be less than 1", "buckets"); }
var average = total / buckets;
var remainder = total % buckets;
var array = new int[buckets];
for (var i = 0; i < buckets; i++)
{
array[i] = average + (i < remainder ? 1 : 0);
}
return array;
}
And the obligatory Linq version:
public static int[] DistributeLinq(int total, int buckets)
{
if (total < 0) { throw new ArgumentException("cannot be less than 0", "total"); }
if (buckets < 1) { throw new ArgumentException("cannot be less than 1", "buckets"); }
var average = total / buckets;
var remainder = total % buckets;
return Enumerable.Range(1, buckets)
.Select(v => average + (v <= remainder ? 1 : 0))
.ToArray();
}
If you want to use a formula:
Math.Max(n - Math.Abs(n - 1), 0)
should do the trick.
Your code should look like:
a[i] = opt + Math.Max(n - Math.Abs(n - 1), 0)
Another option for a formula would be
Math.Max(Math.Sign(n), 0)
If you are looking for a mathematical formula, I'm not sure you're going to find it as the function is discontinuous at n = 0.
How about a simple function which outputs int on a bool expression?
int IsPositive(int number)
{
//if number is > 0 return integer one (1), else return integer zero (0)
return number > 0 ? 1 : 0;
}
You can then use this in your code as such:
a[i] = opt + IsPositive(n);
//opt + 1 if n > 0, opt + 0 if n <= 0
Update: per your comment, you can just move the evaluation inline:
a[i] = opt + (n > 0 ? 1 : 0);
As an aside: you should make #BradleyDotNET's comment one of your programming mottos.

Spread number of items equally across hours

I have a variable number of items that I want to spread across a variable amount of hours. The issue I'm having is how to distribute the remainder so that the space between the "overhang" is as equal as possible. For example, if I have 13 items (X) spread across 5 hours I want to end up with
Hours: 1 2 3 4 5
---------------------------------
x x x x x
x x x x x
x x x
I'm not sure if I'm overthinking this. I'm currently checking if the number of items is greater than the number of hours. If that's true, I'm dividing (number of items/number of hours). Then I think that I have to divide (number of hours/remainder)... But for the above example: 5/3=1.6, which rounds to 2. I think that I have to use Math.Floor somehow, but I'm currently not really sure how.
For 4 items across 5 hours, I'd like to end up with the Xs
For 2 items with the Ys
For 1 item with the Zs
1 2 3 4 5
------------------------
x x x x
y y
z
The number of items and the number of hours are variable.
Okay, I think I'm currently on the right track. I'm now trying to split the bins in half and put one of the remainder in the center-bin. This repeats recursively until the remainder is 0.
EDIT: Fixed issue with even hours and items.
This is a hard problem and below is the solution. I've written solution for a completely generic problem so it works for arbitrary hours and number of items. Here's the example outputs:
Items=10, Hours=14
XX XX XX XX XX
Items=11, Hours=14
XX XXXXX XX XX
Items=12, Hours=14
XX XXXXXXXX XX
Items=16, Hours=13
XXXXXXXXXXXXX
XXX
Items=17, Hours=13
XXXXXXXXXXXXX
X X X X
Items=18, Hours=13
XXXXXXXXXXXXX
X XXX X
Items=19, Hours=13
XXXXXXXXXXXXX
X X X X X X
Items=20, Hours=13
XXXXXXXXXXXXX
X X XXX X X
Items=21, Hours=13
XXXXXXXXXXXXX
X XX X X XX X
Here's how below solution works:
Number of filled lines are trivial which you can get it by (items/hours) * hours.
The last line is where all the magic is required.
When number of remaining items are odd we want to turn on the center. If number of hours are also odd then center is well defined but otherwise we are out of luck and we would have some "imbalance" in that case.
For even items we make them in to pairs and distribute each pair in the order of balanced binary tree. This basically means we first put each pair at the end. Then next pair half way through and recursively follow the pattern. This might be the most difficult part to understand so paper and pen is recommended :).
And here's the code:
static void Main(string[] args)
{
var hours = 13;
for (var items = 16; items < 22; items++)
PrintDistribution(items, hours);
}
private static void PrintDistribution(int items, int hours)
{
Console.WriteLine(string.Format("\nItems={0}, Hours={1}", items, hours));
for (var i = 0; i < (items / hours) * hours; i++)
{
Console.Write('X');
if ((i + 1) % hours == 0)
Console.WriteLine();
}
var line = new StringBuilder(new string(' ', hours));
var remaining = items % hours;
var evens = remaining / 2;
var odd = remaining - (evens * 2);
var seq = BinaryTreeSequence(hours / 2).GetEnumerator();
for (var i = 0; i < evens; i++)
{
seq.MoveNext();
line[seq.Current] = 'X';
line[hours - seq.Current - 1] = 'X';
}
if (odd > 0)
if (hours % 2 == 0)
{
seq.MoveNext();
line[seq.Current] = 'X';
}
else
line[hours / 2] = 'X';
Console.WriteLine(line);
}
public static IEnumerable<int> BinaryTreeSequence(int count)
{
if (count > 1)
yield return count - 1;
if (count > 0)
yield return 0;
var seqQueue = new Queue<Tuple<int, int, int>>();
Enqueue(seqQueue, 0, count - 1);
for (var seqIndex = count - 2; seqIndex > 0; seqIndex--)
{
var moreNeeded = seqQueue.Count < seqIndex;
var seq = seqQueue.Dequeue();
yield return seq.Item1;
if (moreNeeded)
{
Enqueue(seqQueue, seq.Item1, seq.Item3);
Enqueue(seqQueue, seq.Item2, seq.Item1);
}
}
}
private static void Enqueue(Queue<Tuple<int, int, int>> q, int min, int max)
{
var midPoint = (min + max) / 2;
if (midPoint != min && midPoint != max)
q.Enqueue(Tuple.Create(midPoint, min, max));
}
Here's an approximate solution. It returns tuples with the zero-based index, and the item. (I assumed the items might be important, and not just dummy values like your xs) It doesn't choose the optimal spacing in some cases, but I think it'll always be close (i.e. gaps no more than 1 larger than necessary), and always return the correct number of items.
public static IEnumerable<Tuple<int, T>> SplitItems<T>(IEnumerable<T> items, int count)
{
var itemList = items.ToList();
int lastRowCount = itemList.Count % count;
int wholeRowItemCount = itemList.Count - lastRowCount;
// return full rows: 0 <= i < wholeRowCount * count
for (int i = 0; i < wholeRowItemCount; i++)
{
yield return Tuple.Create(i % count, itemList[i]);
}
if (lastRowCount > 0)
{
//return final row: wholeRowCount * count <= i < itemList.Count
double offset = (double)count / (lastRowCount + 1);
for (double j = 0; j < lastRowCount; j++)
{
int thisIntPos = (int)Math.Round(j * count / (lastRowCount + 1) + offset, MidpointRounding.AwayFromZero);
yield return Tuple.Create(thisIntPos, itemList[wholeRowItemCount + (int)j]);
}
}
}
As an example of how to use it:
Console.WriteLine(string.Join("\r\n", SplitItems(Enumerable.Range(1, 12), 5)));
// prints
(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(0, 6)
(1, 7)
(2, 8)
(3, 9)
(4, 10)
(2, 11)
(3, 12)
(this is suboptimal because the last line has items at 2-3 and empty spaces/gaps at 0-1 and 4, while your solution with ys only has gaps of size 1)
Also, though it doesn't match your example (which would be 0, 2, 4 in my zero-based indexing), the following example satisfies the algorithm that you've defined so far, since it's minimized the gap size. (1-size gaps at indices 0 and 2, instead of yours, which has the gaps at 1 and 3) If 0, 2, 4 is indeed better than 1, 3, 4, you need to decide why exactly, and add that to your algorithm definition.
Console.WriteLine(string.Join("\r\n", SplitItems(Enumerable.Range(1, 3), 5)));
// prints
(1, 1)
(3, 2)
(4, 3)
Actually, this is a sort of restricted partition problem. For dividing d items across h hours, you want to find a partition of h-d with no more than h-d parts where max(parts) is the smallest it can be. E.g. dividing 2 items among 5 hours: the optimal solution is 1+1+1, because it has no more than 3 parts, and max(parts) == 1, which is the best you can do. As an example without a single solution, 3 items among 5 hours has 1+1, but there are different ways to arrange it, including 0,2,4, 1,3,4, and 0,2,3.

Categories

Resources