Розв’яжіть пазл обертання


14

На деяких старих телефонах Nokia з'явилася версія п'ятнадцяти головоломки під назвою "Поворот". У цьому варіанті, замість того, щоб ковзати по одній плитці одночасно, ви обертали чотири плитки одночасно в одному напрямку.

У цій грі ви б почали з такої дошки:

4 9 2
3 5 7
8 1 6

А обертаючи нижній лівий блок двічі за годинниковою стрілкою та верхній лівий блок один раз за годинниковою стрілкою, ви отримаєте це:

4 9 2
8 3 7
1 5 6

4 9 2
1 8 7
3 5 6

1 4 2
8 9 7
3 5 6

і 1плитка була б у лівому верхньому куті, де вона повинна бути. Зрештою, після ще декількох рухів ви закінчите:

1 2 3
4 5 6
7 8 9

яка є "оригінальною" конфігурацією.

Ваше завдання полягає в тому, щоб скласти програму, яка буде приймати як введення 3x3 сітку чисел від 1 до 9 (у будь-якому обраному вами форматі) і повертати як вихід послідовність рухів, що представляють рухи, які ви повинні зробити, щоб повернути дошку до її початкової конфігурація (знову ж, у будь-якому обраному вами форматі). Юридичні кроки визначаються як переміщення блоку [верх / низ] - [ліво / право] з 4 плиток [за годинниковою / проти годинникової стрілки].

Ваша програма повинна вміти вирішувати всі можливі 3x3 сітки (усі перестановки вирішувані).

Виграє найкоротший код для цього.


...and return as output a sequence of moves representing the moves you must take to return the board back to its originalЧи означає це "назад до 1 2 3\n4 5 6\n7 8 9"? Я не впевнений, як це читати.
підземниймонорельс

Так, я маю на увазі 1 2 3 4 5 6 7 8 9.
Джо З.

1
Я думаю, що на другому та третьому форумі у вашому прикладі повинні бути замінені 3 та 5.
Мартін Ендер

@JoeZ. Я б запропонував змінити його, щоб заявити, що рішення повинно мати обмежену ефективність у гіршому випадку.
HostileFork каже, що не довіряйте SE

Відповіді:


7

GolfScript, 39/83 байт

# Optimized for size:

{.4rand.p.2/+>`{?1420344440`=}+$..$>}do

# Optimized for speed:

6,(7++:t;~{.(1=.@7=9=+4\-rand+..2/+@.@>:s^[3s=0s=2s=4s=1s=]+s|.)9<\t>|}do.$>30764`*

Швидкість проти розміру

Версія з оптимізацією розміру випадковим чином вибирає обертання за годинниковою стрілкою до досягнення потрібної перестановки. Цього достатньо, оскільки обертання проти годинникової стрілки еквівалентно трьом послідовним обертанням однієї площі за годинниковою стрілкою.

Оптимізована швидкість версія робить те саме, за винятком наступних:

  1. Якщо число 1 знаходиться у лівому верхньому куті, воно більше не обертає верхній лівий квадрат.

  2. Якщо число 9 знаходиться в правому нижньому куті, воно більше не обертає нижній правий квадрат.

  3. Етапи заміни позицій 7 і 8 жорстко кодуються, тому є два положення, які дозволяють циклу розірватися.

Окрім зміни алгоритму, версія, що оптимізує швидкість, досягає обертання прямолінійно, тоді як версія, орієнтована на розмір, використовує вбудований сортування GolfScript шляхом картографування. Він також жорстко кодує кінцевий стан (для порівняння) замість сортування стану за кожною ітерацією.

Оптимізована для швидкості версія вимагає меншої кількості ітерацій, і кожна ітерація набагато швидша сама по собі.

Орієнтири

Я використовував наступний код для рандомізації позицій чисел та проведення пробних прогонів, коментуючи рядок, що відповідає тестуваній версії:

[{[
    0:c;10,1>{;2 32?rand}$
    #{c):c;.4rand.2/+>`{?1420344440`=}+$..$>}do
    #6,(7++:t;{.(1=.@7=9=+4\-rand+..2/+@.@>:s^[3s=0s=2s=4s=1s=]+s|.)9<\t>|}do.$>30764`*
],c+}\~*]

