Простая с виду задачка с подвохом
От: Lazytech Ниоткуда  
Дата: 21.09.20 12:34
Оценка:
Insane Coloured Triangles | Codewars

К сожалению, ни одно из моих решений не укладывается по времени, потому что все они брутфорсные.

  Мои решения на JavaScript
function triangle(row) {
  const pair2one = {
    RR: 'R',
    BB: 'B',
    GG: 'G',
    RB: 'G',
    BR: 'G',
    RG: 'B',
    GR: 'B',
    BG: 'R',
    GB: 'R'
  }

  let rowArr = row.split('');
  let limit;
  let a;
  let ab;  
  
  for (let i = rowArr.length; i > 1; i--) {
    a = rowArr[0];
    
    for (let j = 1; j < i; j++) {
      ab = pair2one[ a + rowArr[j] ];
      a = rowArr[j];
      rowArr[j - 1] = ab;
    }
  }
  
  return rowArr[0];
}


function triangle(row) {
  const pair2one = {
    RR: 'R',
    BB: 'B',
    GG: 'G',
    RB: 'G',
    BR: 'G',
    RG: 'B',
    GR: 'B',
    BG: 'R',
    GB: 'R'
  }

  let a, ab;  
  
  for (let i = row.length; i > 1; i--) {
    a = row[0];
    
    for (let j = 1; j < i; j++) {
      ab = pair2one[ a + row[j] ];
      a = row[j];
      row = row.slice(0, j - 1) + ab + row.slice(j);
    }
  }
  
  return row[0];
}


function triangle(row) {
  const pair2one = {
    RR: 'R',
    BB: 'B',
    GG: 'G',
    RB: 'G',
    BR: 'G',
    RG: 'B',
    GR: 'B',
    BG: 'R',
    GB: 'R'
  }
  
  const repeater = match => match + match;
  const replacer = match => pair2one[match];

  for (let i = row.length; i > 1; i--) {
    row = ( row[0] + row.slice(1, -1).replace( /./g, repeater ) + row[row.length - 1] )
      .replace( /../g, replacer );    
  }

  return row[0];
}

Смутно подозреваю, что вместо работы со всей строкой можно как-то анализировать по несколько символов из начала и конца строки, но вот как...
Re: Простая с виду задачка с подвохом
От: ltc  
Дата: 21.09.20 13:38
Оценка:
Здравствуйте, Lazytech, Вы писали:

L>Insane Coloured Triangles | Codewars


L>К сожалению, ни одно из моих решений не укладывается по времени, потому что все они брутфорсные.


L>Смутно подозреваю, что вместо работы со всей строкой можно как-то анализировать по несколько символов из начала и конца строки, но вот как...


Так а почему у тебя внешний цикл не укорачивается на каждой итерации то?
Re: Простая с виду задачка с подвохом
От: dead0k  
Дата: 21.09.20 13:57
Оценка: 9 (1)
Здравствуйте, Lazytech, Вы писали:

L>К сожалению, ни одно из моих решений не укладывается по времени, потому что все они брутфорсные.


R = 0, G = 1, B = 2.
Несложно заметить, что функция определения элемента следующего ряда f(Ci, Ci+1 ) = (0 — Ci — Ci+1) mod 3.
Тогда можно переработать алгоритм на целочисленную математику и получить ускорение на порядок-другой.
Ну или заметить, что финальное число будет чем-то в духе = (-1)n-1 * (C0*1 + C1*(1)*(n-1) + C2*(2)*(n-1) + C3 * (3) * (n-1) + ...+ Cn-1*(1)*(n-1) + Cn-1*1) mod 3
С коэффициентами и знаком мог и наврать
Re: Простая с виду задачка с подвохом
От: watchmaker  
Дата: 21.09.20 14:02
Оценка: 23 (3)
Здравствуйте, Lazytech, Вы писали:

L>Insane Coloured Triangles | Codewars

L>К сожалению, ни одно из моих решений не укладывается по времени, потому что все они брутфорсные.
L>Смутно подозреваю, что вместо работы со всей строкой можно как-то анализировать по несколько символов из начала и конца строки, но вот как...

Исходная пара цветов преобразуется правилом f(u, v) = -u-v (mod 3)
То есть для получения ответа исходный вектор цветов достаточно умножить скалярно на соответствующую строчку треугольника Паскаля и на (-1)n. Всё по модулю 3.
Задачка на аккуратное вычисление биномиальных коэффициентов по малому модулю.
Re[2]: Простая с виду задачка с подвохом
От: Lazytech Ниоткуда  
Дата: 21.09.20 14:17
Оценка: +1
Здравствуйте, ltc, Вы писали:

