Список Clojure 2d для хэш-карты

У меня есть такой бесконечный список:
((1 1)(3 9)(5 17)...)
Я хотел бы сделать из него хеш-карту:
{:1 1 :3 9 :5 17 ...)

В основном 1-й элемент "внутреннего" списка будет ключевым словом, а второй элемент - значением. Я не уверен, что было бы проще сделать это во время создания, чтобы создать список, который я использую:

(итерация (fn [[a b]] [(вычисление для a) (вычисление для b)]) [1 1])

Вычисление (b) требует (a), поэтому я считаю, что на данный момент (a) не может быть ключевым словом... Весь смысл в том, чтобы можно было легко получить доступ к значению (b), заданному (a).

Благодарим за любую идею...

--EDIT--
Итак, я понял это:

(def my-map (into {} (map #(hash-map (keyword (str (first %))) (first (rest %))) my-list)))

Проблема в том, что он не кажется ленивым... он просто уходит навсегда, хотя я его и не употреблял. Есть ли способ заставить его быть ленивым?


person Daniel Gruszczyk    schedule 09.04.2013    source источник
comment
Какой смысл иметь бесконечную хэш-карту? Вы не можете построить эту структуру во время создания, так как hash-map не поддерживает n-ю операцию.   -  person guilespi    schedule 09.04.2013
comment
Меня никогда не интересовало n-е, но я всегда буду знать ключевое слово, и меня интересует значение, связанное с этим ключевым словом.   -  person Daniel Gruszczyk    schedule 09.04.2013
comment
Я опубликовал способ создания хэш-карты после создания ленивой последовательности, дайте мне знать, если все в порядке.   -  person guilespi    schedule 09.04.2013
comment
Карты Clojure не ленивы, и трудно понять, как они могут быть правильными, если они где.   -  person Arthur Ulfeldt    schedule 09.04.2013
comment
да, теперь это имеет смысл, когда я думаю об этом...   -  person Daniel Gruszczyk    schedule 09.04.2013
comment
Думаю, вам стоит попробовать комбинацию delay и deref (@).   -  person tnoda    schedule 09.04.2013


Ответы (5)


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

(defn get-val [coll k]
  (some (fn [[a b]] (when (= k a) b)) coll))

(get-val '((1 1)(3 9)(5 17))
         3)
;=> 9

Чуть менее наивным решением было бы использовать memoize для кэширования результатов get-val, хотя это все равно будет сканировать входную последовательность больше, чем это необходимо. Более агрессивным решением для кэширования было бы использование атома (как это делает memoize внутри) для кэширования каждой пары по мере ее появления, тем самым потребляя больше входной последовательности только тогда, когда поиск требует чего-то еще не видимого.

Несмотря на это, я бы не рекомендовал оборачивать это в API хэш-карты, так как это подразумевает эффективные неизменяемые «обновления», которые, вероятно, не понадобятся, но которые будет сложно реализовать. Я бы также вообще не рекомендовал использовать ключевые слова для ключей.

person Chouser    schedule 09.04.2013
comment
Спасибо за подробное объяснение. Мне кажется, что это слишком много хлопот, поэтому я останусь со списком. Поскольку я знаю, что все ключи — это нечетные числа, я могу быстро найти ключ X, выполнив (nth mylist (/ (- X 1) 2)) - person Daniel Gruszczyk; 09.04.2013
comment
Достаточно справедливо, но если ввод представляет собой ленивую последовательность, nth все равно будет выполнять линейное сканирование с самого начала для каждого поиска. - person Chouser; 09.04.2013
comment
Я знаю, но это всегда дело, мне придется использовать последовательность до n-го элемента рано или поздно, верно? преобразую ли я его каким-то образом в карту, оберну ли его функцией, чтобы получить n-й элемент, или просто (n-й) его... - person Daniel Gruszczyk; 10.04.2013
comment
При первом вызове функции get-val разницы не будет, но, превратив ленивую последовательность в карту, вы значительно сэкономите на последующих вызовах. - person Leonid Beschastny; 10.04.2013

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

Предположим, у нас есть бесконечный 2d-список, тогда вы можете просто использовать into для создания хэш-карты:

(into {} (vec (map vec my-list)))

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

Собственно, его решение можно немного улучшить:

(def my-map (atom {}))

(def my-seq (atom (partition 2 (range))))

(defn build-map [stop]
  (when-let [[k v] (first @my-seq)]
    (swap! my-seq rest)
    (swap! my-map #(assoc % k v))
    (if (= k stop)
        v
        (recur stop))))

(defn get-val [k]
  (if-let [v (@my-map k)]
    v
    (build-map k)))

my-map в моем примере хранит текущую хеш-карту, а my-seq хранит последовательность еще не обработанных элементов. Функция get-val выполняет ленивый поиск, используя уже обработанные элементы в my-map для повышения производительности:

(get-val 4)
=> 5
@my-map
=> {4 5, 2 3, 0 1}

И ускорение:

(time (get-val 1000))
=> Elapsed time: 7.592444 msecs
(time (get-val 1000))
=> Elapsed time: 0.048192 msecs
person Leonid Beschastny    schedule 09.04.2013

Если вы сгладите его до списка (k v k v k v k v) с помощью flatten, вы можете использовать apply для вызова hash-map с этим списком в качестве аргументов, которые дадут вам список, который вы ищете.

user> (apply hash-map (flatten '((1 1)(3 9)(5 17))))
{1 1, 3 9, 5 17}

хотя он не использует ключевое слово для первого аргумента.

По крайней мере, в clojure последнее значение, связанное с ключом, считается значением для этого ключа. Если это не так, вы не можете создать новую карту с другим значением для ключа, который уже есть в карте, потому что первый (и теперь затененный ключ) будет возвращен функцией поиска. Если функция поиска ищет до конца, то она не ленива. Вы можете решить эту проблему, написав собственную реализацию карты, которая использует списки ассоциаций, хотя ей не хватит гарантий производительности карт Clojure на основе trei, потому что в худшем случае она перейдет к линейному времени.

Я не уверен, что сохранение ленивой входной последовательности даст желаемые результаты.

person Arthur Ulfeldt    schedule 09.04.2013
comment
И тоже не ленивый(def testme(apply hash-map(lazy-seq-here))) душит процессор - person Daniel Gruszczyk; 09.04.2013

Чтобы сделать хэш-карту из вашей последовательности, вы можете попробовать:

(defn to-map [s] (zipmap (map (comp keyword str first) s) (map second s)))

=> (to-map '((1 1)(3 9)(5 17)))
=> {:5 17, :3 9, :1 1}
person Hendekagon    schedule 09.04.2013

Вы можете преобразовать эту структуру в хэш-карту позже таким образом.

(def it #(iterate (fn [[a b]] [(+ a 1) (+ b 1)]) [1 1])) 
(apply hash-map (apply concat (take 3 (it))))
=> {1 1, 2 2, 3 3}
person guilespi    schedule 09.04.2013
comment
это сработает, но мне нужно, чтобы хэш-карта была бесконечной (и ленивой), как и список... - person Daniel Gruszczyk; 09.04.2013
comment
Но если у вас есть последовательность, в которой каждое значение строится из предыдущего, нет возможности получить конкретный ключ, не реализуя все предыдущие. В этом случае просто переопределите функцию get для этого (реализуйте последовательность, пока не будет найден нужный ключ) - person guilespi; 09.04.2013