Як високо ви можете піти? (Проблема кодування + алгоритми)


34

Тепер, коли кожен розробив свою (часто дивовижну) експертизу кодування на низькому рівні для Наскільки повільним є насправді Python? (Або наскільки швидкою є ваша мова?) Та наскільки повільно Python насправді (частина II)? настав час виклику, який також розтягне вашу здатність вдосконалити алгоритм.

Наступний код обчислює список довжини 9. Позиція iу списку підраховує кількість разів, принаймні iпослідовних нулів, знайдених під час обчислення внутрішніх продуктів між Fта S. Щоб зробити це точно, він повторює всі можливі списки Fдовжини nта списки Sдовжини n+m-1.

#!/usr/bin/python
import itertools
import operator

n=8
m=n+1
leadingzerocounts = [0]*m
for S in itertools.product([-1,1], repeat = n+m-1):
    for F in itertools.product([-1,1], repeat = n):
        i = 0
        while (i<m and sum(map(operator.mul, F, S[i:i+n])) == 0):
            leadingzerocounts[i] +=1
            i+=1
print leadingzerocounts

Вихід є

[4587520, 1254400, 347648, 95488, 27264, 9536, 4512, 2128, 1064]

Якщо ви збільшите n до 10,12,14,16,18,20, використовуючи цей код, він дуже швидко стає занадто повільним.

Правила

  • Завдання полягає в тому, щоб дати правильний вихід на якомога більше n. Релевантні лише рівні значення n.
  • Якщо є нічия, виграш переходить до найшвидшого коду на моїй машині для найбільшого n.
  • Я залишаю за собою право не перевіряти код, який займає більше 10 хвилин.
  • Ви можете змінювати алгоритм будь-яким способом, який вам подобається, якщо він дає правильний результат. Насправді вам доведеться змінити алгоритм, щоб досягти гідного прогресу у виграші.
  • Переможець буде нагороджений через тиждень з моменту встановлення питання.
  • Бонус буде присуджений, коли він належить, а це буде трохи пізніше, коли переможець буде нагороджений.

My Machine Часи синхронізуються на моїй машині. Це стандартна установка ubuntu на восьмиядерний процесор AMD FX-8350. Це також означає, що мені потрібно мати можливість запускати ваш код. Як наслідок, використовуйте лише доступне безкоштовне програмне забезпечення та додайте повні інструкції щодо компіляції та запуску коду.

Статус .

  • C . n = 12 за 49 секунд по @Fors
  • Java . n = 16 в 3:07 від @PeterTaylor
  • C ++ . n = 16 в 2:21 від @ilmale
  • Рпітон . n = 22 в 3:11 від @primo
  • Java . n = 22 за 6:56 від @PeterTaylor
  • Німрод . n = 24 за 9:28 секунд від @ReimerBehrends

Переможцем став Реймер Берендс із записом у Німроді!

Для перевірки має бути вихід для n = 22 [12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680].


Конкурс закінчився, але ... Я запропоную 200 балів за кожне подання, що збільшить n на 2 (протягом 10 хвилин на моєму комп’ютері), поки у мене не вистачить балів. Ця пропозиція відкрита назавжди .


1
"Я залишаю за собою право не перевіряти код, який займає більше декількох хвилин." > Ви повинні вказати точний час на вашій машині, інакше для цього питання не вистачить об'єктивного критерію виграшу.
pastebin.com slash 0mr8spkT

14
Я люблю ці завдання "підвищити швидкість". Якщо ви будуєте комерційний продукт із цими, у вас буде один чорт швидкого продукту, і ви також злий геній .
Rainbolt

1
Можливо, на це зверне увагу більш інформативна назва?
TheDoctor

8
Якщо ми продовжуємо робити подібні завдання, я думаю, що нам слід хоча б намагатися вирішити іншу проблему, щоб вона була цікавою (а не варіацією тієї ж проблеми з додатковими специфікаціями).
grovesNL

2
@Claudiu його CPU має 8 фізичних ядер, але модулі вибору / декодування та FPU діляться між ядрами. Тож, коли вузьке місце знаходиться на одній з таких областей, воно поводиться більше як квадрокок. Зловживайте цілочисельною логікою і уникайте великих розмірів коду, і це більше схоже на 8-ядерний.
Стефан

Відповіді:


20

Німрод (N = 22)

import math, locks

const
  N = 20
  M = N + 1
  FSize = (1 shl N)
  FMax = FSize - 1
  SStep = 1 shl (N-1)
  numThreads = 16

type
  ZeroCounter = array[0..M-1, int]
  ComputeThread = TThread[int]

var
  leadingZeros: ZeroCounter
  lock: TLock
  innerProductTable: array[0..FMax, int8]

proc initInnerProductTable =
  for i in 0..FMax:
    innerProductTable[i] = int8(countBits32(int32(i)) - N div 2)

initInnerProductTable()

proc zeroInnerProduct(i: int): bool =
  innerProductTable[i] == 0

proc search2(lz: var ZeroCounter, s, f, i: int) =
  if zeroInnerProduct(s xor f) and i < M:
    lz[i] += 1 shl (M - i - 1)
    search2(lz, (s shr 1) + 0, f, i+1)
    search2(lz, (s shr 1) + SStep, f, i+1)

when defined(gcc):
  const
    unrollDepth = 1
else:
  const
    unrollDepth = 4

template search(lz: var ZeroCounter, s, f, i: int) =
  when i < unrollDepth:
    if zeroInnerProduct(s xor f) and i < M:
      lz[i] += 1 shl (M - i - 1)
      search(lz, (s shr 1) + 0, f, i+1)
      search(lz, (s shr 1) + SStep, f, i+1)
  else:
    search2(lz, s, f, i)

proc worker(base: int) {.thread.} =
  var lz: ZeroCounter
  for f in countup(base, FMax div 2, numThreads):
    for s in 0..FMax:
      search(lz, s, f, 0)
  acquire(lock)
  for i in 0..M-1:
    leadingZeros[i] += lz[i]*2
  release(lock)

