Domanda Clojure: semi-appiattimento di una sequenza annidata


Ho una lista con elenchi di vettori incorporati, che assomiglia a:

(([1 2]) ([3 4] [5 6]) ([7 8]))

Quello che so non è l'ideale con cui lavorare. Mi piacerebbe appiattire questo ([1 2] [3 4] [5 6] [7 8]).

appiattire non funziona: mi dà (1 2 3 4 5 6 7 8).

Come faccio a fare questo? Immagino di dover creare una nuova lista basata su contenuto di ogni elemento della lista, non degli articoli, ed è questa parte che non riesco a scoprire come fare dai documenti.


44
2018-03-08 12:19


origine


risposte:


Se vuoi solo appiattirlo di un livello puoi usare concat

(apply concat '(([1 2]) ([3 4] [5 6]) ([7 8])))
=> ([1 2] [3 4] [5 6] [7 8])

59
2018-03-08 12:58



Per trasformare un elenco di liste in un unico elenco contenente gli elementi di ogni sotto-elenco, si desidera apply concat come suggerisce Nickik.

Tuttavia, di solito c'è una soluzione migliore: non produrre l'elenco degli elenchi per cominciare! Ad esempio, immaginiamo di avere una funzione chiamata get-names-for che prende un simbolo e restituisce una lista di tutte le cose interessanti che potremmo chiamare quel simbolo:

(get-names-for '+) => (plus add cross junction)

Se vuoi ottenere tutti i nomi per qualche elenco di simboli, potresti provare

(map get-names-for '[+ /]) 
=> ((plus add cross junction) (slash divide stroke))

Ma questo porta al problema che stavi avendo. Potresti incollarli insieme a un apply concat, ma meglio sarebbe da usare mapcat invece di map iniziare con:

(mapcat get-names-for '[+ /]) 
=> (plus add cross junction slash divide stroke)

27
2018-03-08 17:41



Il codice per flatten è piuttosto breve:

(defn flatten
  [x]
  (filter (complement sequential?)
    (rest (tree-seq sequential? seq x))))

Utilizza tree-seq per camminare attraverso la struttura dei dati e restituire una sequenza di atomi. Dal momento che vogliamo tutte le sequenze di livello inferiore, potremmo modificarlo in questo modo:

(defn almost-flatten
  [x]
  (filter #(and (sequential? %) (not-any? sequential? %))
    (rest (tree-seq #(and (sequential? %) (some sequential? %)) seq x))))

quindi restituiamo tutte le sequenze che non contengono sequenze.


8
2018-03-08 18:47



Inoltre, potresti trovare utile questa funzione di livello 1 di appiattimento generale che ho trovato clojuremvc:

(defn flatten-1 
  "Flattens only the first level of a given sequence, e.g. [[1 2][3]] becomes
   [1 2 3], but [[1 [2]] [3]] becomes [1 [2] 3]."
  [seq]
  (if (or (not (seqable? seq)) (nil? seq))
    seq ; if seq is nil or not a sequence, don't do anything
    (loop [acc [] [elt & others] seq]
      (if (nil? elt) acc
        (recur
          (if (seqable? elt)
            (apply conj acc elt) ; if elt is a sequence, add each element of elt
            (conj acc elt))      ; if elt is not a sequence, add elt itself 
       others)))))

Esempio:

(flatten-1 (([1 2]) ([3 4] [5 6]) ([7 8])))
=>[[1 2] [3 4] [5 6] [7 8]]

concat exampe sicuramente fa un lavoro per te, ma questo flatten-1 è anche consentendo elementi non seq all'interno di una collezione:

(flatten-1 '(1 2 ([3 4] [5 6]) ([7 8])))
=>[1 2 [3 4] [5 6] [7 8]]
;whereas 
(apply concat '(1 2 ([3 4] [5 6]) ([7 8])))
=> java.lang.IllegalArgumentException: 
   Don't know how to create ISeq from: java.lang.Integer

4
2018-03-08 21:42



Ecco una funzione che si appiattirà al livello di sequenza, indipendentemente dalla nidificazione disomogenea:

(fn flt [s] (mapcat #(if (every? coll? %) (flt %) (list %)) s))

Quindi se la tua sequenza originale era:

'(([1 2]) (([3 4]) ((([5 6])))) ([7 8]))

Avresti comunque lo stesso risultato:

([1 2] [3 4] [5 6] [7 8])

2
2018-02-09 19:31