2014-08-25 3 views
0

Я пытаюсь объединить ключи карты, основанные на сходстве значения ключа, чтобы создать новую карту с ключевыми значениями, подобными объединенным в один. Ниже мой код, чтобы проиллюстрировать свои идеи:Clojure: Идиоматический способ слияния клавиш карты на основе сходства клавиш?

Учитывая набор данных:

(def engineer-visits (incanter.core/dataset ["Engineer" "Credit" "Comments"] 
           [ 
           ["Jonah" 1 "OK"] 
           ["Jonah" 2 "Very good"] 
           ["Joneh" 0 "Not very good"] 
           ["Joneh" 3 "Excellent"] 
           ["Esther" 2 "Missing comment"] 
           ["Esther" 4 "Extraordinary"] 

           ] 
          )) 

со значением:

| Engineer | Credit |  Comments | 
|----------+--------+-----------------| 
| Jonah |  1 |    OK | 
| Jonah |  2 |  Very good | 
| Joneh |  0 | Not very good | 
| Joneh |  3 |  Excellent | 
| Esther |  2 | Missing comment | 
| Esther |  4 | Extraordinary | 

Следующая производит карту от инженера до его/ее записи:

(def by-engineers (incanter.core/$group-by "Engineer" engineer-visits)) 

со значением:

{{"Engineer" "Jonah"} 
| Engineer | Credit | Comments | 
|----------+--------+-----------| 
| Jonah |  1 |  OK | 
| Jonah |  2 | Very good | 
, {"Engineer" "Joneh"} 
| Engineer | Credit |  Comments | 
|----------+--------+---------------| 
| Joneh |  0 | Not very good | 
| Joneh |  3 |  Excellent | 
, {"Engineer" "Esther"} 
| Engineer | Credit |  Comments | 
|----------+--------+-----------------| 
| Esther |  2 | Missing comment | 
| Esther |  4 | Extraordinary | 
} 

С помощью следующей функции, я хотел бы получить:

(map-merged-by-key-value-similarity by-engineers 0.8) 

{{"Engineer" "Jonah"} 
| Engineer | Credit |  Comments | 
|----------+--------+---------------| 
| Jonah |  1 |   OK | 
| Jonah |  2 |  Very good | 
| Joneh |  0 | Not very good | 
| Joneh |  3 |  Excellent | 
, {"Engineer" "Esther"} 
| Engineer | Credit |  Comments | 
|----------+--------+-----------------| 
| Esther |  2 | Missing comment | 
| Esther |  4 | Extraordinary | 
} 


(defn map-merged-by-key-value-similarity 
     "From a map produced by $gorup-by on a datasest, produce a map of the same structure, with key column values merged by similarity." 
     [a-map threshold] 
     (let [ 
      column-keys (keys a-map) 
      key-column-name (->> column-keys 
           first 
           keys 
           first) 
      ;; Deconstruct the key column values from the key of the map, i.e. the pair of column name and column value: 
      key-column-values (flatten (map vals column-keys)) 
      ;; Compute string clusters for the values: 
      value-simularity-cluster (similarity-cluster key-column-values threshold) 
      ;; Reconstruct the key for the updated map from the clustered column values: 
      reconstructed-column-value-key-cluster-list (map (fn [cluster] 
                   (map (fn [name] 
                     {key-column-name name}) 
                    cluster)) 
                  value-simularity-cluster) 
      representative (fn [cluster] (first cluster)) ; out of a cluster 
      map-from-cluster-combined-fn (fn [cluster] 
              ; the cluster is a list of maps from key-column-mane to string of the column's value 
              (if (< 1 (count cluster)) 
              ;; combine 
              (apply merge-with conj-rows (map (fn [key] 
                        {(representative cluster) (a-map key)}) 
                        cluster)) 
              ;; as is 
              {(first cluster) (a-map (first cluster))} 
              )) 
      ] 
     (apply merge (map map-from-cluster-combined-fn reconstructed-column-value-key-cluster-list)) 
     ) 
    ) 

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

Спасибо за ваш комментарий или помощь!