proc main =
  var threads: array[numThreads, ComputeThread]
  for i in 0 .. numThreads-1:
    createThread(threads[i], worker, i)
  for i in 0 .. numThreads-1:
    joinThread(threads[i])

initLock(lock)
main()
echo(@leadingZeros)

Компілювати з

nimrod cc --threads:on -d:release count.nim

(Nimrod можна завантажити тут .)

Це запускається протягом відведеного часу для n = 20 (і для n = 18, коли використовується лише одна нитка, в останньому випадку йде приблизно 2 хвилини).

Алгоритм використовує рекурсивний пошук, обрізаючи дерево пошуку, коли виникає ненульовий внутрішній продукт. Ми також скорочуємо наполовину пошуковий простір, спостерігаючи, що для будь-якої пари векторів (F, -F)нам потрібно враховувати лише один, оскільки інший виробляє такі самі набори внутрішніх продуктів (відкидаючи Sтакож).

Реалізація використовує засоби метапрограмування Nimrod для розгортання / вставки перших кількох рівнів рекурсивного пошуку. Це економить небагато часу при використанні gcc 4.8 та 4.9 як доповнення Nimrod та неабияку суму за кланг.

Простір пошуку можна додатково зменшити, зауваживши, що нам потрібно враховувати лише значення S, які відрізняються парним числом перших N позицій від нашого вибору F. Однак, складність чи потреби в пам'яті, які не масштабуються для великих значень N, враховуючи, що тіло петлі в цих випадках повністю пропускається.

Табличне відображення, де внутрішній продукт дорівнює нулю, виявляється більш швидким, ніж використання будь-якої функції підрахунку бітів у циклі. Мабуть, доступ до столу має досить непогану місцевість.

Схоже, що проблема повинна бути піддана динамічному програмуванню, враховуючи, як працює рекурсивний пошук, але очевидний спосіб зробити це з розумною кількістю пам'яті.

Приклади виходів:

N = 16:

@[55276229099520, 10855179878400, 2137070108672, 420578918400, 83074121728, 16540581888, 3394347008, 739659776, 183838720, 57447424, 23398912, 10749184, 5223040, 2584896, 1291424, 645200, 322600]

N = 18:

@[3341140958904320, 619683355033600, 115151552380928, 21392898654208, 3982886961152, 744128512000, 141108051968, 27588886528, 5800263680, 1408761856, 438001664, 174358528, 78848000, 38050816, 18762752, 9346816, 4666496, 2333248, 1166624]

N = 20:

@[203141370301382656, 35792910586740736, 6316057966936064, 1114358247587840, 196906665902080, 34848574013440, 6211866460160, 1125329141760, 213330821120, 44175523840, 11014471680, 3520839680, 1431592960, 655872000, 317675520, 156820480, 78077440, 39005440, 19501440, 9750080, 4875040]

Для порівняння алгоритму з іншими реалізаціями, N = 16 займає на моїй машині приблизно 7,9 секунди при використанні однієї нитки і 2,3 секунди при використанні чотирьох ядер.

N = 22 займає близько 15 хвилин на 64-ядерній машині з gcc 4.4.6 як резервний сервер Nimrod і переповнює 64-бітні цілі числа leadingZeros[0](можливо, не підписані, не дивилися на це).


Оновлення: я знайшов місце для ще кількох удосконалень. По-перше, для заданого значення F, ми можемо Sточно перерахувати перші 16 записів відповідних векторів, оскільки вони повинні різнитися в точно визначених N/2місцях. Таким чином, ми попередньо обчислюємо список бітових векторів, у Nяких встановлені N/2біти, і використовуємо їх для отримання початкової частини Sз F.

По-друге, ми можемо покращити рекурсивний пошук, спостерігаючи, що ми завжди знаємо значення F[N](оскільки MSB дорівнює нулю в бітовому поданні). Це дозволяє нам точно передбачити, у яку галузь ми повторюємось із внутрішнього продукту. Хоча це насправді дозволить нам перетворити весь пошук у рекурсивний цикл, це насправді досить сильно накручує передбачення гілок, тому ми зберігаємо верхні рівні в початковому вигляді. Ми все-таки економимо деякий час, насамперед за рахунок зменшення кількості розгалужень, які ми робимо.

Для певного очищення код тепер використовує непідписані цілі числа та виправляє їх у 64-бітному випадку (про всяк випадок, якщо хтось захоче запустити це в 32-бітній архітектурі).

Загальна швидкість - між коефіцієнтом x3 та x4. N = 22 досі потребує більше восьми ядер, щоб запустити менше 10 хвилин, але на 64-ядерному апараті він знижується приблизно до чотирьох хвилин (з відповідним numThreadsнахилом). Я не думаю, що набагато більше можливостей для вдосконалення без іншого алгоритму.

N = 22:

@[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]

Оновлено знову, використовуючи подальші можливі скорочення простору пошуку. Пробігає приблизно 9:49 хвилин на N = 22 на моїй чотириконтрольній машині.

Остаточне оновлення (я думаю). Кращі класи еквівалентності варіантів F, скорочення часу виконання на N = 22 до 3:19 хвилин 57 секунд (редагувати: я випадково запустив це лише однією ниткою) на своїй машині.

Ця зміна використовує той факт, що пара векторів видає однакові провідні нулі, якщо один може бути перетворений в інший шляхом його обертання. На жаль, досить критична низькорівнева оптимізація вимагає, щоб верхній біт F у бітовому поданні завжди був однаковим, і при використанні цієї еквівалентності скоротивши простір пошуку трохи і скоротивши час виконання приблизно на одну чверть за допомогою іншого простору стану зменшення на F, накладні витрати не усувають оптимізацію низького рівня, ніж компенсують її. Однак виявляється, що цю проблему можна усунути, також враховуючи той факт, що F, які є оберненими одна від одної, також є рівнозначними. Хоча це трохи додало складності обчислення класів еквівалентності, це також дозволило мені зберегти вищезгадану оптимізацію низького рівня, що призвело до прискорення приблизно x3.

Ще одне оновлення для підтримки 128-бітних цілих чисел для накопичених даних. Для компіляції з 128 - бітними цілими числами, вам потрібно longint.nimвід сюди і компілювати з -d:use128bit. N = 24 все ще займає більше 10 хвилин, але я включив результат нижче для зацікавлених.

