Распечатать спираль ascii в памяти O (log n)


13

Вы можете написать программу или функцию, которая получает нечетное положительное целое число n , где n >= 3в качестве аргумента функции, аргументов командной строки или STDIN (или аналога для вашей системы), и печатает в STDOUT (или эквивалент в системе) спираль ASCII это вращается вовнутрь по часовой стрелке, где верхний край точно в nдлину символов. Первый правый край должен быть n+1длиной символов, очевидно. Например,

Входные данные:

11

Выход:

***********
          *
********* *
*       * *
* ***** * *
* *   * * *
* * * * * *
* * *** * *
* *     * *
* ******* *
*         *
***********

Уловы:

  • Ваша программа должна использовать не больше, чем O(log n)память .
  • Ваша программа может печатать только символы *(ASCII 42), (ASCII 32), <CR>(ASCII 13) и <LF>(ASCII 10).
  • Ваша программа должна печатать строку, а не возвращать ее из функции.
  • Ограничение Большого-вывод только по памяти , нет нет ограничений на время выполнения .
  • Завершающий перевод строки не является обязательным.
  • Если ваш язык не поддерживает большие целочисленные типы, вам не обязательно поддерживать более высокий уровень, чем тот, который он поддерживает, но вы не можете использовать это как трюк, чтобы сказать: «О, ну, мне не нужно поддерживать выше X, поэтому я можно просто сделать огромный массив максимального размера каждый раз

Стандартные лазейки, как обычно, запрещены.


2
Я не верю, что это возможно. Нельзя сохранить ввод nв O (1) памяти.
xnor

@xnor "O (1) представляет собой постоянное использование памяти. Таким образом, количество входных данных несущественно" - если входное значение n соответствует целому числу, то я уверен, что оно может быть закодировано в постоянное использование памяти.
Андре

1
Хранение ввода nзанимает log nбиты. Как nстановится все больше, так что делает пространство , необходимое для ее хранения. Возможно, вы говорите сделать это с ограниченным числом переменных?
xnor

Или, альтернативно, есть ли ограничение на n?
Sp3000

Я думаю, он говорит, что вы не можете сохранить весь вывод одновременно, а просто распечатать все сразу, потому что это станет больше. Вы, вероятно, должны распечатать его рекурсивно.
Джейкоб

Ответы:


9

C 125 121 байт

Версия для гольфа Это не имеет переменной k. Переменная kиспользуется в версии без заглядывания просто для удобства чтения. Также forпереупорядочены условия цикла и {}удален один набор ненужных . Другой набор {}может быть удален путем миграции puts("")внутри скобок jцикла в позиции инициализации, но это будет означать перевод строки в начале вывода, поэтому я этого не сделал.

f(n){int i,j;n/=2;for(i=-n-2;i++-n-1;){if(i){for(j=-n-1;j++-n;)putchar(32+10*(n+(j*j<i*i?i:j+(i!=j|i>0))&1));puts("");}}}

Печатает nшироко по n+1высокой спирали, как в примере.

объяснение

В основном я вдвое значения n(округление вниз) и запустить две петли: внешний один iиз -n/2-1к n/2+1для печати строк ( i=0подавлено таким образом мы получаем n+1строку) и внутренние один jиз ( -n/2в n/2Мы используем для печати символов.) expression & 1Печатать полосы и условие, j*j<i*iчтобы решить, печатать ли вертикальные или горизонтальные полосы (вертикальные по бокам, где абсолютная величина iбольше, и горизонтальные сверху и снизу.) Требуется корректировка, +nчтобы помочь с правильным завершением в зависимости от того, n/2является ли нечетным или четный.

kобычно равен 1 и обеспечивает корректировку того факта, что абсолютные значения iнаходятся в диапазоне от 1 до, n/2+1а абсолютные значения jнаходятся в диапазоне от 0 до n/2. Если бы kвсегда было 1, мы бы получили концентрические прямоугольники, но оно инвертируется в 0, когда i==j&i<=0диагональный ряд ячеек инвертируется, создавая спираль.

разряженный в тестовой программе

