I want to programm a level system for a small game.
The level system would be tied to the score and the levels would get further appart
only 2 score values are given
lvl, score
0, 50 (from 0 - 50)
1, 100 (from 51 to 100)
2, 150
3, 250
4, 400
5, 650
...
How could I elegantly calculate witch level I am in with a given score and 2 start values (50 and 100 in the example)
Or is it best to just calculate the score values in a list or array
With out any formula you can simply compute the whole table in a flash (under 0.0002 sec on a core2). Summing int is pretty fast. That's only 36 computation before hitting the max on int32.
var scoreTable = new []{50, 100, 150, 250, 400, 650, 1050, 1700, 2750, 4450, 7200, 11650, 18850,
30500, 49350, 79850, 129200, 209050, 338250, 547300, 885550, 1432850, 2318400, 3751250,
6069650, 9820900, 15890550, 25711450, 41602000, 67313450, 108915450, 176228900,
285144350, 461373250, 746517600, 1207890850, 1954408450};
For the math to create the table, let's be simple:
var thresholds = new List<int> {50, 100};
var index = 1;
while(true){
var temp = thresholds[cpt] + thresholds[cpt - 1];
if (temp < 0) break;
thresholds.Add(temp);
}
And to know the level:
var score = 51;
// Index is Zero-based numbering. Count is One-based. Index = Count -1;
var level = scoreTable.Where(x => x < score ).Count() - 1;
Binary.Search:
public class SupComparer : IComparer
{
public int Compare(object x, object y)
{
var t1 = UInt64.Parse(x.ToString());
var t2 = UInt64.Parse(y.ToString());
return t1.CompareTo(t2) > 0 ? 1 : 0;
}
}
var cp = new SupComparer();
var level = Array.BinarySearch(scoreTable, score, (IComparer)cp);
There's actually a formula to calculate Fibonacci numbers. That can be transformed into an algorithm to find the index of any given Fibonacci number. There's an example of how to do this in C# here.
You need to adapt that formula for use with your initial conditions of 50 and 100.
I asked a question over on Mathematics SE for help adjusting the original forumula and they suggested using
It's pretty easy to implement this as a C# method.
public int GetScoreIndex(int score)
{
const double phi = 1.61803398875;
const double rad5 = 2.2360679775;
var preLog = (score / 50) * rad5 + (1/2);
var log = Math.Log(preLog, phi);
var floor = (int) Math.Floor(log);
var index = floor - 1;
return index;
}
Related
I'd like to be able to get a log scale between two numbers, for x number of steps between a max and min value in c#
E.g.
var steps = 10;
var minValue = 10;
var maxValue = 1000;
Which would return this array with the values to the nearest int
[{1,10}, {2, 17}, {3,28}, {4,46}, {5,77}, {6,129}, {7,215}, {8,359}, {9, 599}, {10, 1000}]
Can anyone point me in the right direction?
Do it exactly like you would do it for a linear scale, except that you take Math.Log of your min/max values to determine the step and later do a Math.Exp to undo this transformation:
var step = (Math.Log(maxValue) - Math.Log(minValue))/(steps - 1);
for (var i = 0; i < steps; i++)
{
Console.WriteLine("{0}: {1:F0}", i + 1, Math.Exp(Math.Log(minValue) + i * step));
}
Given a maximum travel distance (forward and backwards roads), return the route which uses the maximum travel distance, in case of multiple same routes which uses the maximum travel distance return multiple routes.
Example 1
Forward route : [[1,3000],[2,5000],[3,4000],[4,10000],[5,8000]]
Backward route : [[1,1000],[2,3000],[3,4000]]
Max Distance Traveled: 11000
Result must be: [4,1] and [5,2], as the total traveled distance is 11000 which is less than or equal to max distance.
Example 2
Forward route : [[1,3000],[2,5000],[3,4000],[4,10000]]
Backward route : [[1,2000],[2,3000],[3,4000]]
Max Distance Traveled: 11000
Result must be: [2,3], as the total traveled distance is 9000 which is less than or equal to max distance.
I was able to solve this in O(forLength * backLength) like the below code:
static void Main(string[] args) {
int[][] f = new int[5][];
int[][] b = new int[3][];
f[0] = new int[] { 1, 3000 };
f[1] = new int[] { 2, 5000 };
f[2] = new int[] { 3, 4000 };
f[3] = new int[] { 4, 10000 };
f[4] = new int[] { 5, 8000 };
b[0] = new int[] { 1, 1000 };
b[1] = new int[] { 2, 3000 };
b[2] = new int[] { 3, 4000 };
var result = sol(f, b, 11000);
}
public static List<List<int>> sol(int[][] f, int[][] b,int max) {
List<List<int>> li = new List<List<int>>();
int m = 0;
for (int i = 0; i < f.Length; i++) {
for (int j = 0; j < b.Length; j++) {
if (f[i][1] + b[j][1] <= max) {
li.Add(new List<int>() { f[i][0], b[j][0], f[i][1] + b[j][1] });
if (m < f[i][1] + b[j][1]) {
m = f[i][1] + b[j][1];
}
}
}
}
return li.Where(i => i[2] == m).ToList();
}
Can anyone help me to make it more efficient in terms of time complexity, please?
Maybe I can give you a solution draft:
Let's name the "forward" list fwList, the "backward" list bwList. Each element contains a key and a value, in that order.
Sort both lists in the ascending order using merge sort or heap sort on the value part of the elements (O(N.ln(N)) time complexity).
For each element of bwList (we call it bwElem), find the index in fwList (we call it ID) where the sum is becoming too long (bwElem + fwList[ID] > 11000). Then [bwElem.key, fwList[ID - 1].key] is part of your solution.
The concatenation of the results for each element of bwList should make your list and, if all is clear in my mind, you should have a O(bwLength * (fwLength)^a) time complexity, where a < 1 (I'd even bet on O(bwLength * ln(fwLength))).
I think the algorithm can be optimized using this basis.
Say we have a list of numbers:
{1,3,7,13,19,54}
and a single value : 17
What is the least expensive way to find the closest 2 numbers surrounding the value in the list. So in our example, the answer would be 13 and 19.
Currently, I am using a loop to make 2 lists, one of numbers above and one of numbers below. Then I use something like this:
point1 = pointsAbove.Aggregate(Function(x, y) If(Math.Abs(x.X - xVal) < Math.Abs(y.X - xVal), x, y))
point2 = pointsBelow.Aggregate(Function(x, y) If(Math.Abs(x.X - xVal) < Math.Abs(y.X - xVal), x, y))
This seems so clunky to me. So I look to you!
Try the code below:
List<int> numbers = new List<int>(){ 6, 7, 8, 9, 1, 2, 3, 4, 5 };
int middle = 6;
var min = numbers.Where(y => y < middle).Max(); // min = 5
var max = numbers.Where(y => y > middle).Min(); // max = 7
The code above will work fine for sorted and unsorted lists.
If you can't be certain that you have at least one min and/or one max value, you must do this, otherwise you'll get an Exception:
var min = numbers.Where(y => y < middle).DefaultIfEmpty().Max();
var max = numbers.Where(y => y > middle).DefaultIfEmpty().Min();
And, in the case you are sure 100% of the time that the list is sorted, you can save a bit of performance doing the code below:
var min = numbers.LastOrDefault(y => y < middle);
var max = numbers.FirstOrDefault(y => y > middle);
I hope it helped you.
Assuming your list is sorted, you don't need to iterate the list which would be O(n) in time. Using binary search it can be handled in O(log(n)).
So my question is: fastest code (plus least memory) vs. shortest code... Your question doesn't say anything about that...
BTW: Handling the corner cases will not be easy..
var list = new List<int> { 1, 3, 7, 13, 20, 54 };
var numToSearch = 54;
int point1 = int.MinValue, point2 = int.MinValue;
var inx = list.BinarySearch(numToSearch); //<---
if (inx >= 0) //search item is in the list
{
if (inx == 0)
{
point1 = list[0];
point2 = list[1];
}
else if (inx == list.Count - 1)
{
point1 = list[inx - 1];
point2 = list[inx];
}
else
{
int val1 = list[inx - 1];
int val2 = list[inx + 1];
if (Math.Abs(val1 - list[inx]) < Math.Abs(list[inx] - val2))
{
point1 = list[inx - 1];
point2 = list[inx];
}else
{
point1 = list[inx];
point2 = list[inx+1];
}
}
}
else {
point1 = list[~inx - 1];
point2 = list[~inx];
}
PS: Least time? Least memory? Least Code? Hard to achieve(if not impossible) at the same time. Seems like OP will pick up a random answer :)
PS2: I wouldn't expect OP would accept this answer after reading previous note :)
Since you didn't specify what happens if your single value (17) has no lower of higher value, I will let them default to NULL here.
int number = 17;
int? lower = points.LastOrDefault(x => x < number);
int? upper = points.FirstOrDefault(x => x > number);
or in one line, allthough this is uglier imo
Tuple<int?, int?> closestNumbers = new Tuple<int?, int?>(points.LastOrDefault(x => x < number), points.FirstOrDefault(x => x > number));
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.
I am not quite sure how to go about this.
I need to generate 14,296 random numbers with different levels of probability.
so for example I need an array containing the numbers 18, 1, and 17. Each number has a different percent probability of occuring. So:
55% = 18
(7,862.8 times)
30% = 1
(4,288.8 times)
15% = 17
(2,144.4 times)
the result would be something like new Array () { 18, 18, 1, 17, 1, 18...}
If you'll always have the values as integer percentages, I'd fill a 100-element array with values according to the probability, so in this case your array would have 55 occurrences of 18, 30 occurrences of 1, and 15 occurrences of 17. Then you just need to pick 14,296 random values from that array. (i.e. pick an integer in the range [0, 100) and take that element.)
For different ways of expressing the probabilities, there are different approaches, of course. But if you're given integer percentages, this is an easily-understood option. (Another way is to scale all of the probabilities by the total, i.e. into a range of [0, 1), and then take a random double in that range.)
Divide the range of random generator into proportional segments, and, judging to which segment the next random number has fallen into, select the corresponding number from your set.
Something like (simplified):
const int numbers[3] = { 1, 17, 18 };
const int borders[2] = { 0.30*MAX_RANDOM, (0.30 + 0.15) * MAX_RANDOM };
int i = random.next(), num;
if (i < borders[0]) num = number[0];
else if (i < borders[0]) num = number[1];
else num = number[2];
Of course, if there's more numbers than three, it's better to use a loop.
Note: unlike Jon Skeet's solution, this one can provide any desired granularity up to 1/(MAX_RANDOM+1) (which is often up to 2^32 on 32-bit machines), rather than strictly 1%.
Random r = new Random();
// for each number to generate
int nextNumber;
double probability = r.NextDouble();
if (probability < 55.0 / 100.0)
nextNumber = 18;
else if (probability < (55.0 + 30.0) / 100.0)
nextNumber = 1;
else
nextNumber = 17;
How about something like this (untested):
struct np
{
int n;
int p;
}
Create a List<np> and will it with value/percentage pairs (e.g. n = 18, p = 55).
Then just do the following to pick a number:
List<np> npl = new List<np>();
// (fill the list here)
int r = rnd.next(total_of_all_p_values); // get random number
int res = 0; // result
for(int i = 0; i < npl.Length(); r -= npl[i++].n)
{
if(r < npl[i].p) // remaining vlaue is smaller than current percentage
{
res = npl[i].n;
break;
}
}
You could populate a List<T> with the appropriate number of each of the 3 numbers, and then randomize the List.