N = 24:

@[761152247121980686336, 122682715414070296576, 19793870419291799552, 3193295704340561920, 515628872377565184, 83289931274780672, 13484616786640896, 2191103969198080, 359662314586112, 60521536552960, 10893677035520, 2293940617216, 631498735616, 230983794688, 102068682752, 48748969984, 23993655296, 11932487680, 5955725312, 2975736832, 1487591936, 743737600, 371864192, 185931328, 92965664]

import math, locks, unsigned

when defined(use128bit):
  import longint
else:
  type int128 = uint64 # Fallback on unsupported architectures
  template toInt128(x: expr): expr = uint64(x)

const
  N = 22
  M = N + 1
  FSize = (1 shl N)
  FMax = FSize - 1
  SStep = 1 shl (N-1)
  numThreads = 16

type
  ZeroCounter = array[0..M-1, uint64]
  ZeroCounterLong = array[0..M-1, int128]
  ComputeThread = TThread[int]
  Pair = tuple[value, weight: int32]

var
  leadingZeros: ZeroCounterLong
  lock: TLock
  innerProductTable: array[0..FMax, int8]
  zeroInnerProductList = newSeq[int32]()
  equiv: array[0..FMax, int32]
  fTable = newSeq[Pair]()

proc initInnerProductTables =
  for i in 0..FMax:
    innerProductTable[i] = int8(countBits32(int32(i)) - N div 2)
    if innerProductTable[i] == 0:
      if (i and 1) == 0:
        add(zeroInnerProductList, int32(i))

initInnerProductTables()

proc ror1(x: int): int {.inline.} =
  ((x shr 1) or (x shl (N-1))) and FMax