f(n){
  int i,j,k;
  n/=2;
  for(i=-n-1;i<=n+1;i++){
    if(i){
       for(j=-n;j<=n;j++){
           k=i!=j|i>0;
           putchar(32+10*(n+(j*j<i*i?i:k+j)&1));
         }
       puts("");
     }
  }
} 

int m;
main(){
  scanf("%d",&m);
  f(m);
}

Выход

11
***********
          *
********* *
*       * *
* ***** * *
* *   * * *
* * * * * *
* * *** * *
* *     * *
* ******* *
*         *
***********

9
*********
        *
******* *
*     * *
* *** * *
* * * * *
* *   * *
* ***** *
*       *
*********

3
***
  *
* *
***

1
*
*

Побей меня немного ... +1 это безумно мало!
sudo rm -rf slash


7

C 118 байт

m,p,x,y,d;f(n){for(m=n++/2;p<n*n;x=p%n-m,y=p++/n-m,d=y==x+1&x<0,y-=y>0,d+=x*x>y*y?x:y,putchar(x>m?10:(d+m)%2?32:42));}

Код перед финальной игрой в гольф:

#include <stdio.h>

int m, p, x, y, d;

int f(int n) {
    for (m = n++ / 2; p < n * n; ) {
        x = p % n - m;
        y = p++ / n - m;
        d = y == x + 1 && x < 0;
        y -= y > 0;
        d += x * x > y * y ? x : y;
        if (x > m) {
            putchar(10);
        } else if ((d + m) % 2) {
            putchar(32);
        } else {
            putchar(42);
        }
    }

    return 0;
}

Ключевое наблюдение заключается в том, что шаблон представляет собой почти серию концентрических квадратов. С парой небольших морщин:

  • Размер y на один больше размера x. Это исправляется вычитанием 1 из y для нижней половины, которая по существу повторяет средний ряд.
  • Чтобы превратить прямоугольники в спираль, пиксели вдоль y = x + 1диагонали необходимо инвертировать до середины фигуры.

В остальном код просто зацикливается на всех позициях, вычисляет расстояние Чебышева от центра для каждой позиции и выдает один из двух символов в зависимости от того, является ли расстояние четным или нечетным. И выпуск новой строки для последней позиции каждой строки.

Поскольку существует только несколько скалярных переменных и символы выводятся одна за другой, использование памяти, очевидно, постоянно.


Отличный ответ, но так как вы не инициализируете, pя думаю, что вы не справитесь с meta.codegolf.stackexchange.com/q/4939/15599 . Я также не уверен в объявлении глобальных переменных при отправке функции. Очевидно, мой ответ был бы на 4 байта короче, если бы я сделал это. Я начал мета-публикацию meta.codegolf.stackexchange.com/q/5532/15599
Уровень Река Сент

Да, мне пришло в голову, что я, вероятно, должен инициализировать p.
Рето Коради

3

C ++, 926 байт

#include<iostream>
#include<string>
#include<math.h>
#define S string
using namespace std;S N(S x,int y){S z="";for(int q=0;q<y;q++){z+=x;}return z;}int main(){int n=0,t=0,g=0,fi=1;cin>>n;int t1[]={0,0,n,0};int t2[]={0,n-2,n-2,1};for(int k=0;k<n+1;k++){if((k>(n-2)/2)&&(k<(n+5)/2)){if(g==0){S d,e;if(!((n+1)%4)){cout<<N("* ",t2[0])<<"  *"<<N(" *",t2[0])<<endl<<N("* ",(n+1)/2)<<endl<<N("* ",t2[0])<<"***"<<N(" *",t2[0])<<endl;t2[2]=n-8-(n-11);t1[2]=n-4-(n-11);t1[0]--;t2[3]--;t1[3]-=2;}else{cout<<N("* ",t1[0])<<"***"<<N(" *",t2[0])<<endl<<N("* ",(n+1)/2)<<endl<<N("* ",t1[0])<<"*  "<<N(" *",t2[0])<<endl;t2[0]--;t1[2]+=2;t2[2]+=6;t1[3]--;t2[1]-=2;t2[3]-=2;}fi=0;}g=5;}else{t=1-t;int*tR;tR=t?t1:t2;cout<<N("* ",tR[0])<<N(t?"*":" ",tR[2])<<N(" *",tR[3])<<endl;if(fi){if(t){t1[0]+=k==0?0:1;t1[2]-=k==0?2:4;t1[3]++;}else{t2[0]++;t2[2]-=4;t2[3]++;}}else{if(t){t1[0]--;t1[2]+=4;t1[3]--;}else{t2[0]--;t2[2]+=4;t2[3]--;}}}}return 0;}