$.0='Min: '\+puts .-1='Max: '\+puts ..{+}*\,/'Avg: '\+puts .,2/='Med: '\+

Вихід показує мінімальну та максимальну кількість кроків, необхідних для впорядкування чисел, середню та медіану всіх пробіжок, а також пройдений час у секундах:

$ TIME='\n%e s' time golfscript rotation-test-size.gs <<< 100
Min: 4652
Max: 2187030
Avg: 346668
Med: 216888

21500.10 s
$
$ TIME='\n%e s' time golfscript rotation-test-speed.gs <<< 1000
Min: 26
Max: 23963
Avg: 3036
Med: 2150

202.62 s

На моїй машині (Intel Core i7-3770) середній час виконання версії з оптимізацією розміру становив 3,58 хвилини. Середній час виконання оптимізованої для швидкості версії становив 0,20 секунди. Таким чином, оптимізована швидкість версія приблизно в 1075 разів швидша.

Оптимізована швидкість версія дає обертання в 114 разів менше. Виконання кожного обертання в 9,4 рази повільніше, що головним чином пов’язано з тим, як оновлюється стан.

I / O

Вихід складається з 3-розрядних чисел. MSB встановлюється для обертання проти годинникової стрілки, середній біт встановлюється для нижніх квадратів, а LSB встановлюється для правильних квадратів. Таким чином, 0 (4) - верхній лівий квадрат, 1 (5) верхній правий, 2 (6) нижній лівий і 3 (7) нижній правий.

Оптимізована швидкість версія друкує всі обертання в одному рядку. Версія з оптимізацією розміру друкує по одному обертанню на рядок, а потім кінцеве положення чисел.

Для оптимізованої швидкості версії вхід повинен отримати масив, що містить числа від 1 до 9 при оцінці. Для оптимізованої за розміром версії вхід повинен бути рядком без остаточного нового рядка; це не оцінюється.

Приклад виконання:

$ echo -n '253169748' | golfscript rotation-size.gs
3
0
123456789
$ golfscript rotation-speed.gs <<< '[5 4 7 1 2 9 3 8 6]'
2210300121312212222212211121122211122221211111122211211222112230764

Оптимізований за розміром код

{               #
  .             # Duplicate the state.
  4rand         # Push a randomly chosen integers between 0 and 3.
  .p            # Print that integer.
  .2/+          # Add 1 to it if it is grater than one. Possible results: 0, 1, 3, 4
  >`            # Slice the state at the above index.
  {             # Push a code block doing the following:
    ?           # Get the index of the element of the iteration in the sliced state.
    1420344440` # Push the string "14020344440".
    =           # Retrieve the element at the position of the computed index.
  }+            # Concatenate the code block with the sliced state.
  $             # Sort the state according to the above code block. See below.
  ..$>          # Push two copies of the state, sort the second and compare the arrays.
}do             # If the state is not sorted, repeat the loop.

Оновлення стану досягається наступним чином:

Поворот 2 дає додавання цілого числа 3 після додавання 1. Якщо стан "123456789", розрізання стану дає "456789".

Прямо перед виконанням "$" найвищими елементами стеку є:

[ 1 2 3 4 5 6 7 8 9 ] { [ 4 5 6 7 8 9 ] ? "1420344440" = }

"$" Виконує блок один раз для сортування кожного елемента масиву після натискання на сам елемент.

Індекс 1 у "[4 5 6 7 8 9]" дорівнює -1 (немає), тому останній елемент "1420344440" висувається. Це дає 48, ASCII код, що відповідає символу 0. Для 2 і 3 також висувається 48.