proc initEquivClasses =
  add(fTable, (0'i32, 1'i32))
  for i in 1..FMax:
    var r = i
    var found = false
    block loop:
      for j in 0..N-1:
        for m in [0, FMax]:
          if equiv[r xor m] != 0:
            fTable[equiv[r xor m]-1].weight += 1
            found = true
            break loop
        r = ror1(r)
    if not found:
      equiv[i] = int32(len(fTable)+1)
      add(fTable, (int32(i), 1'i32))

initEquivClasses()

when defined(gcc):
  const unrollDepth = 4
else:
  const unrollDepth = 4

proc search2(lz: var ZeroCounter, s0, f, w: int) =
  var s = s0
  for i in unrollDepth..M-1:
    lz[i] = lz[i] + uint64(w)
    s = s shr 1
    case innerProductTable[s xor f]
    of 0:
      # s = s + 0
    of -1:
      s = s + SStep
    else:
      return

template search(lz: var ZeroCounter, s, f, w, i: int) =
  when i < unrollDepth:
    lz[i] = lz[i] + uint64(w)
    if i < M-1:
      let s2 = s shr 1
      case innerProductTable[s2 xor f]
      of 0:
        search(lz, s2 + 0, f, w, i+1)
      of -1:
        search(lz, s2 + SStep, f, w, i+1)
      else:
        discard
  else:
    search2(lz, s, f, w)

proc worker(base: int) {.thread.} =
  var lz: ZeroCounter
  for fi in countup(base, len(fTable)-1, numThreads):
    let (fp, w) = fTable[fi]
    let f = if (fp and (FSize div 2)) == 0: fp else: fp xor FMax
    for sp in zeroInnerProductList:
      let s = f xor sp
      search(lz, s, f, w, 0)
  acquire(lock)
  for i in 0..M-1:
    let t = lz[i].toInt128 shl (M-i).toInt128
    leadingZeros[i] = leadingZeros[i] + t
  release(lock)

proc main =
  var threads: array[numThreads, ComputeThread]
  for i in 0 .. numThreads-1:
    createThread(threads[i], worker, i)
  for i in 0 .. numThreads-1:
    joinThread(threads[i])

initLock(lock)
main()
echo(@leadingZeros)

Результат при N = 22 дорівнює 12410090985684467712, який займає 63,42 біта і, таким чином, вписується в 64-розрядний без підпису.
Стефан

2
Ви, безумовно, дуже ефектно підняли планку.

1
Приємно бачити, хто використовує Nimrod. :)
cjfaure

@Stefan Можливо, ваш майстер кодування міг би отримати цей метод нижче 10 хвилин за N = 22?

Я спробував N = 22, який закінчився через кілька годин. Однак це дає мені [-6036653088025083904, +2087229562810269696, 351473149499408384, +59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680], що здається помилкою переповнення. Я не знаю німрод, але чи можна використовувати неподписані вставки для вирішення цього питання?

11

Java ( n=22?)

Я думаю, що більшість відповідей краще, ніж n=16використовувати аналогічний підхід до цього, хоча вони відрізняються між собою симетрією, якою вони користуються, і способом розподілу завдання між нитками.

Вектори, визначені у запитанні, можна замінити бітовими рядками, а внутрішній виріб за допомогою XORing перекриваючого вікна та перевірки наявності точно n/2встановлених бітів (а значить, і n/2бітів очищених). Є n! / ((n/2)!)(центральний двочленний коефіцієнт) рядки nбітів з n/2набором бітів (які я називаю врівноваженими рядками), тому для будь-яких даних Fіснує багато вікон, Sякі дають нульовий внутрішній добуток. Більше того, дія ковзання Sпо одному та перевірка того, чи зможемо ми ще знайти вхідний біт, який дає нульовий внутрішній добуток, відповідає пошуку краю в графі, чиї вузли - це вікна та чиї краї пов'язують вузол uіз вузлом v, перші n-1біти якого є останнімиn-1біт u.

Наприклад, з n=6та F=001001ми отримуємо цей графік:

Графік для F = 001001

і F=001011ми отримуємо цей графік:

Графік для F = 001011

Потім нам потрібно розраховувати для кожного iз 0до nскільки шляхів довжини iє, підсумовуючи за графіками на кожен F. Я думаю, що більшість із нас використовують пошук у глибині перших.

Зауважте, що графіки є рідкісними: легко довести, що кожен вузол має ступінь не більше 1, а ступінь - не більше одного. Це також означає, що єдиними можливими структурами є прості ланцюги та прості петлі. Це дещо спрощує DFS.

Я використовую пару симетрій: врівноважені рядки закриваються під бітною оберненою ( ~операція багатьма мовами з сімейства ALGOL) та під обертанням бітів, тому ми можемо згрупувати разом значення F, пов'язані цими операціями, і робити лише DFS один раз.

public class CodeGolf26459v8D implements Runnable {
    private static final int NUM_THREADS = 8;

    public static void main(String[] args) {
        v8D(22);
    }

    private static void v8D(int n) {
        int[] bk = new int[1 << n];
        int off = 0;
        for (int i = 0; i < bk.length; i++) {
            bk[i] = Integer.bitCount(i) == n/2 ? off++ : -1;
        }

        int[] fwd = new int[off];
        for (int i = 0; i < bk.length; i++) {
            if (bk[i] >= 0) fwd[bk[i]] = i;
        }

        CodeGolf26459v8D[] runners = new CodeGolf26459v8D[NUM_THREADS];
        Thread[] threads = new Thread[runners.length];
        for (int i = 0; i < runners.length; i++) {
            runners[i] = new CodeGolf26459v8D(n, i, runners.length, bk, fwd);
            threads[i] = new Thread(runners[i]);
            threads[i].start();
        }

        try {
            for (int i = 0; i < threads.length; i++) threads[i].join();
        }
        catch (InterruptedException ie) {
            throw new RuntimeException("This shouldn't be reachable", ie);
        }

        long surviving = ((long)fwd.length) << (n - 1);
        for (int i = 0; i <= n; i++) {
            for (CodeGolf26459v8D runner : runners) surviving -= runner.survival[i];
            System.out.print(i == 0 ? "[" : ", ");
            java.math.BigInteger result = new java.math.BigInteger(Long.toString(surviving));
            System.out.print(result.shiftLeft(n + 1 - i));
        }
        System.out.println("]");
    }

    public final int n;
    protected final int id;
    protected final int numRunners;
    private final int[] bk;
    private final int[] fwd;

    public long[] survival;

    public CodeGolf26459v8D(int n, int id, int numRunners, int[] bk, int[] fwd) {
        this.n = n;
        this.id = id;
        this.numRunners = numRunners;

        this.bk = bk;
        this.fwd = fwd;
    }

    private int dfs2(int[] graphShape, int flip, int i) {
        if (graphShape[i] != 0) return graphShape[i];

        int succ = flip ^ (fwd[i] << 1);
        if (succ >= bk.length) succ ^= bk.length + 1;

        int j = bk[succ];
        if (j == -1) return graphShape[i] = 1;

        graphShape[i] = n + 1; // To detect cycles
        return graphShape[i] = dfs2(graphShape, flip, j) + 1;
    }

    @Override
    public void run() {
        int n = this.n;
        int[] bk = this.bk;
        int[] fwd = this.fwd;

        // NB The initial count is approx 2^(2n - 1.33 - 0.5 lg n)
        // For n=18 we overflow 32-bit
        // 64-bit is good up to n=32.
        long[] survival = new long[n + 1];
        boolean[] visited = new boolean[1 << (n - 1)];
        int th = 0;
        for (int f = 0; f < visited.length; f++) {
            if (visited[f]) continue;

            int m = 1, g = f;
            while (true) {
                visited[g] = true;
                int ng = g << 1;
                if ((ng >> (n - 1)) != 0) ng ^= (1 << n) - 1;
                if (ng == f) break;
                m++;
                g = ng;
            }

            if (th++ % numRunners != id) continue;

            int[] graphShape = new int[fwd.length];
            int flip = (f << 1) ^ f;
            for (int i = 0; i < graphShape.length; i++) {
                int life = dfs2(graphShape, flip, i);
                if (life <= n) survival[life] += m;
            }
        }

        this.survival = survival;
    }
}

На моєму 2.5 ГГц Core 2 я отримую

# n=18
$ javac CodeGolf26459v8D.java && time java CodeGolf26459v8D
[3341140958904320, 619683355033600, 115151552380928, 21392898654208, 3982886961152, 744128512000, 141108051968, 27588886528, 5800263680, 1408761856, 438001664, 174358528, 78848000, 38050816, 18762752, 9346816, 4666496, 2333248, 1166624]

real    0m3.131s
user    0m10.133s
sys     0m0.380s

# n=20
$ javac CodeGolf26459v8D.java && time java CodeGolf26459v8D
[203141370301382656, 35792910586740736, 6316057966936064, 1114358247587840, 196906665902080, 34848574013440, 6211866460160, 1125329141760, 213330821120, 44175523840, 11014471680, 3520839680, 1431592960, 655872000, 317675520, 156820480, 78077440, 39005440, 19501440, 9750080, 4875040]

real    1m8.706s
user    4m20.980s
sys     0m0.564s

# n=22
$ javac CodeGolf26459v8D.java && time java CodeGolf26459v8D
[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]

real    20m10.654s
user    76m53.880s
sys     0m6.852s

Оскільки комп'ютер Lembik має 8 ядер і виконав мою попередню однопотокову програму вдвічі швидше моєї, я впевнений, що вона виконається n=22менше ніж за 8 хвилин.


7:17! Дуже хороша. Ви б не хотіли пояснити свій метод ще трохи?

6

С

Це в основному лише трохи оптимізована реалізація алгоритму у питанні. Він може керувати n=12протягом строку.

#include <stdio.h>
#include <inttypes.h>

#define n 12
#define m (n + 1)

int main() {
    int i;
    uint64_t S, F, o[m] = {0};
    for (S = 0; S < (1LLU << (n + m - 1)); S += 2)
        for (F = 0; F < (1 << (n - 1)); F++)
            for (i = 0; i < m; i++)
                if (__builtin_popcount(((S >> i) & ((1 << n) - 1)) ^ F) == n >> 1)
                    o[i] += 4;
                else
                    break;
    for (i = 0; i < m; i++)
        printf("%" PRIu64 " ", o[i]);
    return 0;
}

Тестовий запуск n=12, включаючи компіляцію:

$ clang -O3 -march=native -fstrict-aliasing -ftree-vectorize -Wall fast.c
$ time ./a.out 
15502147584 3497066496 792854528 179535872 41181184 9826304 2603008 883712 381952 177920 85504 42560 21280 
real    0m53.266s
user    0m53.042s
sys     0m0.068s
$

Коментар: Я просто увімкнув мозок і використав просту комбінаторику, щоб підрахувати, що перше значення завжди буде n! / ((n / 2)!)^2 * 2^(n + m - 1). Мені здається, що має бути цілком алгебраїчне рішення цієї проблеми.


Коли я складаю це, я отримую багато попереджень. Спробуйте gcc -Wall -Wextra Fors.c -o Fors

Було пару невикористаних змінних, забутих попередньою ітерацією, але я видалив їх, так що принаймні пара попереджень повинна була зникнути. На даний момент у мене немає GCC (лише Clang), і Clang наразі не дає мені попереджень (після видалення невикористаних змінних). А так як Кланг зазвичай суворіший, коли мова йде про попередження, я мушу сказати, що я трохи здивований, що ти взагалі отримав попередження.
Форс

Він скаржиться на Fors.c: 13: 17: попередження: запропонуйте круглі дужки навколо '-' в операнді '&' [-Wparentheses] (двічі), а також попереджає: формат '% llu' очікує аргументу типу 'довго довгий непідписаний int ', але аргумент 2 має тип' uint64_t '[-Wformat =]. Насправді Кланг скаржиться на заяву printf і для мене,

З останніми змінами GCC не повинен викидати будь-які попереджувальні повідомлення.
Форс

Він все ще скаржиться на Fors.c: 13: 49: попередження: запропонуйте дужки навколо арифметики в операнді '^' [-Wparentheses] Але в гірших новинах ... на моїй машині потрібно більше 10 хвилин.

5

Java, n=16

Для будь-якого заданого значення Fіснують \binom{n}{n/2}вектори, що мають нульовий внутрішній добуток. Таким чином, ми можемо побудувати графік, вершинами якого є ті вектори, що відповідають, і ребра яких відповідають зсуву S, і тоді нам просто потрібно порахувати шляхи довжини до nцього графіка.

Я не намагався мікрооптимізувати це, замінюючи умовні умови бітовими операціями, але кожен подвійний приріст nзбільшує час роботи приблизно в 16 разів, тому це не призведе до значної різниці, якщо я досить близько до порогу. На своїй машині я ні.

public class CodeGolf26459 {

    public static void main(String[] args) {
        v3(16);
    }

    // Order of 2^(2n-1) * n ops
    private static void v3(int n) {
        long[] counts = new long[n+1];
        int mask = (1 << n) - 1;
        for (int f = 0; f < (1 << (n-1)); f++) {
            // Find adjacencies
            long[] subcounts = new long[1 << n];
            for (int g = 0; g < (1 << n); g++) {
                subcounts[g] = Integer.bitCount(f ^ g) == n/2 ? 2 : -1;
            }

            for (int round = 0; round <= n; round++) {
                long count = 0;
                // Extend one bit.
                long[] next = new long[1 << n];
                for (int i = 0; i < (1 << n); i++) {
                    long s = subcounts[i];
                    if (s == -1) next[i] = -1;
                    else {
                        count += s;
                        int j = (i << 1) & mask;
                        if (subcounts[j] >= 0) next[j] += s;
                        if (subcounts[j + 1] >= 0) next[j + 1] += s;
                    }
                }
                counts[round] += count << (n - round);
                subcounts = next;
            }
        }

        System.out.print("[");
        for (long count : counts) System.out.print(count+", ");
        System.out.println("]");
    }
}

На моєму 2.5 ГГц Core 2 я отримую

$ javac CodeGolf26459.java && time java -server CodeGolf26459 
[55276229099520, 10855179878400, 2137070108672, 420578918400, 83074121728, 16540581888, 3394347008, 739659776, 183838720, 57447424, 23398912, 10749184, 5223040, 2584896, 1291424, 645200, 322600, ]

real    6m2.663s
user    6m4.631s
sys     0m1.580s

Піггейк, оскільки я не хочу зараз реалізовувати власне рішення. Кожна вершина має максимум одного наступника, тому масив вам дійсно не потрібен. Щоб ефективно повторювати комбінації fвершин і стартових вершин, повторіть усі f_xor_gз точно n/2встановленими бітами. Для кожного з них перегляньте все fі візьміть g = f ^ f_xor_g.
Девід Ейзенстат

@David, я знаю, і моя версія 7 робить n = 18 за одну хвилину в моєму нетбуку Atom, але я не можу розмістити його, поки не повернусь з відпустки.
Пітер Тейлор

4

RPython, N = 22 ~ 3: 23

Багатопотокові, використовуючи безрецептурний рекурсивний спуск. Програма приймає два аргументи командного рядка: N та кількість робочих ниток.

from time import sleep

from rpython.rlib.rthread import start_new_thread, allocate_lock
from rpython.rlib.rarithmetic import r_int64, build_int, widen
from rpython.rlib.rbigint import rbigint

r_int8 = build_int('r_char', True, 8)

class ThreadEnv:
  __slots__ = ['n', 'counts', 'num_threads',
               'v_range', 'v_num', 'running', 'lock']

  def __init__(self):
    self.n = 0
    self.counts = [rbigint.fromint(0)]
    self.num_threads = 0
    self.v_range = [0]
    self.v_num = 0
    self.running = 0
    self.lock = None

env = ThreadEnv()

bt_bits = 12
bt_mask = (1<<bt_bits)-1
# computed compile time
bit_table = [r_int8(0)]
for i in xrange(1,1<<bt_bits):
  bit_table += [((i&1)<<1) + bit_table[i>>1]]

def main(argv):
  argc = len(argv)
  if argc < 2 or argc > 3:
    print 'Usage: %s N [NUM_THREADS=2]'%argv[0]
    return 1

  if argc == 3:
    env.num_threads = int(argv[2])
  else:
    env.num_threads = 2

  env.n = int(argv[1])
  env.counts = [rbigint.fromint(0)]*env.n
  env.lock = allocate_lock()

  v_range = []
  v_max = 1<<(env.n-1)
  v_num = 0
  v = (1<<(env.n>>1))-1
  while v < v_max:
    v_num += 1
    v_range += [v]
    if v&1:
      # special case odd v
      s = (v+1)&-v
      v ^= s|(s>>1)
    else:
      s = v&-v
      r = v+s
      # s is at least 2, skip two iterations
      i = 3
      s >>= 2
      while s:
        i += 1
        s >>= 1
      v = r|((v^r)>>i)
  env.v_range = v_range
  env.v_num = v_num

  for i in xrange(env.num_threads-1):
    start_new_thread(run,())

  # use the main process as a worker
  run()

  # wait for any laggers
  while env.running:
    sleep(0.05)

  result = []
  for i in range(env.n):
    result += [env.counts[i].lshift(env.n-i+3).str()]
  result += [env.counts[env.n-1].lshift(3).str()]
  print result
  return 0

def run():
  with env.lock:
    v_start = env.running
    env.running += 1

  n = env.n
  counts = [r_int64(0)]*n
  mask = (1<<n)-1
  v_range = env.v_range
  v_num = env.v_num
  z_count = 1<<(n-2)

  for i in xrange(v_start, v_num, env.num_threads):
    v = v_range[i]
    counts[0] += z_count
    counts[1] += v_num
    r = v^(v<<1)
    for w in v_range:
      # unroll counts[2] for speed
      # ideally, we could loop over x directly,
      # rather than over all v, only to throw the majority away
      # there's a 2x-3x speed improvement to be had here...
      x = w^r
      if widen(bit_table[x>>bt_bits]) + widen(bit_table[x&bt_mask]) == n:
        counts[2] += 1
        x, y = v, x
        o, k = 2, 3
        while k < n:
          # x = F ^ S
          # y = F ^ (S<<1)
          o = k
          z = (((x^y)<<1)^y)&mask
          # z is now F ^ (S<<2), possibly xor 1
          # what S and F actually are is of no consequence

          # the compiler hint `widen` let's the translator know
          # to store the result as a native int, rather than a signed char
          bt_high = widen(bit_table[z>>bt_bits])
          if bt_high + widen(bit_table[z&bt_mask]) == n:
            counts[k] += 1
            x, y = y, z
            k += 1
          elif bt_high + widen(bit_table[(z^1)&bt_mask]) == n:
            counts[k] += 1
            x, y = y, z^1
            k += 1
          else: k = n

  with env.lock:
    for i in xrange(n):
      env.counts[i] = env.counts[i].add(rbigint.fromrarith_int(counts[i]))
    env.running -= 1

def target(*args):
  return main, None

Складати

Зробіть локальний клон сховища PyPy, використовуючи mercurial, git або будь-що інше. Введіть наступний заклик (якщо приведено названий вище сценарій convolution-high.py):

$ pypy %PYPY_REPO%/rpython/bin/rpython --thread convolution-high.py

де %PYPY_REPO%представляє змінну середовища, що вказує на сховище, яке ви щойно клонували. Компіляція займає близько однієї хвилини.


Зразок часу

N = 16, 4 нитки:

$ timeit convolution-high-c 16 4
[55276229099520, 10855179878400, 2137070108672, 420578918400, 83074121728, 16540581888, 3394347008, 739659776, 183838720, 57447424, 23398912, 10749184, 5223040, 2584896, 1291424, 645200, 322600]
Elapsed Time:     0:00:00.109
Process Time:     0:00:00.390

N = 18, 4 нитки:

$ timeit convolution-high-c 18 4
[3341140958904320, 619683355033600, 115151552380928, 21392898654208, 3982886961152, 744128512000, 141108051968, 27588886528, 5800263680, 1408761856, 438001664, 174358528, 78848000, 38050816, 18762752, 9346816, 4666496, 2333248, 1166624]
Elapsed Time:     0:00:01.250
Process Time:     0:00:04.937

N = 20, 4 нитки:

$ timeit convolution-high-c 20 4
[203141370301382656, 35792910586740736, 6316057966936064, 1114358247587840, 196906665902080, 34848574013440, 6211866460160, 1125329141760, 213330821120, 44175523840, 11014471680, 3520839680, 1431592960, 655872000, 317675520, 156820480, 78077440, 39005440, 19501440, 9750080, 4875040]
Elapsed Time:     0:00:15.531
Process Time:     0:01:01.328

N = 22, 4 нитки:

$ timeit convolution-high-c 22 4
[12410090985684467712, 2087229562810269696, 351473149499408384, 59178309967151104, 9975110458933248, 1682628717576192, 284866824372224, 48558946385920, 8416739196928, 1518499004416, 301448822784, 71620493312, 22100246528, 8676573184, 3897278464, 1860960256, 911646720, 451520512, 224785920, 112198656, 56062720, 28031360, 14015680]
Elapsed Time:     0:03:23.156
Process Time:     0:13:25.437

9:26 Ласкаво просимо до 22 екіпажу :)