Это не элегантно, но не занимает много памяти для больших n. Кроме того, есть (почти наверняка) около 20 персонажей, которых можно в дальнейшем играть в гольф, но я не могу больше смотреть на это.

Краткое объяснение:

Это разбивает линии в спиралях на два типа: те, которые имеют ****** в середине, и те, которые имеют \ s \ s \ s \ s \ s в середине. Тогда ясно, что каждая строка состоит из нескольких «*», середины и некоторого «*». Выяснить, сколько из каждой вещи просто, если вы посмотрите на шаблон достаточно долго. Сложно было напечатать центр спирали, который я в основном жестко запрограммировал, используя условное выражение. Это оказалось полезным, потому что строки *** и \ s \ s \ s переключаются там нечетно / четно.

тесты:

Вход: 55 (я думаю, что большие выглядят круто)

Выход:

************************************************** *****
                                                      *
************************************************** *** *
* * *
* ************************************************* * *
* * * * *
* * ********************************************* * * * *
* * * * * * *
* * * ***************************************** * * * *
* * * * * * * * *
* * * * ************************************* * * * * *
* * * * * * * * * * *
* * * * * ********************************* * * * * * *
* * * * * * * * * * * * *
* * * * * * ***************************** * * * * * * *
* * * * * * * * * * * * * * *
* * * * * * * ************************* * * * * * * * *
* * * * * * * * * * * * * * * * *
* * * * * * * * ********************* * * * * * * * * *
* * * * * * * * * * * * * * * * * * *
* * * * * * * * * ***************** * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * ************* * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * ********* * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * ***** * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * {- моя программа добавляет пробел здесь, кстати
* * * * * * * * * * * * * *** * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * ******* * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * *********** * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * *************** * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * *
* * * * * * * * * ******************* * * * * * * * * *
* * * * * * * * * * * * * * * * * *
* * * * * * * * *********************** * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * *************************** * * * * * * *
* * * * * * * * * * * * * *
* * * * * * ******************************* * * * * * *
* * * * * * * * * * * *
* * * * * *********************************** * * * * *
* * * * * * * * * *
* * * * *************************************** * * * *
* * * * * * * *
* * * ******************************************* * * *
* * * * * *
* * *********************************************** * *
* * * *
* ************************************************* ** *
* *
************************************************** *****

Входные данные: 3

Выход:

***
  *
* * 
***

Примечание: я не учёный-компьютерщик / студент CS, и я не знаю, как доказать, что это использует O (log n) памяти. Я могу только решить, что делать, основываясь на ссылках в вопросе. Я был бы признателен, если кто-то может подтвердить / опровергнуть, если этот ответ является действительным. Моя логика для правильности этого ответа состоит в том, что он никогда не хранит переменную размера, основанную на n, кроме самого ввода. Вместо этого цикл for, который выполняется n раз, вычисляет целочисленные значения на основе n. Количество этих значений одинаковое, независимо от ввода.

Примечание 2: Это не работает для n = 1 из-за моего метода работы с серединой. Это было бы легко исправить с помощью условных выражений, поэтому, если кто-то находится в пределах нескольких символов от моего ответа, я исправлю это;)

Поиграй с этим на ideone.


Я полагаю, что это действительно так, хотя этот код C ++ в одной строке вроде бы нужно было прочитать. ;) Ваше понимание верно. Вы не можете использовать память с размером, который зависит от n. Типичным примером, который не удовлетворяет требованию, была бы какая-то строка / буфер / массив, который содержит полную строку вывода.
Рето Коради

