Clojure: как вы применяете функцию к значениям на определенном уровне вложенности?

#clojure

#clojure

Вопрос:

Я новичок в Clojure, поэтому я сделаю все возможное, чтобы сформулировать это как можно лучше,

У меня есть функция, которая возвращает список вложенных списков после анализа набора данных о ежедневных температурах, каждый вложенный список соответствует ежедневным температурам определенного месяца, например, февраль 2014, февраль 2015 и т. Д., И дополняется до 31 элемента, используя «-999» в качестве заполнителя, чтобы сохранить структуру набора данных.

необработанный набор данных: https://www.metoffice.gov.uk/hadobs/hadcet/cetdl1772on.dat

 (partition 31 (monthly-helper 2 (parse-into-list "CETdataDailyLong")))


=>((-15 7 15 -25 -5 -45 12 47 56 28 20 40 57 38 2 5 25 -3 0 7 7 -3 -10 -10 30 85 46 77 56 -999 -999)
 (0 17 -28 -23 -30 5 -18 -3 -33 -23 -18 -3 -10 50 82 72 62 42 15 57 75 40 92 52 42 62 72 70 -999 -999 -999)
 (-2 -12 4 28 12 0 44 27 -12 16 74 61 76 87 77 78 51 51 59 56 64 52 78 63 39 28 33 81 -999 -999 -999)
 (97 58 75 103 33 46 88 101 56 47 66 36 52 47 58 42 42 37 63 77 76 43 55 85 58 57 55 66 -999 -999 -999)
 (-59 19 28 55 47 30 52 49 42 50 45 25 34 70 40 54 24 13 25 54 85 29 27 38 25 73 44 50 40 -999 -999))
 

Я пытаюсь удалить значения -999 из всех вложенных списков в списке, мне нужно сделать это после разделения данных, чтобы избежать необходимости произвольного разделения данных на количество дней в каждом месяце.
Ближайший, который у меня есть, приведен ниже, но он не имеет никакого эффекта, поскольку применяется только к списку верхнего уровня вместо значений в каждом вложенном списке, как мне нужно изменить это, чтобы получить результат, который я ищу, или задать свой первоначальный вопрос;
Как вы применяете функцию к значениям на определенном уровне вложенности?

 (remove #(= -999 %)(partition 31 (monthly-helper 2 (parse-into-list "CETdataDailyLong"))))
 

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

 (remove #(= -999 %)'(((-15 7 15 -25 -5 -45 12 47 56 28 20 40 57 38 2 5 25 -3 0 7 7 -3 -10 -10 30 85 46 77 56 -999 -999)
                     (0 17 -28 -23 -30 5 -18 -3 -33 -23 -18 -3 -10 50 82 72 62 42 15 57 75 40 92 52 42 62 72 70 -999 -999 -999)
                     (-2 -12 4 28 12 0 44 27 -12 16 74 61 76 87 77 78 51 51 59 56 64 52 78 63 39 28 33 81 -999 -999 -999)
                     (97 58 75 103 33 46 88 101 56 47 66 36 52 47 58 42 42 37 63 77 76 43 55 85 58 57 55 66 -999 -999 -999)
                     (-59 19 28 55 47 30 52 49 42 50 45 25 34 70 40 54 24 13 25 54 85 29 27 38 25 73 44 50 40 -999 -999))))
 

