1

Тема: Порівняння матриць

Дано декілька матриць 2 на 2, кожна у формі одновимірного масиву.
Треба з'ясувати скільки там різних матриць
за умови що якщо обертати одну з матриць на 90, 180 і 270 вправо,
вона може виявитися рівною іншій матриці.
Чи можна якось порівняти такі матриці без обертання?
Можливо, є якийсь унікальний для кожної матриці коефіцієнт, який можна обчислити?

2

Re: Порівняння матриць

Якщо і є, то він має містити не менше ніж 4*n-2 біти (де довжина кожного числа - n біт), бо матрицю можна обертати 4 способами, що усуває 2 біти.

Спробуємо так. Знаходимо максимальне число в матриці. За годинниковою стрілкою від (0,0) обираємо перше положення, де максимальне в лівому верхньому кутку. Це буде нормальна форма матриці. А тепер порівнюєте лише нормальні форми. Якщо хочете їх у числа запхати - ласкаво прошу, для 32-бітних int-ів матимете 128-бітні числа.

Подякували: Teg Miles, leofun01, steamwater3

3

Re: Порівняння матриць

Ем... ні, не так. Знаходимо перше положення з можливих поворотів у лексикографічному порядку. Правда, доведеться все одно кілька порівнянь всередині матриці зробити, зате між різними матрицями - лише одне!

4 Востаннє редагувалося lucas-kane (27.08.2024 10:36:25)

Re: Порівняння матриць

Teg Miles написав:

Чи можна якось порівняти такі матриці без обертання?
Можливо, є якийсь унікальний для кожної матриці коефіцієнт, який можна обчислити?

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

memcmp(a, b, sizeof(int) * 4) == 0

, де a та b матриці розміром 2x2, котрі представлені як одномірний масив (тип звісно int, якщо елементи матриці є цілочисельні елементи!)

Подякували: Teg Miles1

5

Re: Порівняння матриць

Сума - досить слабка геш-функція. І все одно потім треба перевіряти, бо колізії будуть.

Подякували: leofun01, lucas-kane2

6

Re: Порівняння матриць

Teg Miles, у вас проблема цикл для порівняння матриць написати?

7 Востаннє редагувалося Teg Miles (27.08.2024 12:54:54)

Re: Порівняння матриць

koala написав:

Teg Miles, у вас проблема цикл для порівняння матриць написати?

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

int count_different_matrices(const std::vector<std::array<int, 4>> &matrices) {
    int row_size = matrices.size();
    bool loop_break { false };
    
    std::vector<std::array<int, 4>> first { matrices.at(0) };
    for (int i = 1; i < row_size; ++i) {
        std::array<int, 4> second = matrices.at(i);
        for (int k = 0; k < 4; ++k) {
            std::swap(second.at(1), second.at(2));
            std::swap(second.at(0), second.at(1));
            std::swap(second.at(2), second.at(3));
            for (unsigned long m = 0; m < first.size(); ++m) {
                if (second == first.at(m)) {
                    
                    loop_break = true;
                    break;
                }
                if (k == 3 && second != first.at(first.size() - 1)&& std::find(first.begin(), first.end(), second) == first.end()) {
                    first.push_back(second);
                }
            }
            if (loop_break) {
                loop_break = false;
                break;
            }
        }
    }
    return first.size();

Завдання звідси https://www.codewars.com/kata/635fc0497dadea0030cb7936

8

Re: Порівняння матриць

lucas-kane написав:
Teg Miles написав:

Чи можна якось порівняти такі матриці без обертання?
Можливо, є якийсь унікальний для кожної матриці коефіцієнт, який можна обчислити?

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

memcmp(a, b, sizeof(int) * 4) == 0

, де a та b матриці розміром 2x2, котрі представлені як одномірний масив (тип звісно int, якщо елементи матриці є цілочисельні елементи!)

Думав про суми, але все одно треба забагато перевірок робити, тому відкинув цю ідею.

9

Re: Порівняння матриць

Мій розв'язок звідти:

bool are_same(std::array<int, 4> left, const std::array<int, 4>& right) {
  for(int r=0;r<4;++r) {
    if(left==right)
      return true;
    int t = left[0];
    left[0] = left[2];
    left[2] = left[3];
    left[3] = left[1];
    left[1] = t;
  }
  return false;
}

int count_different_matrices(const std::vector<std::array<int, 4>> &matrices) {
  int count = 0;
  for(size_t i=0; i<matrices.size(); ++i)
    if(!std::any_of(matrices.begin()+i+1, matrices.end(), [&](const auto&m){return are_same(matrices[i],m);}))
      count++;
  return count;
}

10

Re: Порівняння матриць

koala написав:

Мій розв'язок звідти:

bool are_same(std::array<int, 4> left, const std::array<int, 4>& right) {
  for(int r=0;r<4;++r) {
    if(left==right)
      return true;
    int t = left[0];
    left[0] = left[2];
    left[2] = left[3];
    left[3] = left[1];
    left[1] = t;
  }
  return false;
}

int count_different_matrices(const std::vector<std::array<int, 4>> &matrices) {
  int count = 0;
  for(size_t i=0; i<matrices.size(); ++i)
    if(!std::any_of(matrices.begin()+i+1, matrices.end(), [&](const auto&m){return are_same(matrices[i],m);}))
      count++;
  return count;
}

Бачу по циклам, що у вас краще, але своє рішення я принаймні розумію:).