Я не знаю, чому, але нова версія для мене не швидша. Ще близько 9:30, коли я займаюся часом ./primo-c 22 8.

@ Лембік, що мало б сенс, якби ділення в середньому було приблизно таким же швидким, як 3 зрушення (3 = сума {(n + 1) / (2 ^ n)}, n = 1..десятдесят). Що стосується архітектур сертифікатів, то, мабуть, це могло бути так, але розділення шахти помітно повільніше. Дякуємо, що знайшли час для тестування :)
примі

3

Python 3.3, N = 20, 3,5хв

Відмова: мій намір НЕ розміщувати це як свою власну відповідь, оскільки алгоритм, який я використовую, є лише безсоромним портом від рішення RPython primo . Моє призначення тут - лише показати, що можна зробити в Python, якщо поєднати магію модулів Numpy і Numba .

Нумба пояснив коротко:

Numba - це вчасно спеціалізований компілятор, який збирає анотовані коди Python та NumPy в LLVM (через декораторів). http://numba.pydata.org/

Оновлення 1 : Я помітив, перекинувши цифри навколо, ми можемо просто пропустити деякі цифри повністю. Отже тепер maxf стає (1 << n) // 2, а maxs стає maxf 2 **. Це досить прискорить процес. n = 16 тепер займає лише ~ 48s (вниз від 4,5 хв). У мене також є інша ідея, яку я спробую перевірити, чи зможу я зробити її трохи швидшою.

