Reputation: 93
I'm trying to get my performance skills (none existent) up to par but ran into a problem with writing out a formula into code. This is the formula I'm trying to - quote unquote - "convert" to code.
Consider a sequence u where u is defined as follows:
The number
u(0) = 1
is the first one inu
. For eachx
inu
, theny = 2 * x + 1
andz = 3 * x + 1
must be inu
too. There are no other numbers inu
. Ex:u = [1, 3, 4, 7, 9, 10, 13, 15, 19, 21, 22, 27, ...]
1
gives3
and4
, then3
gives7
and10
,4
gives9
and13
, then7
gives15
and22
and so on...
And this is what I have so far:
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
Console.WriteLine(DblLinear(10));
//Expected result: 22
Console.WriteLine(DblLinear(20));
//Expected result: 57
Console.WriteLine(DblLinear(30));
//Expected result: 91
Console.WriteLine(DblLinear(50));
//Expected result: 175
}
public static int DblLinear (int n)
{
List<int> linArr = new List<int>();
linArr.Add(1);
int i = 0;
while(linArr.Count < n)
{
linArr.Add((2 * linArr[i]) + 1);
linArr.Add((3 * linArr[i]) + 1);
linArr.Sort();
i++;
}
return linArr[n - 1];
}
}
The calculations are right until it hits 27. After that it just runs wild and I have no idea what it does wrong.
Upvotes: 3
Views: 973
Reputation: 117084
What's going wrong is that you're using a single index to build your values.
When you get to i == 27
, as per your example, the first series produces 55
and the second produces 82
. But the first should have also produced 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81
before the second produces 82
for your sequence to be properly computed.
You need to run two separate indexes and only increment each when the value produced by their respective series is lowest (or equal to the other).
Here's how I would do it:
public static IEnumerable<int> DblLinear()
{
Func<int, int> fxy = x => 2 * x + 1;
Func<int, int> fxz = x => 3 * x + 1;
var intermediate = new System.Collections.Generic.SortedSet<int>();
Action<int> safeAdd = x =>
{
if (!intermediate.Contains(x))
{
intermediate.Add(x);
}
};
safeAdd(1);
while (true)
{
var x = intermediate.First();
safeAdd(fxy(x));
safeAdd(fxz(x));
intermediate.Remove(x);
yield return x;
}
}
Now this can be used to produce the entire sequence so it is a good idea to use the LINQ .Take(...)
operator.
You could using it like this:
Console.WriteLine(String.Join(", ", DblLinear().Take(20)));
...and that gives me:
1, 3, 4, 7, 9, 10, 13, 15, 19, 21, 22, 27, 28, 31, 39, 40, 43, 45, 46, 55
Your test code, once modified to handle the list, works just fine:
Console.WriteLine(DblLinear().Skip(10).First());
//Expected result: 22
Console.WriteLine(DblLinear().Skip(20).First());
//Expected result: 57
Console.WriteLine(DblLinear().Skip(30).First());
//Expected result: 91
Console.WriteLine(DblLinear().Skip(50).First());
//Expected result: 175
Upvotes: 2
Reputation: 6103
You take one item from sequence and produce two. So You really need some data structure to store them, as their numbers would increase. Heap would be the best. If You want to go with what we have directly in .net, You can use SortedSet
.
public static IEnumerable<int> DblLinear2()
{
SortedSet<int> seq = new SortedSet<int> { 1 };
while (true)
{
int min = seq.First();
seq.Remove(min);
yield return min;
seq.Add(min * 2 + 1);
seq.Add(min * 3 + 1);
}
}
The result:
1, 3, 4, 7, 9, 10, 13, 15, 19, 21, 22, 27, 28, 31, 39, 40, 43, 45, 46, 55, 57, 58, 63, 64, 67, 79, 81, 82, 85, 87...
Upvotes: 6