Цілі числа, натиснуті на 4, 5, 6, 7, 8 і 9, складають 49, 52, 50, 48, 51 і 52.

Після сортування перший елемент стану буде одним із елементів, що дають 48; останній буде одним із тих, що дають 52. Вбудований сорт взагалі нестабільний, але я емпірично перевірив, що він стабільний у цьому конкретному випадку.

Результат - «[1 2 3 7 4 6 8 5 9]», що відповідає обертанню нижнього лівого квадрата за годинниковою стрілкою.

Код з оптимізацією швидкості

6,(7++:t;       # Save [ 1 2 3 4 5 7 ] in variable “t” and discard it.
~               # Interpret the input string.
{               #
  :s            # Duplicate the current state.
  (1=           # Unshift the first element and push 1 if it is equal to 1 and 0 otherwise.
  .@            # Duplicate the boolean and rotate the unshifted array on top of it.
  7=9=          # Push 1 if the eighth element of “s” is equal to 9 and 0 otherwise.
  +4\-          # Add the booleans and subtract their sum from 4.
  rand          # Push a randomly chosen integers between 0 and the result from above.
  +.            # Add this integer to the first boolean and duplicate it for the output.
  .2/+          # Add 1 to the result if it is grater than one. Possible results: 0, 1, 3, 4
  @.            # Rotate the state on top of the stack and duplicate it.
  @>:s          # Slice the state at the integer from above and save the result in “s”.
  ^             # Compute the symmetric difference of state and sliced state.
  [             # Apply a clockwise rotation to the sliced array:
    3s=         # The fourth element becomes the first.
    0s=         # The first element becomes the second.
    2s=         # The third element remains the same.
    4s=         # The fifth element becomes the fourth.
    1s=         # The second element becomes the fifth.
  ]             # Collect the results into an array.
  +             # Concatenate with array of elements preceding the slice.
  s|            # Perform set union to add the remaining elements of “s”.
  .             # Duplicate the updated state.
  )9<           # Pop the last element; push 0 if it is equal to 9 and 1 otherwise.
  \t            # Swap the popped state on top and push [ 1 2 3 4 5 7 ].
  >             # Push 0 if the state begins with [ 1 2 3 4 5 6 ] and 1 otherwise.
  |             # Take the logical OR of the booleans.
}do             # If the resulting boolean is 1, repeat the loop.
.$              # Duplicate the state and sort it.
>30764`*        # If the state was not sorted, 7 and 8 are swapped, so push "30764".

Зауважте, що обертання 3, 0, 7, 6 і 4 змінюють місцями в позиціях 7 і 8, не змінюючи позицій решти семи елементів.


Оптимізовано для швидкості? Це Golfscript ...
ıʇǝɥʇuʎs

1
@Synthetica: Тим не менш, це найшвидше рішення, яке було розміщено до цих пір.
Денніс

4

Пітон з Numpy - 158

from numpy import*
A=input()
while any(A.flat>range(1,10)):i,j,k=random.randint(0,2,3);A[i:i+2,j:j+2]=rot90(A[i:i+2,j:j+2],1+2*k);print"tb"[i]+"lr"[j]+"wc"[k]

Введення має бути такого формату:

array([[1,2,5],[4,3,6],[7,8,9]])

Кожен рядок виводу - це хід, закодований у рядках, таких як trwабо, blcякий слід читати так:

  • t: верх
  • b: дно
  • l: зліва
  • r: правильно
  • c: за годинниковою стрілкою
  • w: проти годинникової стрілки (відершини)

Ця програма виконує випадкові рухи до досягнення цільової конфігурації. За приблизним припущенням, що кожен хід має незалежну ймовірність 1/9! щоб потрапити на цільову конфігурацію¹, кількість обертів перед рішенням розподіляється експоненціально із середнім значенням (тобто середньою кількістю ходів) 9! ≈ 3,6 · 10⁵. Це відповідно до короткого експерименту (20 циклів).

¹ 9! будучи загальною кількістю конфігурацій.


2
Так по суті він намагається випадковими рухами, поки не досягне рішення?
Джо З.

Працює для мене. Хоча мене цікавить очікувана кількість обертів до того, як можна буде досягти рішення.
Джо З.

@JoeZ .: Дивіться правки до моєї публікації.
Wrzlprmft

Це круто.
Кайл Канос

4

C ++ найменше рішення ходу - перше вшир (1847 чол.)

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

З попереднього рішення найбільшою зміною було переміщення ключової історії із стану плати (BS) у новий клас, який зберігає історію на заданій глибині (DKH). Кожен раз, коли програма робить крок, вона перевіряє історію на цій глибині та всі глибини, перш ніж перевірити, чи вона коли-небудь була оцінена, якщо так, вона більше не буде додана до черги. Це, здається, значно скорочує сховище у черзі (видаляючи всю цю історію із самого стану плати), а отже, значно скорочує всю тупу обрізку, яку мені довелося зробити, щоб у коду не вичерпалося пам'яті. Плюс це працює набагато швидше, оскільки копіювати на чергу набагато менше.

Тепер це простий першочерговий пошук у різних штатах ради. Плюс, як виявляється, я хочу змінити набір ключів (наразі зберігається як набір чисел у base-9, кожне з яких обчислюється BS :: key як представлення base-9 дошки) на бітсет маючи 9! біти здаються непотрібними; хоча я дізнався, як обчислити ключ у "системі фактичних чисел", який міг би бути використаний для обчислення біта в наборі для тестування / перемикання.

Отже, новітнє рішення:

#include <iostream>
#include <list>
#include <set>
#include <vector>
using namespace std;
struct BS{
#define LPB(i) for(int*i=b;i-b<9;i++)
struct ROP{int t, d;};
typedef vector<ROP> SV;
typedef unsigned int KEY;
typedef set<KEY> KH;
BS(const int*d){const int*x=d;int*y=b;for(;x-d<9;x++,y++)*y=*x;}
BS(){LPB(i)*i=i-b+1;}
bool solved(){LPB(i)if(i-b+1!=*i)return 0;return 1;}
void rot(int t, int d){return rot((ROP){t,d});}
void rot(ROP r){rotb(r);s.push_back(r);}
bool undo(){if (s.empty())return false;ROP &u=s.back();u.d*=-1;rotb(u);s.pop_back();return true;}
SV &sol(){return s;}
KEY key(){KEY rv=0;LPB(i){rv*=9;rv+=*i-1;}return rv;}
int b[9];
SV s;
void rotb(ROP r){int c=r.t<2?r.t:r.t+1;int bi=(r.d>0?3:4)+c;const int*ri=r.d>0?(const int[]){0,1,4}:(const int[]){1,0,3};for(int i=0;i<3;i++)swap(b[bi],b[c+ri[i]]);}
};
ostream &operator<<(ostream &o, BS::ROP r){static const char *s[]={"tl","tr","bl","br"};o<<s[r.t]<<(r.d<0?"w":"c");return o;}
struct DKH{
~DKH(){for(HV::iterator i=h.begin();i<h.end();++i)if(*i!=NULL)delete *i;}
void add(int d,BS b){h.resize(d+1);if(h[d]==NULL)h[d]=new BS::KH();h[d]->insert(b.key());}
bool exists(BS &b){BS::KEY k=b.key();size_t d=min(b.sol().size(),h.size()-1);do if (h[d]->find(k)!=h[d]->end())return true;while(d--!=0);return false;}
typedef vector<BS::KH *> HV;HV h;
};
static bool solve(BS &b)
{
const BS::ROP v[8]={{0,-1},{0,1},{1,-1},{1,1},{2,-1},{2,1},{3,-1},{3,1}};
DKH h;h.add(0,b);
list<BS> q;q.push_back(b);
while (!q.empty())
{
BS qb=q.front();q.pop_front();
if (qb.solved()){b=qb;return true;}
int d=qb.sol().size()+1;
for (int m=0;m<8;++m){qb.rot(v[m]);if (!h.exists(qb)){h.add(d,qb);q.push_back(qb);}qb.undo();}
}
return false;
}
int main()
{
BS b((const int[]){4,9,2,3,5,7,8,1,6});
if (solve(b)){BS::SV s=b.sol();for(BS::SV::iterator i=s.begin();i!=s.end();++i)cout<<*i<<" ";cout<<endl;}
}

1
Ваш код виглядає як C ++ замість C.
user12205

@ace, дійсно так, виправлено.
DreamWarrior

У разі , якщо хто - небудь має проблеми з компіляцією це з г ++, я повинен був змінити всі екземпляри , int[]щоб const int[]і встановити прапор -fpermissive.
Денніс

@Dennis, Вибачте за це, я склав його з двома різними компіляторами g ++, і жоден, здавалося, не проти. Але я можу бачити, як новішу, більш сувору, версію буде скуголити. Спасибі.
DreamWarrior

Зараз добре компілюється, і це набагато швидше. Звертаючись до коментаря, який ви видалили з запитання: Є деякі перестановки, які, здається, потребують 11 кроків. 978654321 - один із них.
Денніс

1

CJam - 39

l{4mr_o_1>+_@m<_[Z0Y4X]\f=\5>+m>__$>}g;

Інший випадковий вирішувач :)
Він займає такий рядок, як 492357816, і виводить (довгі) серії цифр від 0 до 3, кожна з яких обертає блок обертання блоку за годинниковою стрілкою: 0 = вліво-вліво, 1 = праворуч зверху, 2 = знизу -ліво, 3 = праворуч знизу.

Коротке пояснення:

4mrгенерує випадкове число від 0 до 3 з
_1>+кроком число, якщо воно більше 1 (тому ми закінчуємо 0, 1, 3 або 4 - початкові індекси 4 блоків)
m<обертає рядок ліворуч (наприклад, 492357816 -> 923578164, а не блокування обертання) для того, щоб привести блок обертання в першому положенні,
[Z0Y4X]\f=відбувається обертання блоку, яке впливає на перші 5 символів, такі як 12345 -> 41352;
X = 1, Y = 2, Z = 3, тому [Z0Y4X] є насправді [3 0 2 4 1], і це 0-покажчики повернутих плиток, що
5>копіюють, решта рядка
m>повертає (модифіковану) рядок назад до право
__$>перевіряє, чи сортується рядок (це умова зупинки)


1

Математика, 104 чол

Ми можемо інтерпретувати завдання мовою груп перестановок. Чотири обертання - це всього чотири перестановки, які генерують симетричну групу S 9 , і завдання полягає лише в тому, щоб записати перестановку як добуток генераторів. Mathematica має вбудовану функцію для цього.

i={1,2,5,4};GroupElementToWord[PermutationGroup[Cycles/@({i}+#&/@i-1)],Input[]~FindPermutation~Range@9]

Приклад:

Вхід:

{4, 9, 2, 8, 3, 7, 1, 5, 6}

Вихід:

{-2, -3, -4, 2, 4, 1, 4, -1, -2, 3, 2, -4, 3, 4, -3, -3, -4, -4, -2, -2, -3, -2, 3, -1}
  • 1: зверху ліворуч за годинниковою стрілкою
  • 2: вгорі праворуч за годинниковою стрілкою
  • 3: знизу вправо за годинниковою стрілкою
  • 4: знизу вліво за годинниковою стрілкою
  • -1: зверху ліворуч проти годинникової стрілки
  • -2: вгорі праворуч проти годинникової стрілки
  • -3: знизу праворуч проти годинникової стрілки
  • -4: знизу ліворуч проти годинникової стрілки
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.