LINUX.ORG.RU
ФорумTalks

Задача разгрузки коммуникационных каналов, habrahabr и глупость масс

 , , , ,


1

3

Пишу сюда по трем причинам: (i) не могу молчать, (ii) думаю вам должно быть интересно, (iii) это еще один пример зачем нетривиальная математика бывает нужна программисту.

Используя хабр максимум как источник новостей про всякие андройды и прочий мир pop-IT, я всегда поражался несостоятельностью тамошних статей на хоть сколько-то околонаучные темы(взять хотя бы трендовые нейронные сети и «ИИ»). Но вот сейчас наткнулся я на пост «Как я завалил собеседование в Twitter», где рассказана кулстори парня, который не решил задачку «заполнения водой дискретной сутпеньки». Если пройдете по ссылке, то сможете лицезреть десятки постов в стиле «зачем это программисту надо уметь решать», или «что за глупое собеседование, он же в Твиттер интервьюировался, а не в канализацию», ну и прочие народные хохомы, о том что гад интервьюеры такие дураки, задают олимпиадные задачи, которые не позволяют судить о реальном уровне программистов.

Но никто не упомянул, что этот метод на самом деле называется «water-filling», который есть решение задачи оптимальной разгрузки коммуникационных каналов, которая ставится следущим образом

max_x \sum log(a_i + x_i),
s.t.    x_i >= 0,     \sum x_i = 1.

И решение выписывается через ККТ условия.

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



Последнее исправление: ymn (всего исправлений: 1)

Ответ на: комментарий от true_admin

Когда я был студентом, я сдавал право одной очень милой женщине

Милашка очевидно была никакучим педагогом, поэтому все на её лекциях спали и ни во что не ставили. Чтобы оправдать перед самой собой свой педагогический непрофессионализм она и придумала (точнее, переняла у таких же клоунов от педагогики) эту нелепую отмазку.

zolden ★★★★★
()
Ответ на: комментарий от Miguel

Как-то страшновато, не? А мой код можно тупо переложить на хаскель? Или так в итоге и выйдет?

А где лежит тот гист?

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

Гист лежит по ссылке, которая в посте по ссылке.

Что страшновато - это да, но я писал, не особо думая, и добиваясь только O(n) времени без явной рекурсии.

Переложить, наверное, можно. Какой код и какую задачу он решает?

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

Какой код и какую задачу он решает?

Ну, скажем, самую красивую имплементацию этого алгоритма. Или хотя бы эту: что творят девелоперы (комментарий) . Алгоритм простой: каждый небоскрёб может хранить воды min(lmax, rmax) - cur, где lmax и rmax это самые высокие здания справа и слева, а cur это высота текущего небоскрёба. Конечно, отрицательным объём быть не может. Должно же это ложиться нормально на хаскель :). Массив с rmax вычисляется для каждой позиции заранее, а lmax находится «на лету» при проходе массива слева-направо.

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

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

Питона под рукой нет, так что придётся тебе ответить на пару вопросов:

1) Я правильно понимаю, что getrmaxes возвращает массив, где в i-й позиции стоит высота самого высокого уровня правее i-й позиции, включая её саму?

2) Почему мне кажется, что в 0-й позиции этого массива всегда будет 0? Или в этом вашем питоне массивы с 1 нумеруются?

3) Правильно ли я понимаю, что rtbl[-1] означает последний элемент массива?

4) Правильно ли я понимаю, что в данном случае rtbl[-1] будет просто высотой последнего уровня, и что надо бы брать rtbl[0], только мы его не заполняем?

5) Правильно ли я понимаю, что строчка rmax=rtbl[-1] вообще не играет никакой роли, так как в цикле rmax тут же переписывается?

Anyway, вот что получается у меня. Первую функцию можно переписать так:

getRMaxes = scanr1 max
Ну ладно, можно чуток развернуть:
getRMaxes hs = scanr1 max hs
Это одно и то же.

Вторая функция. Здесь нам нужно сделать цикл, перебирая массивы слева направо и передавая далее предыдущие результаты. Для этого предназначена функция foldl. Здесь у нас перебираются аж три массива: hs с нулевого элемента до предпредпоследнего, hs с первого элемента до предпоследнего, и rtbl со второго элемента до последнего. Поэтому мы сведём их воедино с помощью zip3 и сделаем foldl над всеми.

Кроме того, передавать от одной итерации к другой нам нужно две вещи: lmax и volume, причём финальное значение последней будет ответом.

skyScrapers hs =
  let rtbl = getRMaxes hs
      (_, result) = foldl step (0, 0) $ zip3 hs (tail hs) (drop 2 rtbl)
      step (lmax, volume) (hsim1, hsi, rtblip1) =
        let lmax' = max lmax hsim1
            v = min lmax' rtblip1 - hsi
            volume' = if v > 0 then volume + v else volume
        in (lmax', volume')
  in result
