|
level 1 size = 1
level 2 size = 2
level 3 size = 2
level 4 size = 4
level 5 size = 6
level 6 size = 6
level 7 size = 8
level 8 size = 10
level 9 size = 14
level 10 size = 20
level 11 size = 26
level 12 size = 34
level 13 size = 46
level 14 size = 62
level 15 size = 78
level 16 size = 102
level 17 size = 134
level 18 size = 176
level 19 size = 226
level 20 size = 302
level 21 size = 408
level 22 size = 528
level 23 size = 678
level 24 size = 904
level 25 size = 1182
level 26 size = 1540
level 27 size = 2012
level 28 size = 2606
level 29 size = 3410
level 30 size = 4462
level 31 size = 5808
level 32 size = 7586
level 33 size = 9898
level 34 size = 12884
level 35 size = 16774
level 36 size = 21890
level 37 size = 28528
level 38 size = 37158
level 39 size = 48410
level 40 size = 63138
level 41 size = 82350
level 42 size = 107312
level 43 size = 139984
level 44 size = 182376
level 45 size = 237746
level 46 size = 310036
level 47 size = 403966
level 48 size = 526646
level 49 size = 686646
level 50 size = 894810
level 51 size = 1166642
level 52 size = 1520986
level 53 size = 1982710
level 54 size = 2584304
level 55 size = 3369156
level 56 size = 4391702
level 57 size = 5724486
level 58 size = 7462860
level 59 size = 9727930
level 60 size = 12680852
level 61 size = 16530884
level 62 size = 21549544
level 63 size = 28091184
level 64 size = 36619162
level 65 size = 47736936
level 66 size = 62226614
level 67 size = 81117366
level 68 size = 105745224
level 69 size = 137842560
level 70 size = 179691598
level 71 size = 234241786
level 72 size = 305351794
level 73 size = 398049970
level 74 size = 518891358
level 75 size = 676414798
level 76 size = 881752750
level 77 size = 1149440192
level 78 size = 1498380104
level 79 size = 1953245418
level 80 size = 2546222700
level 81 size = 3319186080
level 82 size = 4326816254
level 83 size = 5640348764
level 84 size = 7352630884
level 85 size = 9584715106
level 86 size = 12494412020
level 87 size = 16287462624
level 88 size = 21231903676
level 89 size = 27677468012
level 90 size = 36079732206
level 91 size = 47032657188
level 92 size = 61310766500
level 93 size = 79923316046
level 94 size = 104186199146
level 95 size = 135814773100
level 96 size = 177045063068
level 97 size = 230791944956
level 98 size = 300854953626
level 99 size = 392187941864
level 100 size = 511247092564
finished computation at Fri Dec 1 16:48:41 2017
elapsed time: 7205.75secs
modified 3-Dec-17 18:14pm.
|
|
|
|
|
Congratulations! You're the first to post the correct answer.
Extra credit: how did you do it in 2 hours?
System.ItDidntWorkException: Something didn't work as expected.
C# - How to debug code[ ^].
Seriously, go read these articles.
Dave Kreskowiak
|
|
|
|
|
Since we only need to compute the length, storing the entire string isn't necessary. Furthermore, the computation can be done recursively and requires very little code/storage for each level of recursion. The memory footprint while running was about 16k IIRC.
I removed some extraneous code and got the runtime at l=100 to about 1.5 hours. Probably could optimize it even more, but I don't see the point.
I'd post code here but it seems to be discouraged.
|
|
|
|
|
It would be interesting to see. Code has been an exception in the past for challenges like this.
System.ItDidntWorkException: Something didn't work as expected.
C# - How to debug code[ ^].
Seriously, go read these articles.
Dave Kreskowiak
|
|
|
|
|
OK, here it is...
#include <iostream>
#include <chrono>
#include <ctime>
using namespace std;
#define maxLevel 100
static uint32_t currentLevel = 0;
static chrono::time_point<chrono::system_clock> start, timeFinished;
class LevelProcessor
{
public:
LevelProcessor() :
currentOccurrence(0),
currentPrefix(0),
myLevel(currentLevel++),
totalSize(0)
{
}
void ProcessLevel(uint32_t prefix);
void FinishLevel();
uint32_t currentOccurrence;
uint32_t currentPrefix;
const uint32_t myLevel;
uint64_t totalSize;
};
static LevelProcessor processors[maxLevel];
void LevelProcessor::ProcessLevel(uint32_t prefix)
{
if (prefix == currentPrefix)
{
++currentOccurrence;
return;
}
if (currentOccurrence != 0)
{
if (myLevel < maxLevel - 1)
{
processors[myLevel + 1].ProcessLevel(currentOccurrence);
processors[myLevel + 1].ProcessLevel(currentPrefix);
}
++totalSize;
}
currentPrefix = prefix;
currentOccurrence = 1;
}
void LevelProcessor::FinishLevel()
{
++totalSize;
if (myLevel < maxLevel - 1)
{
processors[myLevel + 1].ProcessLevel(currentOccurrence);
processors[myLevel + 1].ProcessLevel(currentPrefix);
}
chrono::time_point<chrono::system_clock> timeFinished = chrono::system_clock::now();
chrono::duration<double> elapsed_seconds = timeFinished - start;
time_t end_time = chrono::system_clock::to_time_t(timeFinished);
cout << "level " << myLevel + 1 << " is done, size = " << totalSize * 2 << " at " << "elapsed time: " << elapsed_seconds.count() << "secs" << endl;
if (myLevel < maxLevel - 1)
processors[myLevel + 1].FinishLevel();
}
int main()
{
start = chrono::system_clock::now();
processors[1].ProcessLevel(1);
processors[1].FinishLevel();
timeFinished = chrono::system_clock::now();
chrono::duration<double> elapsed_seconds = timeFinished - start;
time_t end_time = chrono::system_clock::to_time_t(timeFinished);
cout << "finished computation at " << ctime(&end_time)
<< "elapsed time: " << elapsed_seconds.count() << "secs" << endl;
}
So much for my indenting, oh well.
|
|
|
|
|
Interesting. When I originally did the research into this thing I saw the pattern developing in the brute force results but I was never able to get any code to work that looked for and tracked the pattern.
I'll have to dig into this later to see exactly how it works and where I made my mistakes. I still have a couple of the broken projects from way back then.
Thanks for sharing!
System.ItDidntWorkException: Something didn't work as expected.
C# - How to debug code[ ^].
Seriously, go read these articles.
Dave Kreskowiak
|
|
|
|
|
I wish I could say that this is exploiting some underlying pattern, but it's really just a more efficient brute force implementation. It's more like a depth-first tree traversal - you never have to compute and store the entire string at one level before working on the next.
|
|
|
|
|
That's what I thought. When I originally started looking at this, I found the storage requirements for a single iteration were going to jump exponentially. I was looking for a method to do this, something like what you've done, but couldn't get it to work.
System.ItDidntWorkException: Something didn't work as expected.
C# - How to debug code[ ^].
Seriously, go read these articles.
Dave Kreskowiak
|
|
|
|
|
Wow, I see where my previous mistakes were compared to yours.
I got a couple of hints from your code that got my old code working, and what I was misinterpreting.
Your code, on my machine, does the 100 numbers in an hour and ten minutes. FAR faster than my brute force runs that store every iteration on disk in a byte-compressed format and takes just under 6 hours to run.
Thanks for the help!
System.ItDidntWorkException: Something didn't work as expected.
C# - How to debug code[ ^].
Seriously, go read these articles.
Dave Kreskowiak
|
|
|
|
|
No problem. It was an interesting challenge!
FWIW, I was looking further at the code and at how to optimize it.
One interesting thing I found is that if the order of ProcessLevel() calls is reversed we get the same sequences but reversed!
This means that the initial prefix for each level is always 1 and can be initialized as such, which then means the check for currentOccurrence != 0 in ProcessLevel() can be removed. Doesn't seem like much, but when that function is executed trillions of times it makes a noticeable difference.
You must have a fast machine, 100 takes quite a bit longer for me.
|
|
|
|
|
Interesting. I'll have to play around with that some more.
I'm wondering how long it would take to get to 200, let alone 3,000. And how to hang onto numbers that big. It seems a BigInt class would be needed but performance may suffer greatly.
I just built a new machine about 9 months ago, overclocked and water cooled of course.
System.ItDidntWorkException: Something didn't work as expected.
C# - How to debug code[ ^].
Seriously, go read these articles.
Dave Kreskowiak
|
|
|
|
|
My runtimes are about 3500 sec at l=100 and about 50000 sec at l=110 for an increase of about 14.3x.
So from l=100 to l=200 is 14.3 ^ 10 x the time, or 347,636,939,799 hours. ~39 million years, give or take
l=3000? heh. I think that's a pretty good definition of "forever".
|
|
|
|
|
OK, I'll see how far I get doing it "my way" -- but I'll address the more general problem, allowing the starting input to be more than one symbol and not limited to the symbols 1 , 2 , and 3 . Also, allowing the caller to specify the maximum subsequence length -- that'll be the hard part.
I think the only alcohol in the place is one shot of tequila; it will have to be enough.
Sunday morning update: By midnight I had the basic functionality (subsequence lengths 0 and 1) working and tested -- but using a List<T> which means that there are allocation issues.
This morning's immediate goal -- implement a SegmentedList<T> class.
Sunday afternoon update: The SegmentedList<T> is working well, and it allows for multiple threads for improved speed.
modified 3-Dec-17 18:31pm.
|
|
|
|
|
Since the only requirement was to determine the length, it is not necessary to store the full string. A simple 100 level recursion that, at each level, returns the next digit in sequence suffices - it takes a long time to run, but does not need huge amounts of space.
At each level above the first it is only necessary to store at most two digits - the digit of which you have just counted the repetitions, and the digit that broke the sequence. Each invocation at any level alternates between returning the count and returning the counted digit.
|
|
|
|
|
|
The quick and dirty code I wrote was
#include <stdio.h>
#include <stdlib.h>
class s {
private:
char indx;
char ondx;
char v[10];
public:
bool done;
unsigned long long count;
s(void) {
indx = '\0';
ondx = '\0';
done = false;
count= 0UL;
}
void put (char c) {
v[indx++] = c;
indx = indx % 10;
}
char get (void) {
char c = v[ondx++];
ondx = ondx %10;
return c;
}
char peek (void) {
return v[ondx];
}
bool isEmpty (void) {
return indx == ondx;
}
};
s context[100];
bool nextItem (char& c, int level);
void doCount (char& c, char v, int level) {
bool r = false;
s& x = context[level];
c = '1';
x.put (v + 0X80);
while (!x.done) {
char t;
x.done = nextItem (t, level - 1);
if (t == v) {
c++;
}
else {
x.put (t);
break;
}
}
}
bool nextItem (char& c, int level) {
s& x = context[level];
bool r = false;
if (!x.isEmpty()) {
char v = x.get();
c = v & 0X7F;
if (v & 0X80) {
r = x.isEmpty();
}
else {
doCount (c, v, level);
}
}
else if (level == 0) {
c = '1';
x.done = true;
r = true;
}
else if (x.isEmpty()) {
char t;
x.done = nextItem(t, level - 1);
doCount (c, t, level);
}
x.count++;
if (r) {
fprintf (stdout, "%3d: %llu digits\n", level + 1, x.count);
}
return r;
}
int main(int argc, char *argv[]) {
char t;
while (!nextItem(t, 99)) {
;
}
}
</blockquote>
|
|
|
|
|
The answer for the length of the 100th number is 511,247,092,564 digits.
The length escalates frighteningly quickly. The LENGTH of the 3000th number in the chain is, get this, 4029857719515768641307384677908679928310793769651641917926155107836565892187598804862177357001771122238068645667821323998368650130801806344030981271295995422208436642014734696538407619447946889047668430308242548524802874469136450965097114152481264391293269162985708430576259447637028591596189605329702198409448541645531801518246316682171504624370 digits long. That's not the number. That's how long it is in digits!
That's more digits than there are the estimated number of atoms in the observable universe, by many orders of magnitude!
System.ItDidntWorkException: Something didn't work as expected.
C# - How to debug code[ ^].
Seriously, go read these articles.
Dave Kreskowiak
modified 4-Dec-17 12:28pm.
|
|
|
|
|
I've left my totally brute-force string-based solution running for 4 days and it's only on the 64th iteration having got up to 50 within an hour - so, yeah, that rather underlines how it can never really be achieved in reasonable time without an awful lot more finesse.
I really enjoyed this as a coding challenge even though I didn't get remotely close to cracking it. A simple looking task on the surface but one that soon reveals itself to be monumentally problematic.
98.4% of statistics are made up on the spot.
|
|
|
|
|
I got the same value for the 100th but it took me almost 21h with iterators.
Did you actually calculate the 3000th????
Paulo Gomes
Measuring programming progress by lines of code is like measuring aircraft building progress by weight.
—Bill Gates
Everything should be made as simple as possible, but not simpler.
—Albert Einstein
|
|
|
|
|
No, I didn't. I don't have an algorithm to go that high in my lifetime. At least not yet. I'm still working on the problem in some spare time.
There is only a single place on the entire 'net where that number is listed, here[^]. Seems to be down right now though.
System.ItDidntWorkException: Something didn't work as expected.
C# - How to debug code[ ^].
Seriously, go read these articles.
Dave Kreskowiak
|
|
|
|
|
That's quite a number.
Got me spending electricity for almost 21h
Paulo Gomes
Measuring programming progress by lines of code is like measuring aircraft building progress by weight.
—Bill Gates
Everything should be made as simple as possible, but not simpler.
—Albert Einstein
|
|
|
|
|
I don't know if anyone is looking at this anymore due to it being an old topic, but there is a way to calculate the length of these sequences in linear time.
For example, it's possible to calculate the length of sequences 1..5000 in < 1 sec.
|
|
|
|
|
|
John Conway did extensive research a while back on this sequence. He discovered that after a certain point (level 8), each level can be written as a series of 92 subsequences that in turn evolve into one or several of the 92 subsequences.
So it's possible to write a program that just keeps track of the number of times a particular subsequence has been seen, and then "evolve" it for the next level, which is iterating over a 92 element array for each level and creating a new 92 element array for the next. Since the size of each subsequence is known, calculating the length of a particular level is as simple as multiplying the count of each subsequence by the length, and adding them all together.
I can post the source here if that's kosher. It's not pretty but it works
|
|
|
|
|
Here's the code, it allows a param to specify the level, default is 100.
uint16384_t allows computation to slightly above level 40000.
#include <chrono>
#include <ctime>
#include <boost multiprecision="" cpp_int.hpp="">
// g++ xxx.cpp -std=c++11 -march=corei7
using namespace std;
using namespace boost::multiprecision;
typedef number<cpp_int_backend<1024 *="" 16,="" 1024="" unsigned_magnitude,="" unchecked,="" void=""> > uint16384_t;
uint32_t maxLevel = 100;
static chrono::time_point<chrono::system_clock> start, timeFinished;
const uint32_t levelSize[] = { 4, 7, 12, 12, 4, 5, 12, 6, 8, 10, // 1-10
10, 14, 12, 14, 18, 42, 42, 26, 14, 28, // 11-20
14, 24, 24, 5, 7, 10, 10, 8, 2, 9, // 21-30
9, 23, 2, 6, 32, 32, 8, 3, 5, 6, // 31-40
10, 18, 18, 6, 10, 8, 7, 8, 12, 20, // 41-50
34, 34, 20, 10, 7, 7, 11, 13, 21, 17, // 51-60
2, 1, 4, 7, 14, 14, 7, 4, 6, 8, // 61-70
10, 16, 28, 28, 9, 12, 12, 16, 18, 24, // 71-80
23, 16, 6, 5, 15, 6, 10, 10, 3, 27, // 81-90
27, 5 }; // 91-92
vector<uint32_t> levelEvolution[] = {
{63},
{64, 62},
{65},
{66},
{68}, // 1-5
{69},
{84, 55},
{70},
{71},
{76}, // 6-10
{77},
{82},
{78},
{79},
{80}, // 11-15
{81, 29, 91},
{81, 29, 90},
{81, 30},
{75, 29, 92},
{75, 32}, // 16-20
{72},
{73},
{74},
{83},
{86}, // 21-25
{87},
{88},
{89, 92},
{1},
{3}, // 26-30
{4},
{2, 61, 29, 85},
{5},
{28},
{24, 33, 61, 29, 91}, // 31-35
{24, 33, 61, 29, 90},
{7},
{8},
{9},
{10}, // 36-40
{21},
{22},
{23},
{11},
{19}, // 41-45
{12},
{13},
{14},
{15},
{18}, // 46-50
{16},
{17},
{20},
{6, 61, 29, 92},
{26}, // 51-55
{27},
{25, 29, 92},
{25, 29, 67},
{25, 29, 85},
{25, 29, 68, 61, 29, 89}, // 56-60
{61},
{33},
{40},
{41},
{42}, // 61-65
{43},
{38, 39},
{44},
{48},
{54}, // 66-70
{49},
{50},
{51},
{52},
{47, 38}, // 71-75
{47, 55},
{47, 56},
{47, 57},
{47, 58},
{47, 59}, // 76-80
{47, 60},
{47, 33, 61, 29, 92},
{45},
{46},
{53}, // 81-85
{38, 29, 89},
{38, 30},
{38, 31},
{34},
{36}, // 86-90
{35},
{37} // 91-92
};
uint16384_t levelCounts[92];
uint16384_t evolvedLevelCounts[92];
int main(int argc, char** argv)
{
if (argc != 1)
{
maxLevel = atoi(argv[1]);
if (maxLevel < 9)
{
maxLevel = 10;
}
}
start = chrono::system_clock::now();
time_t start_time = chrono::system_clock::to_time_t(start);
cout << "Starting run of " << maxLevel << " levels at " << ctime(&start_time) << endl;
for (auto& sequence: levelEvolution)
{
for (auto& seq: sequence )
{
--seq; // Adjust indices for 0-based array
}
}
for (uint32_t i = 0; i < 92; ++i)
{
levelCounts[i] = 0;
}
// Prime the counts for the starting level (9).
levelCounts[23] = 1;
levelCounts[38] = 1;
uint16384_t * workingCounts = levelCounts;
uint16384_t * evolvedCounts = evolvedLevelCounts;
for (uint32_t i = 9; i <= maxLevel; ++i)
{
for (uint32_t j = 0; j < 92; ++j)
{
evolvedCounts[j] = 0;
}
for (uint32_t j = 0; j < 92; ++j)
{
if (workingCounts[j] != 0)
{
for (auto& level: levelEvolution[j])
{
evolvedCounts[level] += workingCounts[j];
}
}
}
uint16384_t totals = 0;
for (uint32_t j = 0; j < 92; ++j)
{
if (evolvedCounts[j] != 0)
{
totals += evolvedCounts[j] * levelSize[j];
}
}
cout << i << " " << totals << endl;
swap(workingCounts, evolvedCounts);
}
timeFinished = chrono::system_clock::now();
chrono::duration<double> elapsed_seconds = timeFinished - start;
time_t end_time = chrono::system_clock::to_time_t(timeFinished);
cout << "finished computation at " << ctime(&end_time)
<< "elapsed time: " << elapsed_seconds.count() << "secs" << endl;
}
|
|
|
|
|