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

(var DATA
  {:kek {:foo {:type :LOL}
         :bar {:type :KEK}}
   :owo {:pip {:type :AGA}}})

Ожидание:

{:LOL {:foo :kek}
 :KEK {:bar :kek}
 :AGA {:pip :owo}}

Это очень упрощенный пример. Грубо говоря, нужно вывернуть вложенную мапу наизнанку: поместить наверх то, что сейчас внизу.

Большинство кложуристов подходят к этой задаче как есть. Они берут мапу и прогоняют ее через комбо map, mapcat и анонимных функций. Кто-то накручивает reduce внутри reduce. В целом работает, но как это читать и отлаживать — я не знаю.

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

Итак, если посмотреть на исходную мапу, станет ясно, что когда-то она была плоской таблицей:

kek foo LOL
kek bar KEK
owo pip AGA

Из-за того, что в левой части были повторы, кто-то решил избавиться от них группировкой. Но данные никуда не пропали: мы по-прежнему знаем, у кого какой атрибут. Посмотрев на самый вложенный элемент, легко проследить его путь. Например, обнаружить, что AGA начинается с owo.

Так вот, чтобы переколбасить эту мапу во что-то другое, нужно сперва выправить данные — привести их к таблице. Поможет макрос for. Он принимает несколько коллекций и строит декартово произведение их элементов. Примечательно, что каждая следующая коллекция может быть получена из предыдущих элементов. Код ниже строит ленивую таблицу:

(for [[k1 submap1]
      DATA

      [k2 submap2]
      submap1

      [_ item]
      submap2]

  [k1 k2 item])

([:kek :foo :LOL]
 [:kek :bar :KEK]
 [:owo :pip :AGA])

Теперь когда мапа развернута, можно сгруппировать ее по-другому. Как именно — это уже тривиальное дело, потому что, имея плоские данные, это делается на раз-два. Кроме того, нужно спросить себя — действительно ли нужна новая группировка? Может быть, лучше оставить как есть? Возможно, на той стороне тоже хотели бы плоские данные.

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

{:node shit
 :children
 [{:node crap
   :children
   [{:node fuck
     :children
     [...]}]}]}

А на второй стороне другой человек потеет, чтобы обойти ее как список. Оба пишут быдлокод и матерятся, а ради чего — не ясно. Такую структуру даже не каждый кложурист обойдет, потому что не все знают про tree-seq и зипперы.

Возвращаясь к теме переколбаса данных: хорошо бы запомнить аналогию. Плоские данные — это как лист бумаги, из которого можно сложить самолет, кораблик или лягушку-квакушку. Но переход от одной фигуры к другой происходит через развертку — то есть откату к нулевому состоянию. Только потом можно переходить к новому.

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

Видеть простейшую форму и возвращаться к ней, когда что-то идет не так — важный навык.