Если поинлайнить то, что вызывается только в одном месте, получится так:
skyScrapers hs =
  let (_, result) = foldl step (0,0) $ zip3 hs (tail hs) (drop 2 $ scanr1 max hs)
      step (lmax, volume) (hsim1, hsi, rtblip1) =
        let lmax' = max lmax hsim1
        in (lmax', volume + max 0 (min lmax' rtblip1 - hsi))
  in result
Дальнейшее улучшение можно получить, если выделить lmax-ы в отдельный список. Этот список будет просто
scanl max 0 hs
а его значения с индекса 1 (а только они у нас используются) будут
scanl1 max hs
и его можно использовать так (на этот раз список hs с первого элемента нам не нужен):
skyScrapers hs =
  foldl step 0 $ zip3 (scanl1 max hs) (tail hs) (drop 2 $ scanr1 max hs) where
    step volume (lmax, hsi, rtblip1) = volume + max 0 (min lmax rtblip1 - hsi)
Наконец, можно заметить, что мы тут делаем очень специфический fold - фактически, просто суммируем некие значения. Значит, это преобразуется в сумму:
skyScrapers hs = sum $ map vol $ zip3 (scanl1 max hs) (tail hs) (drop 2 $ scanr1 max hs) where
  vol (lmax, hsi, rmax) = max 0 (min lmax rmax - hsi)
или просто
skyScrapers hs =
  sum $
  map (\(lmax, hsi, rmax) -> max 0 (min lmax rmax - hsi)) $
  zip3 (scanl1 max hs) (tail hs) (drop 2 $ scanr1 max hs)

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

Спасибо за ответ. Я так понял последний сниппет это и есть полное решение? Выглядит очень хорошо.

1) Я правильно понимаю, что getrmaxes возвращает массив, где в i-й позиции стоит высота самого высокого уровня правее i-й позиции, включая её саму?

Похоже на то.

2) Почему мне кажется, что в 0-й позиции этого массива всегда будет 0? Или в этом вашем питоне массивы с 1 нумеруются?

Да. Так и должно быть, у первого столба (=небоскрёба) не может быть своего полезного объёма т.к. слева от него пустота. Честно говоря, и у последнего столба та же фигня должна быть. Щас мозг уже отказывается думать.

3) Правильно ли я понимаю, что rtbl[-1] означает последний элемент массива?

Да.

4) Правильно ли я понимаю, что в данном случае rtbl[-1] будет просто высотой последнего уровня, и что надо бы брать rtbl[0], только мы его не заполняем?

Да. Похоже, мой косяк.

5) Правильно ли я понимаю, что строчка rmax=rtbl[-1] вообще не играет никакой роли, так как в цикле rmax тут же переписывается?

Да, проглядел. Осталось от старых ревизий.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

Я так понял последний сниппет это и есть полное решение?

Угу. Собственно, только в первом варианте skyScrapers ещё требовался getRMaxes, дальше он заинлайнен. Соответственно, все эти варианты — полные решения, если к первому добавить код getRMaxes.

Miguel ★★★★★
()
Ответ на: комментарий от true_admin

О, кстати, забыл. Специально для таких случаев придумана функция zipWith3:

skyScrapers hs = sum $ zipWith3 (\l h r -> max 0 (min l r - h)) (scanl1 max hs) (tail hs) (drop 2 $ scanr1 max hs)
Получился однострочник.

Miguel ★★★★★
()

О, я помню, решил эту задачу на це O(n), правда не понял тогда каким она боком там нужна.

Вот, ага.

PolarFox ★★★★★
()
Последнее исправление: PolarFox (всего исправлений: 2)
Ответ на: комментарий от Miguel

Фантастика, спасибо.

Всё же меня угнетает что даже гуру программирования требуется время чтобы сделать благородное решение. Потому что то что с монадами это, имхо, трындец :)

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

На самом деле, там всё уж очень прямолинейно. Потому и длинно.

Я придумал подход, где сначала находится максимум, а затем мы идём к нему слева и справа. Но списки в хаскеле плохо подходят для того, чтобы идти справа. Значит, нужно либо разделить список и вторую часть развернуть, либо использовать массив с быстрым произвольным доступом. Первое мне показалось некрасивым.

А какой массив с быстрым произвольным доступом можно использовать в чистом коде (или хотя бы в коде, преобразуемом в чистый)? STArray, используемый в монаде ST, которая вызовом runST переделывается в чистое значение. Ну и вот.

Miguel ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.