11
\$\begingroup\$

The period of a string is the shortest non-zero shift so that the string matches itself, ignoring any parts that overhang. So for example, abcabcab has period 3. By convention we say that if there is no such shift then a string has period equal to its length. So the period of abcde is 5 and the period of a is 1.

In more formal terms, the period of a string S is the minimum i > 0 so that S[1,n-i] == S[i+1,n] (indexing from 1).

For a given string S of power of two length, we will compute the period of all its prefixes of power of two length. For example, consider S = abcabcab. The periods we will compute are:

'a', 1
'ab', 2
'abca', 3
'abcabcab', 3

We will in fact just output the array of periods, that is [1, 2, 3, 3].

For a given positive power of two n, consider all possible binary strings S. Recall that a binary string is simply a string of 1s and 0s so there are exactly 2^n such strings (that is 2 to the power n). For each one we can compute this array of periods.

The challenge is to write code that takes n (a power of two) as input and computes how many distinct such arrays there are.

The answers for n = 1, 2, 4, 8, 16, 32, 64, 128 are:

1, 2, 6, 32, 320, 6025, 216854, 15128807

The full set of distinct period arrays for n = 4 is:

1, 1, 1
1, 1, 3
1, 1, 4
1, 2, 2
1, 2, 3
1, 2, 4

Score

I will run your code on my computer running Ubuntu for 10 minutes. Your score is the largest n for which your code terminates in that time. In the case of a tie, the answer that completes the joint largest n fastest wins. In the case that there is a tie within 1 second on timings, the first answer posted wins.

Languages and libraries