Поскольку это единственный ответ, я изменил вопрос так, чтобы он не требовал обработки n=1, поскольку я не считаю такой особый случай интересным.
durron597

3

Haskell, 151 байт

(#)=mod
f n=[[if y<= -(abs$x+1)||y>abs x then r$y#2/=n#2 else r$x#2==n#2|x<-[-n..n]]|y<-[-n-1..n+1],y/=0]
r b|b='*'|1<2=' '
p=putStr.unlines.f.(`div`2)

Пример использования:

*Main> p 9
*********
        *
******* *
*     * *
* *** * *
* * * * *
* *   * *
* ***** *
*       *
*********

*Main> p 11
***********
          *
********* *
*       * *
* ***** * *
* *   * * *
* * * * * *
* * *** * *
* *     * *
* ******* *
*         *
***********

Благодаря лени Хаскелла, это работает в постоянной памяти. Он использует очевидный подход, то есть цикл над yи , xи выбор между *и , в зависимости от

  • если текущая позиция выше или ниже диагонали
  • xсоответственно yчетный или нечетный
  • n/2 четный или нечетный

2

Common Lisp - 346

(lambda(n &aux(d 0))(tagbody $ #6=(#7=dotimes(i n)#4=(princ"*"))#2=(#7#(i d)#5=(princ" ")#4#)#3=(terpri)#1=(#7#(i d)#4##5#)(when(> n 0)(#7#(i(1- n))#5#)#4#)#2##3#(when(> n 3)#1##4##4#(incf d)(decf n 4)(go $))(go /)@(decf d)(incf n 4)(when(> n 3)#2##5##4##3#)/ #1#(when(> n 0)#4#)(when(> n 1)(#7#(i(- n 2))#5#)#4#)#2##3##1##6#(when(> d 0)(go @))))

Итеративное решение с постоянным использованием памяти. Вышесказанное интенсивно использует переменные #n=и функции #n#чтения. Несмотря на то, что есть более прямые подходы, здесь я начал с рекурсивной функции и изменил ее, чтобы имитировать рекурсию с помощью gotoоператоров: это, вероятно, нечитаемо.

Выход для всех входных значений от 0 до 59 .

Оригинальная рекурсивная версия с отладочной информацией

(примечание: terpriозначает newline)

(defun spiral (n &optional (d 0) )
  (flet ((prefix ()
           (format t "~4d~4d | " n d)
           (dotimes (i d)
             (princ "a ")))
         (postfix ()
           (dotimes (i d)
             (princ " b"))))
    (when (= d 0) (prefix))
    (dotimes (i n) (princ "c"))
    (postfix)
    (terpri)

    (prefix)
    (when (> n 0)
      (dotimes (i (1- n)) (princ " "))
      (princ "d"))
    (postfix)
    (terpri)

    (when (> n 3)
      (prefix)
      (princ "**")
      (spiral (- n 4) (1+ d))
      (postfix)
      (princ " f")
      (terpri))

    (prefix)
    (when (> n 0)
      (princ "g"))

    (when (> n 1)
      (dotimes (i (- n 2)) (princ " "))
      (princ "h"))
    (postfix)
    (terpri)

    (prefix)
    (dotimes (i n) (princ "i"))
    ))

Например:

(spiral 8)

   8   0 | cccccccc
   8   0 |        d
   8   0 | **cccc b
   4   1 | a    d b
   4   1 | a ** b b
   0   2 | a a  b b
   0   2 | a a  b b
   0   2 | a a  b f
   4   1 | a g  h b
   4   1 | a iiii f
   8   0 | g      h
   8   0 | iiiiiiii

Смотрите также эту пасту со всеми результатами от 0 до 59 (не так, как выше, эта более многословна).

Итеративная версия с отладочной информацией

(defun spiral (n &aux (d 0) )
  (flet ((prefix ()
           (format t "~4d~4d | " n d)
           (dotimes (i d)
             (princ "a ")))
         (postfix ()
           (dotimes (i d)
             (princ " b"))))
    (tagbody
     step-in
       (when (= d 0) (prefix))
       (dotimes (i n) (princ "c"))
       (postfix)
       (terpri)

       (prefix)
       (when (> n 0)
         (dotimes (i (1- n)) (princ " "))
         (princ "d"))
       (postfix)
       (terpri)

       (when (> n 3)
         (prefix)
         (princ "**")

         (incf d)
         (decf n 4)
         (go step-in))

       (go skip)

     step-out
       (decf d)
       (incf n 4)
       (when (> n 3)
         (postfix)
         (princ " f")
         (terpri))

     skip
       (prefix)
       (when (> n 0)
         (princ "g"))

       (when (> n 1)
         (dotimes (i (- n 2)) (princ " "))
         (princ "h"))
       (postfix)
       (terpri)

       (prefix)
       (dotimes (i n) (princ "i"))
       (when(> d 0)(go step-out)))))

Можете ли вы объяснить, как это соответствует ограничению памяти? Я вижу только одну точку рекурсии, и это хорошо, но не могли бы вы рассказать немного подробнее?
durron597

@ durron597 Да, я работаю над этим. В настоящее время это O (n), потому что мы вызываем функцию рекурсивно число времени, пропорциональное nи стек вызовов соответственно увеличивается, но в этом случае мы можем смоделировать рекурсию с двумя циклами: один с nуменьшением и dувеличением (пока n <= 3 ) и еще один с dуменьшением до нуля. У меня не так много времени, чтобы поработать над этим прямо сейчас, но я постараюсь соответственно обновить ответ. Кстати, есть более прямые способы печати спирали, но было интересно попытаться определить ее рекурсивно.
coredump

2

CJam, 72 байта

li_2/:M;)__*{1$mdM-\M-_2$)=2$0<*@_*@_0>-_*e>mQ_M>2*@@+M+2%+'#S+N+N+=o}/;

Это довольно прямое преобразование моего C-решения в CJam. Не такой короткий, как вы обычно ожидаете от решения CJam, но этот действительно страдает от ограничения памяти. Общие преимущества построения результатов в стеке, который автоматически сбрасывается в конце, и использование необычных операций со списком / строкой - все это выходит за пределы окна. Это генерирует и выводит решение по одному символу за раз. Стек содержит только несколько целых чисел во время выполнения и пуст в конце.

Несмотря на то, что это не очень хороший способ использования языка игры в гольф, он все равно значительно короче, чем код на С, только потому, что нотация более компактная.

Объяснение:

li    Get input n.
_2/   Calculate n/2.
:M;   Store it in variable M
)__*  Calculate (n+1)*(n+1), which is the total number of output characters.
      Also keep a copy of n+1 on the stack.
{     Start loop over output character positions.
  1$md  Calculate divmod of position with n+1. This gives y and x of position.
  M-    Subtract M from x.
  \M-   Subtract M from y.
  _     Copy y.
  2$)   Calculate x+1.
  =     Check if y == x+1
  2$0<  Check if x < 0.
  *     Multiply the two check results. This is the result of the flip
        condition for the top-left diagonal to turn the rectangles into a spiral.
  @_*   Calculate x*x.
  @_    Get y to top of stack, and copy it.
  0>-   Subtract 1 from y if it is in the bottom half.
  _*    Calculate y*y.
  e>    Take maximum of x*x and y*y...
  mQ    ... and calculate the square root. This is the absolute value of the
        larger of the two.
  _M>   Check if the value is greater M, which means that this is the
        position of a line end.
  2*    Multiply by 2 so that we can add another condition to it later.
  @     Get result of diagonal flip condition to the stack top.
  @     Get max(x,y) to the top.
  +M+   Add the two, and add M to the whole thing. This value being even/odd
        determines if the output is a # or a space.
  2%    Check if value is odd.
  +     Add to line end condition to get a single ternary condition result.
  '#S+N+N+
        Build string "# \n\n".
  =     Use the condition result to pick the output character out of the string.
  o     Output the character.
}/    End loop over output characters.
;     Pop n+1 value off stack, to leave it empty.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.