在Clojure中更改矩阵中的元素

时间:2018-04-04 03:27:28

标签: for-loop vector clojure

我对Clojure和函数式编程非常陌生,我想知道如何执行以下操作:

我有一个名为s的向量矢量,看起来像这样:

user=> (println s)
[[1 1 1 3] [2 2 2 3] [3 2 1 1] [4 3 3 4]]

我想要做的是'循环'通过s的每个内部向量,并根据要替换的数字将第二个元素更改为不同的字符串。最终结果可能是这样的:

; Hopeful Result:
user=> (println new-s)
[[1 "Joe" 1 3] [2 "Fred" 2 3] [3 "Fred" 1 1] [4 "Martha" 3 4]]

我到目前为止的这个想法是使用for循环遍历每个向量,并从那里将单个向量传递给另一个使用assoc的函数:

(for [line s] (assemble [line]))

(defn assemble [line]
  (let [x (- (read-string (line 1)) 1)])     ; ==> 0 (To match vector of string names)
  (assoc line 1 new-name[x]))

但是我将单个向量传递给我的func assemble时遇到了麻烦。它似乎传递的不是矢量数据,而是传递给它。有没有更好的方法来实现这一目标?

3 个答案:

答案 0 :(得分:2)

像这样的事情可以解决问题:

(def data [[1 1 1 3] [2 2 2 3] [3 2 1 1] [4 3 3 4]])

(def replacements {1 "joe" 2 "fred" 3 "martha"})

user> (mapv (fn [row] (update row 1 replacements)) data)
;;=> [[1 "joe" 1 3] [2 "fred" 2 3] [3 "fred" 1 1] [4 "martha" 3 4]]

在你的情况下,你只是将错误的参数传递给你的assemble函数:你将它包装在一个你不需要的向量中。

user> (def names ["joe" "fred" "martha"])
#'user/names

user> (defn assemble [line]
        (assoc line 1 (names (dec (line 1)))))
#'user/assemble

user> (for [line data] (assemble line))
;;=> ([1 "joe" 1 3] [2 "fred" 2 3] [3 "fred" 1 1] [4 "martha" 3 4])

然后您可以重写assemble以使用update

user> (defn assemble [line]
        (update line 1 (comp names dec)))
#'user/assemble

user> (for [line data] (assemble line))
;;=> ([1 "joe" 1 3] [2 "fred" 2 3] [3 "fred" 1 1] [4 "martha" 3 4])

user> (mapv assemble data)
;;=> [[1 "joe" 1 3] [2 "fred" 2 3] [3 "fred" 1 1] [4 "martha" 3 4]]

答案 1 :(得分:1)

您的问题就在于将子向量传递给assemble函数:

(for [line s] (assemble [line]))

您将子向量line包装在另一个向量中,以便最终传递[[1 2 3]]而不是[1 2 3]

只需写下

(for [line s] (assemble line))

一切都应该没问题。

编辑:

您应该阅读有关Clojure和Lisps语法的更多信息。您的代码不会在很多方面起作用 例如:

  • let创建一个本地绑定。您在其向量中声明的“变量”仅在let语句中可用。因此,访问x语句中的assoc将无效。您要做的是将assoc来电直接放在let的结束括号前面,如:(let [x 1] (assoc line 1 x))
  • 我不知道你想要通过编写new-name[x]来实现什么,也许可以访问字符串中的字符?这不行。 Lisps中不存在这样的句法诡计。你会使用一个函数。如果我猜对了,请查看clojure.string命名空间。
  • (line 1)会给你一个很好的例外,就像“不知道如何使用字符串作为函数”。直接在左括号后面的标识符只能是函数,宏或符号的名称。即使在Clojure中,字符串也是你无法调用的东西。

我强烈建议您阅读http://www.braveclojure.com/之类的内容,尤其是前几章。

答案 2 :(得分:-2)

以下是4种方法。方法1& 2使用tupelo.array函数。

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)
  (:require [tupelo.array :as tar] ))

(def data
  [[1 1 1 3]
   [2 2 2 3]
   [3 2 1 1]
   [4 3 3 4]])

(dotest
  (let [arr          (tar/rows->array data)
        result-1     (-> arr
                       (tar/elem-set 0 1 "Joe") ; can be in any order here
                       (tar/elem-set 3 1 "Martha")
                       (tar/elem-set 2 1 "Fred")
                       (tar/elem-set 1 1 "Fred"))

        row-name-map {0 "Joe"
                      2 "Fred"
                      3 "Martha"
                      1 "Fred"}
        result-2     (reduce
                       (fn [cum-arr [row-idx name]]
                         (tar/elem-set cum-arr row-idx 1 name))
                       arr
                       row-name-map)

方法2b使用原生clojure assoc-in,将嵌套向量视为关联数据结构(如地图)。

        result-2b    (reduce
                       (fn [cum-arr [row-idx name]]
                         (assoc-in cum-arr [row-idx 1] name))
                       arr
                       row-name-map)

方法3使用可变的Java原生数组。

        jarr-3       (to-array-2d arr) ; native java, mutable array
        >>           (doseq [[row-idx name] row-name-map] ; imperative code
                       (aset jarr-3 row-idx 1 name))
        result-3     (mapv vec jarr-3) ] ; convert back to nested clojure vecs

所有4种技术产生相同的结果:

    (is= result-1 result-2 result-2b result-3
      [[1 "Joe" 1 3]
       [2 "Fred" 2 3]
       [3 "Fred" 1 1]
       [4 "Martha" 3 4]])))

更多documentation can be found here