ltc>Так а почему у тебя внешний цикл не укорачивается на каждой итерации то?


У меня там 3 решения, о каком из них идет речь?

P.S. Кажется, понял, о чем речь. У меня же там, в зависимости от решения, на каждой итерации внешнего (или единственного) цикла либо строка, либо дистанция от начала массива до условно конечного элемента укорачивается на единицу. Все решения рабочие, просто крайне неэффективные.
Отредактировано 21.09.2020 14:36 Lazytech . Предыдущая версия .
Re[2]: Простая с виду задачка с подвохом
От: Lazytech Ниоткуда  
Дата: 21.09.20 14:21
Оценка:
Здравствуйте, dead0k, Вы писали:

D>Несложно заметить, что функция определения элемента следующего ряда f(Ci, Ci+1 ) = (0 — Ci


К сожалению, я даже близко не заметил. Ускорения ради сдуру пробовал брать первый и последний символ строки, на некоторых тестах прокатило.
Re[2]: Простая с виду задачка с подвохом
От: Lazytech Ниоткуда  
Дата: 21.09.20 14:22
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Задачка на аккуратное вычисление биномиальных коэффициентов по малому модулю.


Можно было сказать это на немецком, я бы понял с таким же успехом. Про треугольник Паскаля знаю, про модуль — тоже, а про биномиальные коэффициенты, если когда-то и знал, то давно забыл...
Отредактировано 21.09.2020 14:39 Lazytech . Предыдущая версия .
Re[2]: Простая с виду задачка с подвохом
От: Lazytech Ниоткуда  
Дата: 21.09.20 15:56
Оценка:
Здравствуйте, dead0k, Вы писали:

D>R = 0, G = 1, B = 2.

D>Несложно заметить, что функция определения элемента следующего ряда f(Ci, Ci+1 ) = (0 — Ci — Ci+1) mod 3.
D>Тогда можно переработать алгоритм на целочисленную математику и получить ускорение на порядок-другой.

Переход на целочисленную математику действительно значительно ускорил код! По моим грубым прикидкам, на пару-тройку порядков (самое быстрое из старых решений в лучшем случае проходило один тест со строкой средней длины, а новое решение прошло все сто таких тестов, плюс пару тестов со строками большой длины). По времени всё равно не уложился, но уже что-то.
  Еще одно решение
function triangle(row) {
  const colors = 'RGB';  
  let a, b, ab;  
  let rowArr = row.split('').map( char => colors.indexOf(char) );
  
  for (let i = rowArr.length; i > 1; i--) {
    a = rowArr[0];
    
    for (let j = 1; j < i; j++) {
      b = rowArr[j];
      ab = ( 6 - a - b ) % 3;
      a = b;
      rowArr[j - 1] = ab;
    }
  }

  return colors[ rowArr[0] ];
}

Спасибо за подсказку.
Отредактировано 21.09.2020 16:07 Lazytech . Предыдущая версия .
Re: Простая с виду задачка с подвохом
От: kov_serg Россия  
Дата: 21.09.20 18:15
Оценка: 9 (1)
Здравствуйте, Lazytech, Вы писали:

L>Insane Coloured Triangles | Codewars


L>Смутно подозреваю, что вместо работы со всей строкой можно как-то анализировать по несколько символов из начала и конца строки, но вот как...

#!/usr/bin/lua

function fn(x)
    local s,c,r,c1,r1,c2,r2,r3
    function r3(t,r)
        r=0 while t%3==0 do t=t//3 r=r+1 end
        return t%3,r
    end
    if #x<2 then return x[1] end
    s=x[1]+x[#x] c=1 r=0
    for i=2,#x-1 do
        c1,r1=r3(#x-i+1)
        c2,r2=r3(i-1)
        c=(c*c1*c2)%3
        r=r+r1-r2
        if r==0 then s=s+x[i]*c end
    end
    if #x%2==0 then s=2*s end
    return s%3    
end

function F(s)
    local x,t,RGB,r={},{R=0,G=1,B=2},"RGB"
    s:gsub("[RGB]",function(c) table.insert(x,t[c]) end)
    r=fn(x)+1 return RGB:sub(r,r)
end

print(F"RRGBRGBB")

https://rextester.com/HTKP68415
G
Отредактировано 22.09.2020 13:01 kov_serg . Предыдущая версия .
Re[2]: Простая с виду задачка с подвохом
От: watchmaker  
Дата: 21.09.20 19:35
Оценка: 3 (1)
Здравствуйте, kov_serg, Вы писали:

_>function fn(x,n,s)
_>    n=#x%2+1 s=2*(x[1]+x[#x])
_>    for i=2,#x-1 do s=s+x[i] end
_>    return (n*s)%3    
_>end


Не пройдёт тест
F"G" == "G"
или
F"RGGR" == "R"


Задача хоть и решается за единственный проход по массиву, но веса, с которыми нужно брать элементы при суммировании, устроены чуть-чуть сложнее
Автор: watchmaker
Дата: 21.09.20
: не для всех внутренних элементов они равны единице. Например, для строк длиной 4 результат определяется суммой граничных символов, а от двух внутренних никак не зависит.
Отредактировано 21.09.2020 19:46 watchmaker . Предыдущая версия . Еще …
Отредактировано 21.09.2020 19:44 watchmaker . Предыдущая версия .
Re[2]: Простая с виду задачка с подвохом
От: Буравчик Россия  
Дата: 21.09.20 20:36
Оценка: 6 (1)
Здравствуйте, watchmaker, Вы писали:

W>Задачка на аккуратное вычисление биномиальных коэффициентов по малому модулю.


Вычисление биномиальных коэффициентов "в лоб" для n=100000 не быстро даже если считать по модулю.

Наверное, для ускорения расчетов надо учитывать повторяющиеся паттерны (фракталы?)
Тогда достаточно рассчитать всего несколько первых рядов треугольника.

http://jwilson.coe.uga.edu/EMAT6680/Parsons/MVP6690/Essay1/mod3.html

  "не знаю как уменьшить картинку"
Best regards, Буравчик
Re[3]: Простая с виду задачка с подвохом
От: watchmaker  
Дата: 21.09.20 21:16
Оценка: 17 (2)
Здравствуйте, Буравчик, Вы писали:

Б>Здравствуйте, watchmaker, Вы писали:


W>>Задачка на аккуратное вычисление биномиальных коэффициентов по малому модулю.


Б>Вычисление биномиальных коэффициентов "в лоб" для n=100000 не быстро даже если считать по модулю.



Теорема Люка говорит нам, что для посчёта биномиального коэффициента достаточно будет сложить log3N чисел.
В задаче ограничение на число элементов L = 100000.
Одиннадцать операций сложения — это не очень долго. Даже наоборот, достаточно быстро.
(в формуле в вики там умножение, но практичнее складывать логарифмы; а впрочем можно и умножать, не сильно медленнее)

const int inf = 65536;

// предпосчитанная таблица C^k_n, n=0..2, k=0..2

// BIN3LOG2[n][k] ≈ log_2(C^k_n mod 3)
const int BIN3LOG2[3][4] = {
    {0, -inf, -inf},
    {0,    0, -inf},
    {0,    1,    0},
};

// экспонента: 2^x mod 3
int pow2mod3(int x) {
    return (x < 0) ? 0 : 1 + (x & 1);
}

// вычисление C^k_n, где n, k записаны в троичной системе счисления
int b(const char nv[], const char kv[], const size_t l) {
    int rl = 0;
    for (size_t i = 0; i < l; ++i) {
        rl += BIN3LOG2[nv[i]][kv[i]];
    }
    return pow2mod3(rl);
}






  остальное
char solve(const string& l) {
    size_t n = l.size();
    const std::vector<char> bc = binomial(n - 1);
    int r = 0;
    for (size_t i = 0; i < n; ++i) {
        r += l[i] * bc[i];
    }
    r <<= ~n & 1;
    return "BRGBRG"[r % 3 + 3];
}


Лень делать совсем уж быстро, поэтому функция подсчёта строки треугольника Паскаля сделана отдельно и с выделением памяти, а не inplace. Зато сразу видно, что в решении нет ничего кроме скалярного произведения и учёта чётности.

  binomial
Тут только одна содержательная строка, остальное — перевод в троичную систему счисления и инкремент в ней же
std::vector<char> binomial(size_t n) {
    std::vector<char> r;
    r.reserve(n + 1);
    std::vector<char> nv;  // n в троичной системе счисления
    for (size_t tn = n; tn; tn /= 3) {
        nv.push_back(tn % 3);
    }
    nv.push_back(0);
    std::vector<char> kv(nv.size(), 0);  // k в троичной системе счисления

    for (size_t k = 0; k <= n; ++k) {
        r.push_back(b(nv.data(), kv.data(), nv.size() - 1));

        for (size_t t = 0; ; t++) {
            if (kv[t] == 2) {
                kv[t] = 0;
            } else {
                kv[t] += 1;
                break;
            }
        }
    }

    return r;
}



Кстати, fun fact: можно исходный вектор символов не приводить к 0,1,2, так как ascii-коды символов RGB уже имеют различные остатки от деления на 3. Сразу можно символы на биномиальные коэффициенты умножать




Б>Наверное, для ускорения расчетов надо учитывать повторяющиеся паттерны

Да, можно.
Например, учитывать, что треугольник симметричный и поэтому можно вычислить лишь половину коэффициентов, и исходную строку можно предварительно сложить со своим реверсом и дальше обрабатывать тоже только первую половину.
Или не вычислять коэффициенты подряд, а поменять циклы местами — тогда можно будет отбрасывать весь интервал, если встретился нулевой множитель, так как финальные коэффициенты на нём также будут нулевые.
Ну или банально векторизовать вычисления — тут всё очень регулярно и с минимум ветвлений.
Впрочем, кажется, эти идеи ускорят вычисление максимум в несколько раз, и не окупятся тут по соотношению ускорение/трудозатраты
Отредактировано 21.09.2020 22:41 watchmaker . Предыдущая версия .
Re[3]: Простая с виду задачка с подвохом
От: kov_serg Россия  
Дата: 22.09.20 00:34
Оценка: 22 (1)
Здравствуйте, watchmaker, Вы писали:

W>Задача хоть и решается за единственный проход по массиву, но веса, с которыми нужно брать элементы при суммировании, устроены чуть-чуть сложнее
Автор: watchmaker
Дата: 21.09.20
: не для всех внутренних элементов они равны единице. Например, для строк длиной 4 результат определяется суммой граничных символов, а от двух внутренних никак не зависит.

Да был не прав. Поправил:
#!/usr/bin/lua

function fn(x)
    local s,c,r,c1,r1,c2,r2,r3
    function r3(t,r)
        r=0 while t%3==0 do t=t//3 r=r+1 end
        return t%3,r
    end
    if #x<2 then return x[1] end
    s=x[1]+x[#x] c=1 r=0
    for i=2,#x-1 do
        c1,r1=r3(#x-i+1)
        c2,r2=r3(i-1)
        c=(c*c1*c2)%3
        r=r+r1-r2
        if r==0 then s=s+x[i]*c end
    end
    if #x%2==0 then s=2*s end
    return s%3    
end

function F(s)
    local x,t,RGB,r={},{R=0,G=1,B=2},"RGB"
    s:gsub("[RGB]",function(c) table.insert(x,t[c]) end)
    r=fn(x)+1 return RGB:sub(r,r)
end

print(F"RGGR")
Отредактировано 22.09.2020 0:39 kov_serg . Предыдущая версия .
Re[3]: Простая с виду задачка с подвохом
От: Lazytech Ниоткуда  
Дата: 22.09.20 03:25
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>[skip] Например, для строк длиной 4 результат определяется суммой граничных символов, а от двух внутренних никак не зависит.


Ну, хоть что-то я угадал.
Re[3]: Простая с виду задачка с подвохом
От: Lazytech Ниоткуда  
Дата: 22.09.20 03:27
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Наверное, для ускорения расчетов надо учитывать повторяющиеся паттерны (фракталы?)

Б>Тогда достаточно рассчитать всего несколько первых рядов треугольника.

Небольшое математическое исследование я точно не потяну.
Re[4]: Простая с виду задачка с подвохом
От: Lazytech Ниоткуда  
Дата: 22.09.20 03:30
Оценка:
Здравствуйте, watchmaker, Вы писали:

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

W>Или не вычислять коэффициенты подряд, а поменять циклы местами — тогда можно будет отбрасывать весь интервал, если встретился нулевой множитель, так как финальные коэффициенты на нём также будут нулевые.
W>Ну или банально векторизовать вычисления — тут всё очень регулярно и с минимум ветвлений.
W>Впрочем, кажется, эти идеи ускорят вычисление максимум в несколько раз, и не окупятся тут по соотношению ускорение/трудозатраты

Похоже, я ошибся с названием темы: «Простая с виду задачка с подвохом for senior software engineers only».
Re[4]: Простая с виду задачка с подвохом
От: Буравчик Россия  
Дата: 22.09.20 05:53
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Кстати, fun fact: можно исходный вектор символов не приводить к 0,1,2, так как ascii-коды символов RGB уже имеют различные остатки от деления на 3. Сразу можно символы на биномиальные коэффициенты умножать


Да, совпало

W>Впрочем, кажется, эти идеи ускорят вычисление максимум в несколько раз, и не окупятся тут по соотношению ускорение/трудозатраты


Не, там один паттерн вложен в другой — каждый треугольник состоит из таких же шести треугольников. Общее количество вычислений будет линейным от номера ряда в треугольнике Паскаля.
Best regards, Буравчик
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.