Примечание: similarity-cluster преобразует список строк в список списка строк, где аналогичные строки помещаются в закрытый список. Это моя реализация. Подробности здесь не имеют отношения к моему вопросу.

+0

Пожалуйста, попробуйте представить более изолированный случай, иллюстрирующий проблему. –

ответ

2

Это немного проще, если вы просто используете таблицу (вектор карт с идентичными ключами) вместо наборов данных Incanter. Тем не менее, есть несколько функций для переключения между ними.

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

Для measuring similarity between strings Я использовал этот Purely Functional Levenshtein Distance как функция levenshtein-distance, и использовали точку среза 3 редактирует:

(def engineer-visits 
    [{:comments "OK", :engineer "Jonah", :credit 1} 
    {:comments "Very good", :engineer "Jonah", :credit 2} 
    {:comments "Not very good", :engineer "Joneh", :credit 0} 
    {:comments "Excellent", :engineer "Joneh", :credit 3} 
    {:comments "Missing comment", :engineer "Esther", :credit 2} 
    {:comments "Extraordinary", :engineer "Esther", :credit 4}]) 

(defn similarity-matrix 
    [coll] 
    (into {} (for [x coll, y coll 
       :when (< (levenshtein-distance x y) 3)] 
      [x y]))) 

(def similarity 
    (similarity-matrix (distinct (map :engineer engineer-visits)))) 
=> {"Jonah" "Joneh", "Joneh" "Joneh", "Esther" "Esther"}  

(group-by #(get similarity (:engineer %)) engineer-visits) 
=> 
{"Joneh" 
[{:comments "OK", :engineer "Jonah", :credit 1} 
    {:comments "Very good", :engineer "Jonah", :credit 2} 
    {:comments "Not very good", :engineer "Joneh", :credit 0} 
    {:comments "Excellent", :engineer "Joneh", :credit 3}], 
"Esther" 
[{:comments "Missing comment", :engineer "Esther", :credit 2} 
    {:comments "Extraordinary", :engineer "Esther", :credit 4}]} 

Следует отметить, что, помещая элементы подобия-матрицы в хэш-карте, Пара ключей-значений ["Jonah","Jonah"] заменяется на следующую пару ["Jonah","Joneh"]. То же самое касается ["Joneh","Jonah"], за которым следует ["Joneh","Joneh"]. Это очень полезно для результата.

+0

Спасибо Niels! Откровение для меня заключается в том, что использование правильной структуры данных решает проблему самостоятельно. Incanter нет, но список карт в этом случае. –

0

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

Учитывая таблицу, и способ группирования колонки „: инженер“ 'значения s, и способ выбора репрезентативного значения из кластера, каково выражение для построения карты из этих представителей в соответствующие строки в таблице?

Вот дистиллированное решение. Опять же, благодаря ответу Нильса.

(def engineer-visits 
    [{:comments "OK", :engineer "Jonah", :credit 1} 
    {:comments "Very good", :engineer "Jonah", :credit 2} 
    {:comments "Not very good", :engineer "Joneh", :credit 0} 
    {:comments "Excellent", :engineer "Joneh", :credit 3} 
    {:comments "Missing comment", :engineer "Esther", :credit 2} 
    {:comments "Extraordinary", :engineer "Esther", :credit 4}]) 

(defn clusters [names] '(("Jonah" "Joneh") ("Esther"))) 
(defn representative [cluster] (first cluster)) 

(def representatives 
    (->> engineer-visits 
     (map :engineer) 
     distinct 
     clusters 
     (map (fn [cluster] (apply merge (map (fn [name] {name (representative cluster)}) cluster)))) 
     (apply merge) 
     )) 

(group-by #(get representatives (:engineer %)) engineer-visits) 

Result =>

{"Jonah" 
[{:comments "OK", :engineer "Jonah", :credit 1} 
    {:comments "Very good", :engineer "Jonah", :credit 2} 
    {:comments "Not very good", :engineer "Joneh", :credit 0} 
    {:comments "Excellent", :engineer "Joneh", :credit 3}], 
"Esther" 
[{:comments "Missing comment", :engineer "Esther", :credit 2} 
    {:comments "Extraordinary", :engineer "Esther", :credit 4}]} 
Смежные вопросы