Оновлення 2: Змінений алгоритм (рішення primo). Хоча мій порт ще не підтримує багатопотоковість, додати досить тривіально. Можна навіть випустити CPython GIL за допомогою Numba та ctypes. Однак це рішення працює дуже швидко і на одній ядрі!

import numpy as np
import numba as nb

bt_bits = 11
bt_mask = (1 << bt_bits) - 1
bit_table = np.zeros(1 << bt_bits, np.int32)

for i in range(0, 1 << bt_bits):
    bit_table[i] = ((i & 1) << 1) + bit_table[i >> 1]

@nb.njit("void(int32, int32, int32, int32, int64[:], int64[:])")
def run(n, m, start, re, counts, result):
    mask = (1 << n) - 1

    v_max = (1 << n) // 2
    rr = v_max // 2

    v = (1 << (n >> 1)) - 1
    while v < v_max:
        s = start

        while s < rr:
            f = v ^ s
            counts[0] += 8
            t = s << 1
            o, j = 0, 1

            while o < j and j < m:
                o = j
                w = (t ^ f) & mask
                bt_high = bit_table[w >> bt_bits]

                if bt_high + bit_table[w & bt_mask] == n:
                    counts[j] += 8
                    t <<= 1
                    j += 1
                elif bt_high + bit_table[(w ^ 1) & bt_mask] == n:
                    counts[j] += 8
                    t = (t | 1) << 1
                    j += 1
                    s += re

            s = v & -v
            r = v + s
            o = v ^ r
            o = (o >> 2) // s
            v = r | o

    for e in range(m):
        result[e] += counts[e] << (n - e)

