Fork me on GitHub
#clojure-russia
<
2017-05-22
>
dottedmag20:05:14

Господа, а вот расскажите, что мне делать. Есть у меня дерево {:dirs {"a" <..> "b" <..>} :files {"c" <..> "d" <..>}} где внутри :dirs такие же узлы, а внутри :files листья. И есть у меня путь ["a" "b" "c" "file"] внутри такого дерева. Хочу я сделать операцию "добавить путь в дерево" с созданием промежуточных директорий, если их там нет.

dottedmag20:05:43

Руками попытался рекурсивную функцию сделать -- получился какой-то ужас с ручным (conj ..) на каждом шагу того, что поменялось.

dottedmag20:05:51

Почитал specter -- вроде не то.

dottedmag20:05:59

Зиппер, что ли, брать?

artemyarulin20:05:21

ага, как раз для зипперов задача. Правда я редактировал всего один раз дерево, что-то простое совсем делал, почти все время тока на чтение работал

artemyarulin21:05:51

ну и кста счас думаю про ручную функцию, там же сразу можно ее свести заместо >с созданием промежуточных директорий, если их там нет упросить до >создать 1 уровень ниже если не существует что должно быть проще уже

artemyarulin21:05:45

а уж из пути проитерировать и дернуть эту функцию N раз не должно быть сложно через обычный loop/reduce. Или я что-то упускаю?

artemyarulin21:05:25

а там же надо будет находить опять куда мы в прошлый раз добавили ноду и откуда опять начать копать, трекать в каком мы сейчас месте… oh shit… it’s a zipper! 😄

dottedmag21:05:53

Да, вот как-то так 🙂

dottedmag21:05:08

Есть update-in, но там нет возможности вызвать на каждом узле функцию.

dottedmag21:05:35

А зиппер из коробки только на деревьях, у которых дети - seq, а не map.

artemyarulin21:05:03

да там же свой кастомный зиппер то сделать как два пальца как функцию вызвать

dottedmag21:05:47

Ну вот он кривоватый получается.

dottedmag21:05:55

Я случше сделаю свой update-in, с блэкджэком.

artemyarulin21:05:13

а ну окей, свое то милее конечно 🙂

dottedmag21:05:48

У меня несколько операций вида "поколдовать так над узлом в дереве, а потом по-другому над листиком".

artemyarulin21:05:41

прям как скопировал из описания зиппера, но да ладно 🙂 Ты потом чиркани сюда как решил в итоге, любопытно

larhat21:05:06

@dottedmag а specter смотрел? оно продаётся как операции-типа-update-in/get-in только лучше

kishanov21:05:28

@dottedmag https://clojuredocs.org/clojure.walk/postwalk посмотри, там можно написать в update функции создание новых плюшек

mike_ananev21:05:38

@dottedmag есть еще одна функция встроенная обхода nested структур и применения функции на внутренних уровнях https://clojuredocs.org/clojure.walk/postwalk

kishanov21:05:55

хаха, /me GET забрал 🙂

mike_ananev21:05:12

да, /you winner

dottedmag21:05:44

postwalk не хочу, там нет поцыи "не хочу суваться в это поддерево"

dottedmag21:05:18

@larhat Вот я на specter посмотрел и не совсем понял. Как там ходить по фиксированному маршруту - это ясно, а вот по динамическому как-то сложно.

misha21:05:09

я чёт зиперы не затащил harold

dottedmag21:05:17

zipper попробовал, но там есть такая засада: после создания зиппера из такой структуры данных, что у меня есть, теряется "ассоциативность" дерева — дети становятся упорядоченными. Так что стоя на узле, найти ребёнка по имени можно только сходив по всем детям слева направо.

dottedmag21:05:43

Думаю, можно сделать associative zipper без особых проблем.

dottedmag21:05:52

Там будут другие операции навигации.

misha21:05:07

как это нет опции не соваться? тааак падажжи *бана

dottedmag21:05:33

@misha Так просто walker же. Типа map на дерево.

misha21:05:15

я с чем-то могу путать, там есть какая-то фя, которая принимает node? и на основании этого дальше идет

misha21:05:48

но как ноды модифицировать - не знаю, не пригождалось

misha21:05:03

(tree-seq branch? children root)

kishanov21:05:19

tree-seq не может добавлять элементы после прохода вроде

misha21:05:27

ну вот да, все примеры - просто итерация. получается, нужно самому сбоку в процессе итерации заново пере-собирать дерево с модификациями

kishanov21:05:49

тот редкий момент когда надо-таки написать свою функцию

misha21:05:59

наверное можно луп-рекуром по пути забацать в пару строк

kishanov21:05:34

мы очень долго юзали instar (https://github.com/boxed/instar), там можно положить начальное дерево как seed для reduce’а и в reduce функции звать transform на нужных путях

kishanov21:05:50

походу перформанс уровня пузырька

misha21:05:50

(let [path ["a" "b" "c" "file"]
      f (last path)
      addr (reverse (cons :files (-> path count dec (repeat :dirs))))
      path* (interleave addr path)]
  (assoc-in {} path* f))
=> {:dirs {"a" {:dirs {"b" {:dirs {"c" {:files {"file" "file"}}}}}}}}
opieop

misha21:05:35

(defn pathify [path]
  (let [xs (reverse (conj (repeat (dec (count path)) :dirs) :files))]
    (interleave xs path)))

(let [path1 [:a :b :c :d]
      path2 [:a :b :c]
      path3 [:a :b :e :f]]
  (-> {}
    (assoc-in (pathify path1) (last path1))
    (assoc-in (pathify path2) (last path2))
    (assoc-in (pathify path3) (last path3))))
=>
{:dirs {:a {:dirs {:b {:dirs {:c {:files {:d :d}},
                              :e {:files {:f :f}}},
                       :files {:c :c}}}}}}

misha22:05:58

а вообще:

(defn assoc-in
  "Associates a value in a nested associative structure, where ks is a
  sequence of keys and v is the new value and returns a new nested structure.
  If any levels do not exist, hash-maps will be created."
  {:added "1.0"
   :static true}
  [m [k & ks] v]
  (if ks
    (assoc m k (assoc-in (get m k) ks v))
    (assoc m k v)))
вместо (get m k) можно написать (get m k (f)), и передать f, которая будет пустую дефолтную ноду возвращать