I am trying to solve the concept of filling every item in a list until every item has reached a ceiling. Once every item has reached a ceiling/limit any remaining excess values is then distributed once more starting from the first item of the list. Examples after the current breakdown.
I believe I have a solution for the first component of the problem, distributing values - however I am unsure of the solution to dealing with excess values.
A detailed example:
I have a list of boxes (10 boxes) and a value of 33 products, I
want to distribute the products one at a time across these boxes until
a limit ceiling for each box is set (5 products). Every box must be
filled to 5 before I pick my first box again and start one at a time
to add the remaining excess.
In the table below, init is the pre-existing values stored in each box. The next columns show distribution across the boxes for values of 25,33, and finally 40. In 40 you can see more clearly how priority of filling in the emptiest boxes preceeds eventually filling every other box in.
init
25
33
40
2
4
5
6
1
3
5
6
2
4
5
5
0
2
4
5
-1
1
3
5
2
4
5
5
3
5
5
5
0
2
4
5
2
4
5
5
1
3
4
5
My current logic is this:
class Box
{
public float amount;
}
public static void Init()
{
// Create a list of boxes.
List<Box> boxes = new List<Box>
{
// Add boxes to the list.
new Box() { amount = 2 },
new Box() { amount = 1 },
new Box() { amount = 2 },
new Box() { amount = 0 },
new Box() { amount = -1 },
new Box() { amount = 2 },
new Box() { amount = 3 },
new Box() { amount = 0 },
new Box() { amount = 2 },
new Box() { amount = 1 }
};
Distribute(boxes, 25, 2);
}
static void Distribute(List<Box> boxes, float totalValue, float maxDistribution)
{
float ceiling = 5; //The "soft limit" for each box.
List<Box> currentBoxes = boxes;
List<Box> criticalBoxes = new List<Box>();
int distribution = (int)(totalValue / maxDistribution); //amount of times needed for even distribution.
int currentBoxIndex = 0;
for (int i = 0; i < distribution; i++)
{
//If we have room, add distribution, else move to a "at capacity list"
if (currentBoxes[currentBoxIndex].amount + maxDistribution < ceiling)
{
currentBoxes[currentBoxIndex].amount += maxDistribution; //Distribute the maximum allowed per loop
} else
{
criticalBoxes.Add(currentBoxes[currentBoxIndex]);
currentBoxes.RemoveAt(currentBoxIndex);
}
//Status of loop.
if(currentBoxIndex == currentBoxes.Count)
{
currentBoxIndex = 0;
} else
{
currentBoxIndex++;
}
}
}
It seems binary; either the amount you have to distribute will completely fill boxes (with/out overspill) or it won't. If it will, then start from them all full and distribute the overspill, otherwise, distribute to a cap
int distrib = 40;
int cap = 5;
int space = boxes.Sum(b => cap - b.Amount);
int overspill = distrib - space;
if(overspill >= 0){
boxes.ForEach(b => b.Amount = cap + overspill / boxes.Count);
distrib = overspill % boxes.Count;
cap = int.MaxValue;
}
for(int x= 0; x < distrib;){
var b = boxes[x%boxes.Count];
if(b.Amount >= cap)
continue;
boxes[x%boxes.Count].Amount++;
x++;
}
If instead you want a logic of prioritizing the emptiest boxes, it gets simpler with MinBy (.net6 or MoreLinq)
int distrib = 25;
while(distrib-- > 0)
boxes.MinBy(b => b.Amount).Amount++;
In older .net that's boxes.First(b => b.Amount == boxes.Min(b2 => b2.Amount) ).Amount++;
There's plenty of scope for optimizing and tweaking this, but how much it's worth the effort depends on how much of a hot spot it is
I think this is close:
List<Box> boxes = new List<Box>
{
// Add boxes to the list.
new Box() { amount = 2 },
new Box() { amount = 1 },
new Box() { amount = 2 },
new Box() { amount = 0 },
new Box() { amount = -1 },
new Box() { amount = 2 },
new Box() { amount = 3 },
new Box() { amount = 0 },
new Box() { amount = 2 },
new Box() { amount = 1 }
};
Console.WriteLine(String.Join(", ", boxes.Select(b => b.amount)));
var sorted = boxes.OrderBy(b => b.amount < 5f ? int.MinValue : b.amount).ToList();
while (boxes.Sum(b => b.amount) < 52f)
{
var box = sorted.First();
sorted.RemoveAt(0);
box.amount += 1.0f;
sorted = sorted.Append(box).OrderBy(b => b.amount < 5f ? int.MinValue : b.amount).ToList();
Console.WriteLine(String.Join(", ", boxes.Select(b => b.amount)));
}
That gives me:
2, 1, 2, 0, -1, 2, 3, 0, 2, 1
3, 1, 2, 0, -1, 2, 3, 0, 2, 1
3, 2, 2, 0, -1, 2, 3, 0, 2, 1
3, 2, 3, 0, -1, 2, 3, 0, 2, 1
3, 2, 3, 1, -1, 2, 3, 0, 2, 1
3, 2, 3, 1, 0, 2, 3, 0, 2, 1
3, 2, 3, 1, 0, 3, 3, 0, 2, 1
3, 2, 3, 1, 0, 3, 4, 0, 2, 1
3, 2, 3, 1, 0, 3, 4, 1, 2, 1
3, 2, 3, 1, 0, 3, 4, 1, 3, 1
3, 2, 3, 1, 0, 3, 4, 1, 3, 2
4, 2, 3, 1, 0, 3, 4, 1, 3, 2
4, 3, 3, 1, 0, 3, 4, 1, 3, 2
4, 3, 4, 1, 0, 3, 4, 1, 3, 2
4, 3, 4, 2, 0, 3, 4, 1, 3, 2
4, 3, 4, 2, 1, 3, 4, 1, 3, 2
4, 3, 4, 2, 1, 4, 4, 1, 3, 2
4, 3, 4, 2, 1, 4, 5, 1, 3, 2
4, 3, 4, 2, 1, 4, 5, 2, 3, 2
4, 3, 4, 2, 1, 4, 5, 2, 4, 2
4, 3, 4, 2, 1, 4, 5, 2, 4, 3
5, 3, 4, 2, 1, 4, 5, 2, 4, 3
5, 4, 4, 2, 1, 4, 5, 2, 4, 3
5, 4, 5, 2, 1, 4, 5, 2, 4, 3
5, 4, 5, 3, 1, 4, 5, 2, 4, 3
5, 4, 5, 3, 2, 4, 5, 2, 4, 3
5, 4, 5, 3, 2, 5, 5, 2, 4, 3
5, 4, 5, 3, 2, 5, 5, 3, 4, 3
5, 4, 5, 3, 2, 5, 5, 3, 5, 3
5, 4, 5, 3, 2, 5, 5, 3, 5, 4
5, 5, 5, 3, 2, 5, 5, 3, 5, 4
5, 5, 5, 4, 2, 5, 5, 3, 5, 4
5, 5, 5, 4, 3, 5, 5, 3, 5, 4
5, 5, 5, 4, 3, 5, 5, 4, 5, 4
5, 5, 5, 4, 3, 5, 5, 4, 5, 5
5, 5, 5, 5, 3, 5, 5, 4, 5, 5
5, 5, 5, 5, 4, 5, 5, 4, 5, 5
5, 5, 5, 5, 4, 5, 5, 5, 5, 5
5, 5, 5, 5, 5, 5, 5, 5, 5, 5
5, 5, 5, 5, 5, 5, 6, 5, 5, 5
6, 5, 5, 5, 5, 5, 6, 5, 5, 5
I have been recently asked by a co-worker: Is it possible just take the first five elements and the last five elements by one query from an array?
int[] someArray = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
What I've tried:
int[] someArray = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
var firstFiveResults = someArray.Take(5);
var lastFiveResults = someArray.Skip(someArray.Count() - 5).Take(5);
var result = firstFiveResults;
result = result.Concat(lastFiveResults);
Is it possible to just take the first five elements and the last five elements by one query?
You can use a .Where method with lambda that accepts the element index as its second parameter:
int[] someArray = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
int[] newArray = someArray.Where((e, i) => i < 5 || i >= someArray.Length - 5).ToArray();
foreach (var item in newArray)
{
Console.WriteLine(item);
}
Output:
0, 1, 2, 3, 4, 14, 15, 16, 17, 18
A solution with ArraySegment<> (requires .NET 4.5 (2012) or later):
var result = new ArraySegment<int>(someArray, 0, 5)
.Concat(new ArraySegment<int>(someArray, someArray.Length - 5, 5));
And a solution with Enumerable.Range:
var result = Enumerable.Range(0, 5).Concat(Enumerable.Range(someArray.Length - 5, 5))
.Select(idx => someArray[idx]);
Both these solution avoid iterating through the "middle" of the array (indices 5 through 13).
In case you are not playing code puzzles with your co-workers, but just want to create a new array with your criteria, I wouldn't do this with queries at all, but use Array.copy.
There are three distinct cases to consider:
the source array has fewer than 5 items
the source array has 5 to 9 items
the source array has 10 or more items
The third one is the simple case, as the first and last 5 elements are distinct and well defined.
The other two require more thought. I'm going to assume you want the following, check those assumptions:
If the source array has fewer than 5 items, you will want to have an array of 2 * (array length) items, for example [1, 2, 3] becomes [1, 2, 3, 1, 2, 3]
If the source array has between 5 and 9 items, you will want to have an array of exactly 10 items, for example [1, 2, 3, 4, 5, 6] becomes [1, 2, 3, 4, 5, 2, 3, 4, 5, 6]
A demonstration program is
public static void Main()
{
Console.WriteLine(String.Join(", ", headandtail(new int[]{1, 2, 3})));
Console.WriteLine(String.Join(", ", headandtail(new int[]{1, 2, 3, 4, 5, 6})));
Console.WriteLine(String.Join(", ", headandtail(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11})));
}
private static T[] headandtail<T>(T[] src) {
int runlen = Math.Min(src.Length, 5);
T[] result = new T[2 * runlen];
Array.Copy(src, 0, result, 0, runlen);
Array.Copy(src, src.Length - runlen, result, result.Length - runlen, runlen);
return result;
}
which runs in O(1);
If you are playing code puzzles with your co-workers, well all the fun is in the puzzle, isn't it?
It's trivial though.
src.Take(5).Concat(src.Reverse().Take(5).Reverse()).ToArray();
this runs in O(n).
Try this:
var result = someArray.Where((a, i) => i < 5 || i >= someArray.Length - 5);
This should work
someArray.Take(5).Concat(someArray.Skip(someArray.Count() - 5)).Take(5);
Try this:
int[] someArray = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
var firstFiveResults = someArray.Take(5);
var lastFiveResults = someArray.Reverse().Take(5).Reverse();
var result = firstFiveResults;
result = result.Concat(lastFiveResults);
The second Reverse() reorders the numbers so you won't get 18,17,16,15,14
Please try this:
var result = someArray.Take(5).Union(someArray.Skip(someArray.Count() - 5).Take(5));
Is there a way to access my three dimensional jagged array like this:
jaggedArray[1,2,3];
I got the following code snippets so far:
int[][] jaggedArray = new int[3][]
{
new int[] { 2, 3, 4, 5, 6, 7, 8 },
new int[] { -4, -3, -2, -1, 0, 1},
new int[] { 6, 7, 8, 9, 10 }
};
int[,,] dontWork = new int[,,] // expect 7 everywhere in the last dimension
{
{ { 2, 3, 4, 5, 6, 7, 8 } },
{ { -4, -3, -2, -1, 0, 1} },
{ { 6, 7, 8, 9, 10 } }
};
As for the first question, you're trying to access the 3rd element, of the 2nd element of the 1st element of the jagged array:
jaggedArray[1][2][3]
As for the error, a 3D array expects the same number of elements in each element of a dimension. Let's say, for simplicity's sake, that you have a 2D jagged array, a rough representation of what that looks like in memory would be:
First row -> 2, 3, 4, 5, 6, 7, 8
Second row -> -4, -3, -2, -1, 0, 1
Third row -> 6, 7, 8, 9, 10
You can see that each row is seen as a different array, and can therefore differ in size. A multidimensional array, however, does not have this property. It needs to be filled completely:
Column : 0 1 2 3 4 5 6
------------------------------------
First row : 2, 3, 4, 5, 6, 7, 8
Second row: -4, -3, -2, -1, 0, 1
Third row : 6, 7, 8, 9, 10
Your table is missing some cells, which makes no sense. You need to use the same number of elements per dimension.
You got the syntax for declaring 2D jagged array right, 3D jagged arrays are an extension of that. For example:
int[][][] jagged3d = new int[][][]
{
new int[][] { new int[] { 111, 112 }, new int[] { 121, 122, 123 } },
new int[][] { new int[] { 211 } }
}
But to access it, you need different syntax:
jagged3d[0][1][2]