І, нарешті:

if __name__ == "__main__":
    n = 20
    m = n + 1

    result = np.zeros(m, np.int64)
    counts = np.zeros(m, np.int64)

    s1 = time.time() * 1000
    run(n, m, 0, 1, counts, result)
    s2 = time.time() * 1000

    print(result)
    print("{0}ms".format(s2 - s1))

Це працює на моїй машині в 212688 мс або ~ 3,5 хв.


Спасибі. А як щодо n = 18? :)

Минуло майже 20 хвилин, як я запустив програму, використовуючи n = 18. Я думаю, що можна впевнено сказати, що Python не може вчасно вирішити це навіть з Numba, використовуючи саме цей алгоритм.
Анна Джокела

Я оптиміст, що існує кращий алгоритм.

Я спробував встановити програму numba, але він говорить, що не може знайти llvmpy. Я спробував встановити sudo pip llvmpy, але він говорить, що він не може знайти versioneer. Я спробував встановити sudo pip versioneer, але він говорить, що я вже маю.

Хоча у мене ще немає нумби для роботи (я думаю, що мені доведеться встановити анаконду в підсумку), я вражений цим. Питання полягає в тому, чи можете ви змусити її вирішити N = 22, використовуючи аналогічний для nimrod метод?

2

C ++ N = 16

Я тестую на EEEPC з атомом .. мій час не має великого сенсу. : D
Атом вирішити n = 14 за 34 секунди. І n = 16 за 20 хвилин. Я хочу перевірити n = 16 на ПК ПК. Я оптиміст.

Ідея полягає в тому, що кожного разу, коли ми знаходимо рішення для заданого F, ми знаходимо рішення 2 ^ i, оскільки ми можемо змінити нижню частину S, що веде до того ж результату.

#include <stdio.h>
#include <cinttypes>
#include <cstring>

int main()
{
   const int n = 16;
   const int m = n + 1;
   const uint64_t maxS = 1ULL << (2*n);
   const uint64_t maxF = 1ULL << n;
   const uint64_t mask = (1ULL << n)-1;
   uint64_t out[m]={0};
   uint64_t temp[m] = {0};
   for( uint64_t F = 0; F < maxF; ++F )
   {
      for( uint64_t S = 0; S < maxS; ++S )
      {
         int numSolution = 1;
         for( int i = n; i >= 0; --i )
         {
            const uint64_t window = S >> i;
            if( __builtin_popcount( mask & (window ^ F) ) == (n / 2) )
            {
               temp[i] += 1;
            } else {
               numSolution = 1 << i;
               S += numSolution - 1;
               break;
            }
         }
         for( int i = n; i >= 0; --i )
         {
            out[i] += temp[i]*numSolution;
            temp[i] = 0;
         }
      }
   }
   for( int i = n; i >= 0; --i )
   {
      uint64_t x = out[i];
      printf( "%lu ", x );
   }
   return 0;
}

скласти:

gcc 26459.cpp -std = c ++ 11 -O3 -march = native -fstrict-aliasing -ftree-vectorize -Wall -pedantic -o 26459


1
Це чудово. У мене є кілька напівзапечених ідей насправді, як вирішити це для більшої російської. Ви хотіли б їх почути чи це може зіпсувати конкуренцію?

2

JAVASCRIPT n: 12

На моєму комп’ютері це зайняло 231.242 секунди. У демонстраційній програмі я використовую веб-працівників, щоб запобігти замерзанню браузера. Це впевнене можна вдосконалити разом з паралельними працівниками. Я знаю, що JS не має шансів у цьому виклику, але я це зробив заради задоволення!

Клацніть, щоб запустити демонстрацію онлайн