You can use any available language and libraries you like. Please include a full explanation for how to run/compile your code in Linux if at all possible.`

Your code should actually compute the answers and not, for example, just output precomputed values.

Leading entries

  • 2 minutes and 21 seconds for n = 128 in C# by Peter Taylor
  • 9 seconds for n = 32 in Rust by isaacg
\$\endgroup\$
11
  • \$\begingroup\$ This made my head hurt. \$\endgroup\$
    – Henry
    Commented Aug 3, 2017 at 19:46
  • 1
    \$\begingroup\$ The challenge is interesting, but I still cannot see objective criterium you're using to distinguish between "precomputed" and "actually computed" answers. If you, for example, cannot understand how my code works, but it gives correct answers for huge n, would you accept it? It is not well defined where is the border between hardcoding and actual computing. \$\endgroup\$
    – user72349
    Commented Aug 3, 2017 at 19:49
  • \$\begingroup\$ A123903? \$\endgroup\$
    – H.PWiz
    Commented Aug 3, 2017 at 20:28
  • 1
    \$\begingroup\$ @ThePirateBay codegolf.meta.stackexchange.com/a/1063/9206 . It's a standard rule. \$\endgroup\$
    – user9206
    Commented Aug 3, 2017 at 20:29
  • 2
    \$\begingroup\$ @Cowsquack All but the first three letters of the string is abcab. All but the last 3 letters is abcab. These match, and removing a smaller number of letters does not match. \$\endgroup\$
    – isaacg
    Commented Aug 7, 2017 at 3:34

7 Answers 7

9
+50
\$\begingroup\$

C#, n=128 in about 2:40

using System;
using System.Collections.Generic;
using System.Linq;

namespace Sandbox
{
    class PPCG137436
    {
        public static void Main(string[] args)
        {
            if (args.Length == 0) args = new string[] { "1", "2", "4", "8", "16", "32", "64", "128" };

            foreach (string arg in args)
            {
                Console.WriteLine(Count(new int[(int)(0.5 + Math.Log(int.Parse(arg)) / Math.Log(2))], 0));
            }
        }

        static int Count(int[] periods, int idx)
        {
            if (idx == periods.Length)
            {
                //Console.WriteLine(string.Join(", ", periods));
                return 1;
            }

            int count = 0;
            int p = idx == 0 ? 1 : periods[idx - 1];
            for (int q = p; q <= 1 << (idx + 1); q++)
            {
                periods[idx] = q;
                if (q == p || q > 1 << idx || p + q - Gcd(p, q) > 1 << idx && UnificationPasses(periods, idx, q)) count += Count(periods, idx + 1);
            }

            return count;
        }

        private static int Gcd(int a, int b)
        {
            while (a > 0) { int tmp = a; a = b % a; b = tmp; }
            return b;
        }

        private static bool UnificationPasses(int[] periods, int idx, int q)
        {
            UnionSet union = new UnionSet(1 << idx);
            for (int i = 0; i <= idx; i++)
            {
                for (int j = 0; j + periods[i] < Math.Min(2 << i, 1 << idx); j++) union.Unify(j, j + periods[i]);
            }

            IDictionary<int, long> rev = new Dictionary<int, long>();
            for (int k = 0; k < 1 << idx; k++) rev[union.Find(k)] = 0L;
            for (int k = 0; k < 1 << idx; k++) rev[union.Find(k)] |= 1L << k;

            long zeroes = rev[union.Find(0)]; // wlog the value at position 0 is 0

            ISet<int> onesIndex = new HashSet<int>();

            // This can be seen as the special case of the next loop where j == -1.
            for (int i = 0; i < idx; i++)
            {
                if (periods[i] == 2 << i) onesIndex.Add((2 << i) - 1);
            }
            for (int j = 0; j < idx - 1 && periods[j] == 2 << j; j++)
            {
                for (int i = j + 1; i < idx; i++)
                {
                    if (periods[i] == 2 << i)
                    {
                        for (int k = (1 << j) + 1; k <= 2 << j; k++) onesIndex.Add((2 << i) - k);
                    }
                }
            }

            for (int i = 1; i < idx; i++)
            {
                if (periods[i] == 1) continue;

                int d = (2 << i) - periods[i];
                long dmask = (1L << d) - 1;
                if (((zeroes >> 1) & (zeroes >> periods[i]) & dmask) == dmask) onesIndex.Add(periods[i] - 1);
            }

            long ones = 0L;
            foreach (var key in onesIndex) ones |= rev[union.Find(key)];

            if ((zeroes & ones) != 0) return false; // Definite contradiction!

            rev.Remove(union.Find(0));
            foreach (var key in onesIndex) rev.Remove(key);

            long[] masks = System.Linq.Enumerable.ToArray(rev.Values);

            int numFilteredMasks = 0;
            long set = 0;
            long M = 0;
            for (int i = 1; i <= idx; i++)
            {
                if (periods[i - 1] == 1) continue;

                // Sort the relevant masks to the start
                if (i == idx) numFilteredMasks = masks.Length; // Minor optimisation: skip the filter because we know we need all the masks
                long filter = (1L << (1 << i)) - 1;
                for (int j = numFilteredMasks; j < masks.Length; j++)
                {
                    if ((masks[j] & filter) != 0)
                    {
                        var tmp = masks[j];
                        masks[j] = masks[numFilteredMasks];
                        masks[numFilteredMasks++] = tmp;
                    }
                }

                // Search for a successful assignment, using the information from the previous search to skip a few initial values in this one.
                set |= (1L << numFilteredMasks) - 1 - M;
                M = (1L << numFilteredMasks) - 1;
                while (true)
                {
                    if (TestAssignment(periods, i, ones, masks, set)) break;
                    if (set == 0) return false; // No suitable assignment found

                    // Gosper's hack with variant to reduce the number of bits on overflow
                    long c = set & -set;
                    long r = set + c;
                    set = (((r ^ set) >> 2) / c) | (r & M);
                }
            }

            return true;
        }

        private static bool TestAssignment(int[] periods, int idx, long ones, long[] masks, long assignment)
        {
            for (int j = 0; j < masks.Length; j++, assignment >>= 1) ones |= masks[j] & -(assignment & 1);
            for (int i = idx - 1; i > 0; i--) // i == 0 is already handled in the unification process.
            {
                if (Period(ones, 2 << i, periods[i - 1]) < periods[i]) return false;
            }

            return true;
        }

        private static int Period(long arr, int n, int min)
        {
            for (int p = min; p <= n; p++)
            {
                // If the bottom n bits have period p then the bottom (n-p) bits equal the bottom (n-p) bits of the integer shifted right p
                long mask = (1L << (n - p)) - 1L;
                if ((arr & mask) == ((arr >> p) & mask)) return p;
            }

            throw new Exception("Unreachable");
        }

        class UnionSet
        {
            private int[] _Lookup;

            public UnionSet(int size)
            {
                _Lookup = new int[size];
                for (int k = 0; k < size; k++) _Lookup[k] = k;
            }

            public int Find(int key)
            {
                var l = _Lookup[key];
                if (l != key) _Lookup[key] = l = Find(l);
                return l;
            }

            public void Unify(int key1, int key2)
            {
                int root1 = Find(key1);
                int root2 = Find(key2);

                if (root1 < root2) _Lookup[root2] = root1;
                else _Lookup[root1] = root2;
            }
        }
    }
}

Extending to n=256 would require switching to BigInteger for the masks, which probably kills the performance too much for n=128 to pass without new ideas, let alone n=256.

Under Linux, compile with mono-csc and execute with mono.

Basic explanation

I'm not going to do a line-by-line dissection, just an overview of the concepts.

As a rule of thumb, I'm happy to iterate through on the order of 250 elements in a brute-force combinatoric program. To get to n=128 it is therefore necessary to use an approach which doesn't analyse every bitstring. So rather than working forwards from bit strings to period sequences, I work backwards: given a period sequence, is there a bitstring which realises it? For n=2x there's an easy upper bound of 2x(x+1)/2 period sequences (vs 22x bitstrings).

Some of the arguments use the string periodicity lemma:

Let p and q be two periods of a string of length n. If p + q ≤ n + gcd(p, q) then gcd(p, q) is also a period of the string.

Wlog I will assume that all of the bitstrings under consideration start with 0.

Given a period sequence [p1 p2 ... pk] where pi is the period of the prefix of length 2i (p0 = 1 always), there are some easy observations about possible values of pk+1:

  • pk+1 ≥ pk since a period of a string S is also a period of any prefix of S.

  • pk+1 = pk is always a possible extension: just repeat the same primitive string for twice as many characters.

  • 2k < pk+1 ≤ 2k+1 is always a possible extension. It suffices to show this for pk+1 = 2k+1 because an aperiodic string of length L can be extended to an aperiodic string of length L+1 by appending any letter which isn't its first letter.

    Take a string Sx of length 2k whose period is pk and consider the string SxyS of length 2k+1. Clearly SxyS has a period 2k+1. Suppose its period q is smaller.

    Then 2k+1 + q ≤ 2k+1+1 ≤ 2k+1 + gcd(2k+1, q) so by the periodicity lemma gcd(2k+1, q) is also a period of SxyS, and since the greatest divisor is less than or equal to its arguments and q is the smallest period, we require q to be a proper factor of 2k+1. Since its quotient cannot be 2, we have q ≤ (2k+1)/3.

    Now, since q ≤ 2k is a period of SxyS it must be a period of Sx. But the period of Sx is pk. We have two cases:

    1. gcd(pk, q) = pk, or equivalently pk divides exactly into q.
    2. pk + q > 2k + gcd(pk, q) so that the periodicity lemma doesn't force a smaller period.

    Consider first the second case. pk > 2k + gcd(pk, q) - q ≥ 2k+1 - q ≥ 2k+1 - (2k+1)/3 ≥ 2q, contradicting the definition of pk as the period of Sx. Therefore we are forced into the conclusion that pk is a factor of q.

    But since q is a period of Sx and pk is the period of Sx, the prefix of length q is just q/pk copies of the prefix of length pk, so we see that pk is also a period of SxyS.

    Therefore the period of SxyS is either pk or 2k+1. But we have two options for y! At most one choice of y will give period pk, so at least one will give period 2k+1. QED.

  • The periodicity lemma allows us to reject some of the remaining possible extensions.

  • Any extension which hasn't passed a quick-accept or a quick-reject test needs to be tested constructively.

The construction of a bitstring given a period sequence is essentially a satisifiability problem, but it has a lot of structure. There are 2k - pk simple equality constraints implied by each prefix period, so I use a union set data structure to combine bits into independent clusters. This was enough to tackle n=64, but for n=128 it was necessary to go further. I employ two useful lines of argument:

  1. If the prefix of length M is 01M-1 and the prefix of length L > M has period L then the prefix of length L must end in 1M. This is most powerful precisely in the cases which would otherwise have most independent clusters, which is convenient.
  2. If the prefix of length M is 0M and the prefix of length L > M has period L - d with d < M and ends in 0d then it must in fact end in 10d. This is most powerful at the opposite extreme, when the period sequence starts with a lot of ones.

If we don't get an immediate contradiction by forcing the cluster with the first bit (assumed to be zero) to be one then we brute force (with some micro-optimisations) over the possible values for the unforced clusters. Note that the order is in descending number of ones because if the ith bit is a one then the period cannot be i and we want to avoid periods which are shorter than the ones which are already enforced by the clustering. Going down increases the chances of finding a valid assignment early.

\$\endgroup\$
3
  • \$\begingroup\$ This is a really great achievement! I am very impressed. \$\endgroup\$
    – user9206
    Commented Aug 8, 2017 at 18:06
  • \$\begingroup\$ @Lembik, I've simplified and optimised the code and reduced runtime for n=128 by about a third. \$\endgroup\$ Commented Aug 9, 2017 at 16:19
  • 1
    \$\begingroup\$ I would love to know what algorithm you have designed for this. Your code has remarkably little logic in it and must be doing something very clever. \$\endgroup\$
    – user9206
    Commented Aug 9, 2017 at 18:01
7
\$\begingroup\$

Rust, 32, 10s 11s 29s on my laptop

Call it with the bitsize as the command line argument.

Clever techniques: represent bitstrings directly as numbers, use bittwiddling to check for cycles. Only search the first half of the bitstrings, those starting with 0, because the array of periods of a bitstring and its inverse (0s swapped for 1s) are identical. If every possibility for the final position has already occured, I don't search it.

More clever stuff:

To deduplicate each block (strings where the first half of the bits are the same) I use a bitvector, which is much faster than a hashset, since the final cycle lengths don't need hashing.

Also, I skip the first steps of the cycle checking, because I know that the final cycle can't be shorter than the second-to-last cycle.

After lots of profiling, I can now tell that almost all of the time is being used productively, so algorithmic improvements will be required to improve from here, I think. I also switched to 32 bit integers to save a little more time.

//extern crate cpuprofiler;
//use cpuprofiler::PROFILER;

extern crate bit_vec;
use bit_vec::BitVec;

use std::collections::HashSet;

fn cycle_len(num: u32, mask: u32, skip_steps: usize) -> usize {
    let mut left = num >> skip_steps;
    let mut mask = mask >> skip_steps;
    let mut steps = skip_steps;
    loop {
        left >>= 1;
        if left == (num & mask) {
            return steps;
        }
        mask >>= 1;
        steps += 1;
    }
}

fn all_cycles(size_log: usize) -> HashSet<Vec<usize>> {
    let mut set = HashSet::new();
    if size_log == 0 {
        set.insert(vec![]);
        return set;
    } else if size_log == 1 {
        set.insert(vec![0]);
        set.insert(vec![1]);
        return set;
    }
    let size: usize = 1 << size_log;
    let half_size: usize = 1 << size_log - 1;
    let shift_and_mask: Vec<(usize, u32)> = (1..size_log)
        .map(|subsize_log| {
            let subsize = 1 << subsize_log;
            (size - subsize, (1 << (subsize - 1)) - 1)
        })
        .collect();
    let size_mask = (1 << (size - 1)) - 1;
    for block in 0..(1 << (half_size - 1)) as u32 {
        let start: u32 = block << half_size;
        if block % 1024 == 0 {
            eprintln!(
                "{} ({:.2}%): {}",
                start,
                start as f64 / (1u64 << size - 1) as f64 * 100f64,
                set.len()
            );
        }
        let leader = {
            let mut cycles = Vec::new();
            for &(shift, mask) in &shift_and_mask {
                let subnum = start >> shift;
                cycles.push(cycle_len(subnum, mask, 0));
            }
            cycles
        };
        let &end = leader.last().unwrap();
        if (end..size).all(|count| {
            let mut new = leader.clone();
            new.push(count);
            set.contains(&new)
        })
        {
            continue;
        }
        let mut subset = BitVec::from_elem(size, false);
        for num in start..start + (1 << half_size) {
            subset.set(cycle_len(num, size_mask, end), true);
        }
        for (unique_cycle_len, _) in subset.into_iter().enumerate().filter(|x| x.1) {
            let mut new_l = leader.clone();
            new_l.push(unique_cycle_len);
            set.insert(new_l);
        }
    }
    set
}

fn main() {
    let size: f32 = std::env::args().nth(1).unwrap().parse().unwrap();
    let size_log = size.log2() as usize;
    //PROFILER.lock().unwrap().start("./my-prof.profile").unwrap();
    let cycles = all_cycles(size_log);
    //PROFILER.lock().unwrap().stop().unwrap();
    println!(
        "Number of distinct arrays of periods of bitstrings of length {} is {}",
        1 << size_log,
        cycles.len()
    );
}

Put bit-vec = "0.4.4" in your Cargo.toml

If you'd like to run this, clone this: github.com/isaacg1/cycle then Cargo build --release to build, then Cargo run --release 32 to run.

\$\endgroup\$
7
  • \$\begingroup\$ Looks like eprintln needs a version of rust after 0.16.0. It works if I change it to println. \$\endgroup\$
    – user9206
    Commented Aug 6, 2017 at 17:42
  • \$\begingroup\$ This answer is very impressive. time gives it 27 user seconds on my laptop. \$\endgroup\$
    – user9206
    Commented Aug 6, 2017 at 17:56
  • \$\begingroup\$ @Lembik why are you in such an old version of rust? Rust 1.0 came out years ago. \$\endgroup\$
    – isaacg
    Commented Aug 6, 2017 at 20:00
  • \$\begingroup\$ Typo :) I meant 1.16.0. blog.rust-lang.org/2017/03/16/Rust-1.16.html \$\endgroup\$
    – user9206
    Commented Aug 6, 2017 at 20:30
  • \$\begingroup\$ For the rust newbies, would you mind spelling out exactly how to compile your code using cargo? \$\endgroup\$
    – user9206
    Commented Aug 7, 2017 at 12:24
4
\$\begingroup\$

Rust, 16

use std::collections::HashSet;
use std::io;

fn main() {
	print!("Enter a pow of two:");
	let mut input_text = String::new();
    io::stdin()
        .read_line(&mut input_text)
        .expect("failed to read from stdin");

    let n_as_string = input_text.trim();
	match n_as_string.parse::<usize>() {
		Ok(n) => {
			let log2 = (n as f64).log(2_f64) as usize;
			if n != 1 << log2 {
				panic!("{} is not a power of two", n);
			}
			let count = compute_count_array(log2, n);
			println!("n = {} -> count = {}", n, count);
		}
		Err(_) => { panic!("{} is not a number", n_as_string); }
	}
}

fn compute_count_array(log2:usize, n: usize) -> usize {
	let mut z = HashSet::new();

	let mut s:Vec<bool> = vec!(false; n);
	loop {
		let mut y:Vec<usize> = vec!();
		for j in 0..log2+1 {
			let p = find_period(&s[0..1<<j]);
			y.push(p);
		}		
		z.insert(y);
		if !next(&mut s) {
			break;
		}
	}
	z.len()
}

#[inline]
fn find_period(s: &[bool]) -> usize {
	let n=s.len();
	let mut j=1;
	while j<n {
		if s[0..n-j] == s[j..n] {
			return j;
		}
		j+=1;
    }
	n
}	

#[inline]
fn next(s:&mut Vec<bool>) -> bool {
	if s[0] {
		s[0] = false;
		for i in 1..s.len() {
			if s[i] {
				s[i] = false;
			} else {
				s[i] = true;
				return true;
			}
		}
		return false
	} else {
		s[0] = true;
	}
	true
}

Try it online!

Compilation: rustc -O <name>.rs

The string is implemented as a Bool vector.

  • The next function iterate through the combinations ;

  • The find_period takes a Bool slice and returns the period ;

  • The compute_count_array does the job for each "power of two" subsequence of each combinations of Bools.

Theoretically, no overflow is expected until 2^n exceeds the u64 maximum value, ie n > 64. This limit could be outreached with an expensive test on s=[true, true, ..., true].

Bad news is: it returns 317 for n=16, but I don't know why. I don't know either if it will make it in ten minutes for n=32, since the Vec<bool> is not optimized for this kind of computation.

EDIT

  1. I've managed to remove the limit of 64 for n. Now, it won't crash until n is greater than the max usize integer.

  2. I found why the previous code returned 317 for n=32. I was counting sets of periods and not arrays of periods. There were three arrays with the same elements:

    [1, 2, 3, 3, 8] -> {1, 2, 3, 8}
    [1, 2, 3, 8, 8] -> {1, 2, 3, 8}
    [1, 1, 3, 3, 7] -> {1, 3, 7}
    [1, 1, 3, 7, 7] -> {1, 3, 7}
    [1, 1, 3, 3, 8] -> {1, 3, 8}
    [1, 1, 3, 8, 8] -> {1, 3, 8}
    

Now it works. It is still slow but it works.

\$\endgroup\$
2
  • \$\begingroup\$ Here are all 320 for n = 16 bpaste.net/show/3664e25ebc01 . \$\endgroup\$
    – user9206
    Commented Aug 5, 2017 at 14:41
  • 1
    \$\begingroup\$ @Lembik I found the explanation for 317 thanks to your list. \$\endgroup\$
    – jferard
    Commented Aug 6, 2017 at 21:58
2
\$\begingroup\$

C - 16

It fails on values greater than 16 cuz of overflow.

I have no idea how fast this runs cuz im on a chromebook running it on repl.it.

Just implements the question as it reads, goes through all bit strings, calculates the period arrays, and checks if they have already been counted.

#include "stdio.h"
#include <stdbool.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

int per(int s[], int l) {
  int period = 0;
  while (1) {
    period++;
    
    bool check = 1;
    int i;
    for (i=0; i<l-period; i++) {
      if (s[i]!=s[i+period]) {
        check = 0;
        break;
      }
    }
    if (check) {
      return period;
    }
  }
}

bool perar(int* s, int l, int* b, int i) {
  int n = 1;
  int j=0;
  while (n<=l) {
    b[i*l+j] = per(s, n);
    n=n<<1;
    j++;
  }

  for (j=0;j<i;j++) {
    int k;
    bool check = 1;
    for(k=0; k<l; k++) {
      if (b[j*l+k] != b[i*l+k]) {
        check = 0;
        break;
      }
    }
    if (check) {
      return 0;
    }
  }
  return 1;
}

int main(int argc, char* argv[]) {
  int n;
  scanf("%d", &n);
  puts("Running...");
  int i;
  int c = 0;
  int* a = malloc(n*sizeof(int));
  int m=pow(2, n);
  int* b = malloc(m*n*sizeof(int));
  for (i=0; i<m; i++) {
    int j;
    for (j=0; j<n; j++) {
      a[j] = (i>>j)&1;
    }
    c+=perar(a, n, b, i);
  }
  printf("Answer: %d\n", c);
  return 0;
}

Just compile it with gcc, etc.

\$\endgroup\$
8
  • \$\begingroup\$ FYI - It was erroring for 16 then when the code was changed so that the two mallocs were malloc(...int*)) and ...** respectively 16 printed Answer: 320 as expected, however 32 printed Answer: 0 (and pretty quickly). \$\endgroup\$ Commented Aug 3, 2017 at 21:47
  • \$\begingroup\$ @JonathanAllan fixed the stuff, just made b an int*. \$\endgroup\$
    – Maltysen
    Commented Aug 3, 2017 at 21:58
  • \$\begingroup\$ @JonathanAllan the 32 thing is cuz 2**32 overflows the int. Also i'll prolly run out of memory. \$\endgroup\$
    – Maltysen
    Commented Aug 3, 2017 at 22:01
  • \$\begingroup\$ @ThePirateBay i made i and m longs, and that just segfaults when i try 32. repl.it/JwJl/2 I'm guessing it runs out of memory. \$\endgroup\$
    – Maltysen
    Commented Aug 3, 2017 at 22:10
  • \$\begingroup\$ @Maltysen. It seems that it segfaults because you messed something up in allocation/deallocation rather than lack of available memory. I got segfault for n = 8 but after the result is printed, which means that the stack is corrupted. Probably you're somewhere writting beyond allocated memory block(s). \$\endgroup\$
    – user72349
    Commented Aug 3, 2017 at 22:14
2
\$\begingroup\$

Haskell

import qualified Data.Set as S
import Data.Bits

period :: Int -> Int -> Int
period num bits = go (bits-2) (div prefix 2) (clearBit prefix $ bits-1)
  where
  prefix = (2^bits-1) .&. num
  go p x y
    | x == y    = p
    | otherwise = go (p-1) (div x 2) (clearBit y p)

allPeriods :: Int ->  [[Int]]
allPeriods n = map periods [0..div(2^n)2-1]
  where
  periods num = map (period num) powers
  powers = takeWhile (<=n) $ iterate (*2) 2

main = readLn >>= print . S.size . S.fromList . allPeriods

Compile with ghc -O2. Try it online!

Runs in less than 0.1sec on my 6 year old laptop hardware for n=16. n=32 takes 99 92min, so I'm factor 9 or 10 off. I've tried caching the periods in a lookup table so I don't have to recalculate them over and over again, but this quickly runs out of memory on my 4GB machine.

\$\endgroup\$
6
  • \$\begingroup\$ Despite being a factor of 10 off, your code is very good looking. \$\endgroup\$
    – user9206
    Commented Aug 5, 2017 at 17:31
  • \$\begingroup\$ @Lembik. Thanks. I'm just trying an improvement: the above code calculates periods for substrings of length 1, but that's completely unnecessary. Besides not to have to calculate them, it also saves some time when finding unique arrays of periods, because they are all one element shorter. \$\endgroup\$
    – nimi
    Commented Aug 5, 2017 at 17:36
  • \$\begingroup\$ @Lembik: omitting length 1 substrings saves about 7min for n=32. Still far too long. \$\endgroup\$
    – nimi
    Commented Aug 5, 2017 at 19:30
  • \$\begingroup\$ There is a fast linear algorithm for computing the period which might help. \$\endgroup\$
    – user9206
    Commented Aug 5, 2017 at 19:31
  • \$\begingroup\$ Can you really not build a look up table of size 2^16 ? That doesn't seem too big. \$\endgroup\$
    – user9206
    Commented Aug 5, 2017 at 20:09
1
\$\begingroup\$

Python 2 (PyPy), 16

import sys
import math
def do(n):
 masks=[]
 for i in range(n):
  masks+=[(1<<((2<<i)-1))-1]
 s=set()
 bits=1<<n
 for i in xrange(1<<bits):
  r=[0,]*n
  for j in range(len(masks)):
   mask=masks[j]
   k,c=i>>bits-(2<<j),1
   d=k>>1
   while k&mask^d:
    d>>=1
    mask>>=1
    c+=1
   r[j]=c
  s|={tuple(r)}
 return len(s)
print do(int(math.log(int(sys.argv[1]),2)))
\$\endgroup\$
5
  • \$\begingroup\$ :| why does 32 have to take so long \$\endgroup\$
    – ASCII-only
    Commented Aug 6, 2017 at 3:01
  • \$\begingroup\$ I know I can skip half of them but IDK how :/ \$\endgroup\$
    – ASCII-only
    Commented Aug 6, 2017 at 3:02
  • \$\begingroup\$ Your code seems to only output "None" for me. How are you running it? osboxes@osboxes:~/python$ python ascii_user.py 16 None \$\endgroup\$
    – user9206
    Commented Aug 6, 2017 at 7:50
  • \$\begingroup\$ crap sorry this isn't actually what i run \$\endgroup\$
    – ASCII-only
    Commented Aug 6, 2017 at 10:32
  • \$\begingroup\$ @Lembik fixed now \$\endgroup\$
    – ASCII-only
    Commented Aug 6, 2017 at 10:32
1
\$\begingroup\$

[C++], 32, 4 minutes

#include <iostream>
#include <vector>

typedef unsigned int u;
template<typename T, typename U>
u Min(T a, U b) {
    return a < b ? a : b;
}

template<typename T, typename U>
u Max(T a, U b) {
    return a > b ? a : b;
}

u Mask(int n) {
    if (n < 0) n = 0;
    return ~((u)(-1) << n);
}
u MASKS[32];

inline u Rshift(u v, int n) {
    return n < 0 ? v >> (-1*n)
    : n > 0 ? v << n
    : n;
}

int GetNextPeriodId(u pattern, int pattern_width, int prior_id) {
    int period = (prior_id % (pattern_width>>1)) + 1;
    int retval = prior_id * pattern_width;

    for (; period < pattern_width; period+=1) {
        u shift = pattern >> period;
        int remainder = pattern_width-period;
        u mask = MASKS[period];

        for (;remainder >= period && !((pattern ^ shift) & mask);
             shift >>= period, remainder -= period);

        if (remainder > period) continue;
        if (remainder == 0 || !((pattern ^ shift) & MASKS[remainder])) {
            retval += (period-1);
            break;
        }
    }
    if (period == pattern_width) {
        retval += pattern_width-1;
    }
    return retval;
}

int ParseInput(int argc, char** argv) {
    if (argc > 1) {
        switch(atoi(argv[1])) {
            case 1:
                return 1;
            case 2:
                return 2;
            case 4:
                return 4;
            case 8:
                return 8;
            case 16:
                return 16;
            case 32:
                return 32;
            default:
                return 0;
        }
    }
    return 0;
}

void PrintId(u id, int patternWidth) {
    for(;patternWidth > 0; id /= patternWidth, patternWidth >>= 1) {
        std::cout << (id % patternWidth)+1 << ",";
    }
    std::cout << std::endl;
}

int TestAndSet(std::vector<bool>& v, int i) {
    int retval = v[i] ? 0 : 1;
    v[i] = true;
    return retval;
}

std::vector<bool> uniques(1<<15);
int uniqueCount = 0;

void FillUniques(u i, int id, int target_width, int final_width) {
    int half_size = target_width / 2;
    u end = 1u<<(half_size-1);
    u mask = MASKS[half_size];
    u lowers[] = { i, (~i)&mask };
    for (u j = 0ul; j < end; j++) {
        u upper = j << half_size;
        u patterns[] = { (upper|lowers[0]), (upper|lowers[1]) };
        for (int k=0; k < sizeof(patterns)/sizeof(patterns[0]); k+=1) {
            int fid = GetNextPeriodId(patterns[k], target_width, id);
            if (target_width != final_width) {
                FillUniques(patterns[k], fid, target_width*2, final_width);
            } else {
                if (TestAndSet(uniques, fid)) {
                    uniqueCount += 1;
                }
            }
        }
    }
}

int main(int argc, char** argv) {
    for (int i = 0; i < 32; i++) {
        MASKS[i] = Mask(i);
    }
    int target_width = 32; // ParseInput(argc, argv);
    if (!target_width) {
        std::cout << "Usage: " << argv[0] << " [1|2|4|8|16|32]" << std::endl;
        return 0;
    }
    if (target_width == 1) {
        std::cout << 1 << std::endl;
        return 0;
    }
    FillUniques(0, 0, 2, target_width);
    std::cout << uniqueCount << std::endl;
    return 0;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.