Я пробовал приведенное ниже и множество вариантов с map и т. Д., Но ничего не добился, Просмотр правильного примера действительно помог бы мне понять, где я ошибаюсь.

 (apply #(remove -999 %) (partition 31 (monthly-helper 2 (parse-into-list "CETdataDailyLong"))))
Exception: Wrong number of args (21) passed 
 

Ответ №1:

Итак, iiuc,:

  • Общий список содержит списки лет, а
  • Списки года содержат списки месяцев, а
  • Списки месяцев содержат температуры для дней и
  • Каждый список месяцев дополнен цифрами / -999, чтобы сделать их одинаковыми по размеру: 31 запись в длину

Что я вижу, что вы пробовали:

  • Вы использовали remove функцию с предикатом для удаления, если значение равно -999. Значение в этом случае '((-15 7 15 -25 -5 -45 12 ...)) не равно -999, так что в итоге вы получаете то, с чего начали.
  • apply принимает функцию и одну последовательность аргументов. Вы передали 21 список в apply .

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

 ; Remove -999's, three levels deep, with for.

(defn remove-999s [s-of-s]
   ; All data
   (for [year s-of-s]
      ; For all years   
      (for [month year] 
         ; For all months
         ; (filter #(not (= % -999)) month) would also work
         (remove #(= % -999) month))))

(remove-999s '(((-15 7 15 -25 -5 -45 12 47 56 28 20 40 57 38 2 5 25 -3 0 7 7 -3 -10 -10 30 85 46 77 56 -999 -999) (0 17 -28 -23 -30 5 -18 -3 -33 -23 -18 -3 -10 50 82 72 62 42 15 57 75 40 92 52 42 62 72 70 -999 -999 -999) (-2 -12 4 28 12 0 44 27 -12 16 74 61 76 87 77 78 51 51 59 56 64 52 78 63 39 28 33 81 -999 -999 -999) (97 58 75 103 33 46 88 101 56 47 66 36 52 47 58 42 42 37 63 77 76 43 55 85 58 57 55 66 -999 -999 -999)(-59 19 28 55 47 30 52 49 42 50 45 25 34 70 40 54 24 13 25 54 85 29 27 38 25 73 44 50 40 -999 -999))))
 

Вот результат, без -999.

 ; (((-15 7 15 -25 -5 -45 12 47 56 28 20 40 57 38 2 5 25 -3 0 7 7 -3 -10 -10 30 85 46 77 56) 
; (0 17 -28 -23 -30 5 -18 -3 -33 -23 -18 -3 -10 50 82 72 62 42 15 57 75 40 92 52 42 62 72 70) 
; (-2 -12 4 28 12 0 44 27 -12 16 74 61 76 87 77 78 51 51 59 56 64 52 78 63 39 28 33 81) 
; (97 58 75 103 33 46 88 101 56 47 66 36 52 47 58 42 42 37 63 77 76 43 55 85 58 57 55 66) 
; (-59 19 28 55 47 30 52 49 42 50 45 25 34 70 40 54 24 13 25 54 85 29 27 38 25 73 44 50 40))) [End of data]
 

Поскольку Clojure не разрешает вложенные #, а вложенность fn становится грубой, если вы хотите использовать карты, как предлагает Biped , вы, вероятно, захотите использовать ее с letfn или defn . Вот как я это сделал:

 ; Remove -999's, three levels deep, with maps.

(defn remove-999s [s-of-s]   
   (letfn [(is-999 [v] (= v -999))
           ( map-month [s] (remove is-999 s))
           ( map-year [s] (map map-month s)) ]
     (map map-year s-of-s))) ; Gives the same results.
 

После написания этого я понял, что for это странно map , поэтому можно использовать либо.

Другая альтернатива loop и recur / или классическая рекурсия.

Ответ №2:

я бы начал с служебной функции, обновляющей вложенные последовательности на любом уровне. это может выглядеть так:

 (defn update-nested [level f]
  (cond (neg? level) identity
        (zero? level) f
        :else (partial map (update-nested (dec level) f))))

user> ((update-nested 0 (partial remove #{1})) [1 1 0 1])
;;=> (0)

user> ((update-nested 1 (partial remove #{1})) [[1 1 0 1] [0 0 1 0]])
;;=> ((0) (0 0 0))

user> ((update-nested 2 (partial remove #{1})) [[[1 1] [0 1]] [[0 0] [1 0]]])
;;=> ((() (0)) ((0 0) (0)))

user> ((update-nested 3 (partial remove #{1})) [[[[1 1] [0 1]]] [[[0 0] [1 0]]]])
;;=> (((() (0))) (((0 0) (0))))

user> ((update-nested 3 reverse) [[[[1 1] [0 1]]] [[[0 0] [1 0]]]])
;;=> ((((1 1) (1 0))) (((0 0) (0 1))))
 

Ответ №3:

Ваш первый экспонат — это список списков. И ваш желаемый результат — это тоже список списков, но разных списков. Следовательно, вы хотите map вместо apply .

Комментарии:

1. «Определенный уровень вложенности» — это область более эзотерических решений, зависящих от формы рассматриваемых данных. Вы всегда можете использовать карту, вложенную в карту, вложенную в карту… Или, проверьте assoc-in, update-in и clojure.zip функции в API Clojure по адресу clojure.org . И есть также библиотеки, такие как «меандр».

Ответ №4:

 (require '[com.rpl.specter :as s])

(def data '(your list here))

(s/setval (s/walker #(= % -999)) s/NONE data)
 

Комментарии:

1. Я посмотрел Spectre , и это кажется захватывающим: «Единственными сопоставимыми встроенными операциями Clojure являются get-in and update-in , а эквиваленты Spectre на 30% и 85% быстрее соответственно».

2. Тем не менее, я новичок во внешних библиотеках и вставляю require подсказки macroexpanding clojure.core/ns и No such ns: s ошибки. Я новичок в требовании внешних библиотек, и после просмотра Clojure, org здесь , здесь и здесь , документации Spectre и ответов SO на эту установку lein? Я все еще не уверен, как добавить внешние библиотеки. Можете ли вы указать нам правильное направление?

3. Я добавил зависимость в project.clj , lein installed , поместил require как в код, так и в ns , попробовал без псевдонима, все еще получаю ClassNotFoundException .

4. О, я понял. Я использую VS Code / Calva. Вы должны перезапустить его. Итак, 1) Добавить [com.rpl/specter "1.1.3"] в project.clj, 2) потребовать его, 3) потому что ошибки (? перезагрузка w / C-M-c enter не загрузила его), полностью перезапустите IDE, тогда это сработает.