Подякували: lucas-kane1

11

Re: Порівняння матриць

Трохи переробив, щоб без змін:

bool are_same(const std::array<int, 4> &left, const std::array<int, 4>& right) {
  int order[4] {0,1,3,2};
  for(int rotation=0; rotation<4;++rotation) {
    bool equal = true;
    for(int i=0; equal && i<4; ++i)
      equal = (left[order[i]]==right[order[(rotation+i)%4]]);
    if(equal)
      return true;
  }
  return false;
}

Одразу ідея - замість залишку збільшити масив order:

bool are_same(const std::array<int, 4> &left, const std::array<int, 4>& right) {
  int order[] {0,1,3,2,0,1,3};
  for(int rotation=0; rotation<4;++rotation) {
    bool equal = true;
    for(int i=0; equal && i<4; ++i)
      equal = (left[order[i]]==right[order[rotation+i]]);
    if(equal)
      return true;
  }
  return false;
}

12

Re: Порівняння матриць

Трохи пітончика

def hash(matrix):
    ROTATION = [0,1,3,2,0,1,3]
    return min(tuple(matrix[r] for r in ROTATION[rot:rot+4]) for rot in range(4))

def count_different_matrices(matrices):
    return len(set(hash(m) for m in matrices))
Подякували: leofun01, lucas-kane2

13

Re: Порівняння матриць

Я хотю бачити красу і симетрію, шось типу такого :

#include <vector>
#include <array>

using arr_t = std::array<int, 4>;
using vec_t = std::vector<arr_t>;

static vec_t const rot = {
    { 0, 1, 2, 3 },
    { 2, 0, 3, 1 },
    { 1, 3, 0, 2 },
    { 3, 2, 1, 0 },
};
static arr_t const &ord = rot.front();

bool same(arr_t const &a, arr_t const &b) {
    return std::any_of(rot.cbegin(), rot.cend(), [&a, &b](arr_t const &x) {
        return std::all_of(ord.cbegin(), ord.cend(), [&a, &b, &x](int const i) {
            return a[i] == b[x[i]];
        });
    });
}
int count_different_matrices(vec_t &m) {
    for(auto b = m.begin(); ++b != m.end(); )
        for(auto a = m.begin(); a != b; ++a)
            if(same(*a, *b)) {
                m.erase(b--);
                break;
            }
    return m.size();
}

Але мені не подобається складність O(n^2) в такому рішеню.

для koala

Зайшов подивити шо там інші пишуть, а там koala вже на вершині Олімпу.
Як тобі вдалось досягнути 1 kyu ?

14

Re: Порівняння матриць

leofun01 написав:

Але мені не подобається складність O(n^2) в такому рішеню.

Ага, я щойно переробив старий розв'язок на С на O(n log(n)).