var n = 8;        
var m = n + 1;
var o = [];
var popCount = function(bits) {
  var SK5  = 0x55555555,
      SK3  = 0x33333333,
      SKF0 = 0x0f0f0f0f,
      SKFF = 0xff00ff;

  bits -= (bits >> 1) & SK5;
  bits  = (bits & SK3) + ((bits >> 2) & SK3);
  bits  = (bits & SKF0) + ((bits >> 4) & SKF0);
  bits += bits >> 8;

  return (bits + (bits >> 15)) & 63;
};
for(var S = 0; S < (1 << n + m - 1); S += 2){
  for(var F = 0; F < (1 << n - 1); F += 1){
    for (var i = 0; i < m; i++){
      var c = popCount(((S >> i) & ((1 << n) - 1)) ^ F);
      if(c == n >> 1){
        if(!o[i]) o[i] = 0;
        o[i] += 4;
      } else break;
    }
  }
}
return o;

А що з одним із нових (швидких) двигунів javascript? Чи можна їх використовувати?

Ви маєте на увазі щось на зразок дротика ?
rafaelcastrocouto

1
Насправді я помиляюся. Ви також можете просто спробувати як firefox, так і хром. Якщо ви не хочете написати це в asm.js звичайно :)

1
виклик прийнятий ... я зроблю це!
rafaelcastrocouto

1
Спробував це і зайняв мій комп'ютер 5,4 секунди, щоб зробити n=22 [235388928,86292480,19031048,5020640,1657928,783920,545408,481256,463832,460256,459744,459744,459744,459744,459744,459744,459744,459744,459744,459744,459744,459744] i.imgur.com/FIJa2Ch.png
Spedwards

1

Фортран: n = 12

Я просто зробив швидку версію тридцяти у Fortran, не було оптимізацій, крім OpenMP. Він повинен затиснути трохи нижче 10 хвилин на n = 12 на машині OPs, це займає 10:39 на моїй машині, яка плавно повільніше.

64-бітні цілі числа дійсно мають негативний вплив на продуктивність; здогадуюсь, мені доведеться переосмислити весь алгоритм, щоб це було набагато швидше. Не знаю, чи я буду турбуватись, я думаю, що я краще витрачу деякий час на обдумування гарного виклику, який більше відповідає моїм власним смакам. Якщо хтось хоче взяти це і побігти з ним, продовжуйте :)

program golf
use iso_fortran_env
implicit none
integer, parameter ::  n=12
integer :: F(n), S(2*n)
integer(int64) :: leadingzerocounts(n+1)
integer :: k
integer(int64) :: i,j,bindec,enc

leadingzerocounts=0

!$OMP parallel do private(i,enc,j,bindec,S,F,k) reduction(+:leadingzerocounts) schedule(dynamic)
do i=0,2**(2*n)-1
  enc=i
  ! Short loop to convert i into the array S with -1s and 1s
  do j=2*n,1,-1
    bindec=2**(j-1)
    if (enc-bindec .ge. 0) then
      S(j)=1
      enc=enc-bindec
    else
      S(j)=-1
    endif
  end do
  do j=0,2**(n)-1
    ! Convert j into the array F with -1s and 1s
    enc=j
    do k=n,1,-1
      bindec=2**(k-1)
      if (enc-bindec .ge. 0) then
        F(k)=1
        enc=enc-bindec
      else
        F(k)=-1
      endif
    end do
    ! Compute dot product   
    do k=1,n+1
      if (dot_product(F,S(k:k+n-1)) /= 0) exit
      leadingzerocounts(k)=leadingzerocounts(k)+1
    end do
  end do
end do
!$OMP end parallel do

print *, leadingzerocounts

end

1

Луа: n = 16

Відмова: мій намір НЕ розміщувати це як свою власну відповідь, оскільки алгоритм, який я використовую, безсоромно викрадений з розумної відповіді Анни Джокели . яке було безсоромно вкрадено з розумної відповіді Ілмале .

Крім того, він навіть не дійсний - у ньому є неточності, викликані числами з плаваючою комою (було б краще, якби Lua підтримував 64-бітні цілі числа). Однак я все-таки завантажую його, лише щоб показати, наскільки швидко це рішення. Це динамічна мова програмування, і все ж я можу обчислити n = 16 за розумний час (1 хвилина на 800 МГц процесора).

Запуск з LuaJIT, стандартний перекладач занадто повільний.

local bit = require "bit"
local band = bit.band
local bor = bit.bor
local bxor = bit.bxor
local lshift = bit.lshift
local rshift = bit.rshift

-- http://stackoverflow.com/a/11283689/736054
local function pop_count(w)
    local b1 = 1431655765
    local b2 = 858993459
    local b3 = 252645135
    local b7 = 63

    w = band(rshift(w, 1), b1) + band(w, b1)
    w = band(rshift(w, 2), b2) + band(w, b2)
    w = band(w + rshift(w, 4), b3)
    return band(rshift(w, 24) + rshift(w, 16) + rshift(w, 8) + w, b7)
end

local function gen_array(n, value)
    value = value or 0
    array = {}
    for i = 1, n do
        array[i] = value
    end
    return array
end

local n = 16
local u = math.floor(n / 2)
local m = n + 1
local maxf = math.floor(lshift(1, n) / 2)
local maxs = maxf ^ 2
local mask = lshift(1, n) - 1

local out = gen_array(m, 0)
local temp = gen_array(m, 0)


for f = 0, maxf do
    local s = 0
    while s <= maxs do
        local num_solution = 1

        for i = n, 0, -1 do
            if pop_count(band(mask, bxor(rshift(s, i), f))) == u then
                temp[i + 1] = temp[i + 1] + 8
            else
                num_solution = lshift(1, i)
                s = s + num_solution - 1
                break
            end
        end

        for i = 1, m do
            out[i] = out[i] + temp[i] * num_solution
            temp[i] = 0
        end

        s = s + 1
    end
end

for i = m, 1, -1 do
    print(out[i])
end

Дякую. Я думаю, що в останніх версіях lua використовується довгий довгий int, який має бути 64-бітовим у 64-бітній системі. Див. "Lua_integer" за адресою lua.org/work/doc/manual.html .

@Lembik: Цікаво. Так чи інакше, це стандартний Lua (який вже підтримує long longзамість doubleналаштування компіляції), а не LuaJIT.
Конрад Боровський

Я думаю, що я просто помилявся в будь-якому випадку wrt luajit. Потрібно 5.3, якого не існує. Найкраща порада, яку могли дати люди, - "спробувати 5.3-workx".
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.