leofun01 написав:

Як тобі вдалось досягнути 1 kyu ?

Ну от власне так і вдалось

Подякували: leofun011

15

Re: Порівняння матриць

Переклав кату на Rust.

Подякували: leofun011

16 Востаннє редагувалося leofun01 (28.08.2024 22:25:11)

Re: Порівняння матриць

Навіяно пітончиком :
O(n log(n)) (тут була помилка)

O(n), C++
#include <array>
#include <unordered_set>
#include <vector>

int count_different_matrices(std::vector<std::array<int, 4>> const &matrices) {
    std::unordered_set<uint16_t> s;
    for(auto const &a : matrices)
        #define A(n) a[n] << (3 << n & 0xC)
        s.insert(A(0) | A(1) | A(2) | A(3));
        #undef A
    int count = 0;
    for(uint16_t v; !s.empty(); ++count)
        #define E(n) s.erase((v << 16 | v) >> (n << 2))
        v = *s.cbegin(), E(0) | E(1) | E(2) | E(3);
        #undef E
    return count;
}

Людини кажуть шо це не читабельно.

Подякували: lucas-kane1

17

Re: Порівняння матриць

Це ж O(n)

Подякували: leofun011

18

Re: Порівняння матриць

leofun01 написав:

Людини кажуть шо це не читабельно.

Та коментарі були б незайві

Подякували: leofun011

19

Re: Порівняння матриць

Він якимись збоченнями формує число з матриці (виходячи з того, що елементи матриці не більші за 9), пхає те число до unordered_set, а потім видаляє з тієї множини по 4 можливі зсуви того числа.

n          (3 << n & 0xC)
0          0
1          4
2          12
3          8
Подякували: leofun011

20

Re: Порівняння матриць

koala написав:

Це ж O(n)

Так, дякую, дійсно, я допустив помилку в оцінку.
Моя память трохи потекла і я думав що erase займе O(log(n)), але для unordered_set це не так.

lucas-kane написав:

Та коментарі були б незайві

Там все досить просто

1. розкриваїмо макроси
    int count = 0;
    std::unordered_set<uint16_t> s;
    for(auto const &a : matrices)
        s.insert(
            a[0] << (3 << 0 & 0xC) |
            a[1] << (3 << 1 & 0xC) |
            a[2] << (3 << 2 & 0xC) |
            a[3] << (3 << 3 & 0xC)
        );
    for(uint16_t v; !s.empty(); ++count) {
        v = *s.cbegin();
        s.erase((v << 16 | v) >> (0 << 2));
        s.erase((v << 16 | v) >> (1 << 2));
        s.erase((v << 16 | v) >> (2 << 2));
        s.erase((v << 16 | v) >> (3 << 2));
    }
    return count;
2. виносимо `v << 16 | v` і обчислимо все шо можна обчислити
    int count = 0;
    std::unordered_set<uint16_t> s;
    for(auto const &a : matrices)
        s.insert(
            a[0] << 0x0 |
            a[1] << 0x4 |
            a[2] << 0xC |
            a[3] << 0x8
        );
    for(uint16_t v; !s.empty(); ++count) {
        v = *s.cbegin();
        uint32_t w = v << 16 | v;
        s.erase(w >> 0x0);
        s.erase(w >> 0x4);
        s.erase(w >> 0x8);
        s.erase(w >> 0xC);
        // кожний erase приймає uint16_t
        // старші біти uint32_t w іґноровані
    }
    return count;
3. перший цикл впорядкуїмо за зміщеням
    for(auto const &a : matrices)
        s.insert(
            a[0] << 0x0 |
            a[1] << 0x4 |
            a[3] << 0x8 |
            a[2] << 0xC
        );

Так ми отримали цикл [0,1,3,2]. Загалом є 8 подібних перестановок, і ця виглядає найпростішою за кількістю операторів в коді.

koala написав:

формує число з матриці (виходячи з того, що елементи матриці не більші за 9), пхає те число до unordered_set, а потім видаляє з тієї множини по 4 можливі зсуви того числа.

Так, дійсно, це важлива деталь.