harika bir Listeden bir Harita oluşturmak için kısayol?


107

Bunun için bir sürü şey istiyorum:

Map rowToMap(row) {
    def rowMap = [:];
    row.columns.each{ rowMap[it.name] = it.val }
    return rowMap;
}

GDK olayları göz önüne alındığında, aşağıdakilere benzer bir şey yapabilmeyi umuyorum:

Map rowToMap(row) {
    row.columns.collectMap{ [it.name,it.val] }
}

ama belgelerde hiçbir şey görmedim ... bir şey mi kaçırıyorum? yoksa ben çok mu tembelim?


2
Amir'in yorumu artık doğru cevap: stackoverflow.com/a/4484958/27561
— Robert Fischer

Yanıtlar:


120

Son zamanlarda tam olarak bunu yapma ihtiyacıyla karşılaştım: bir listeyi haritaya dönüştürmek. Bu soru Groovy 1.7.9 sürümü çıkmadan önce yayınlandı, bu nedenle yöntem collectEntrieshenüz mevcut değildi. Tam olarak önerilencollectMap yöntem gibi çalışır :

Map rowToMap(row) {
    row.columns.collectEntries{[it.name, it.val]}
}

Herhangi bir nedenle eski bir Groovy sürümüne takılırsanız, injectyöntem de kullanılabilir ( burada önerildiği gibi ). Bu, kapanış içinde yalnızca bir ifade alan biraz değiştirilmiş bir versiyondur (sadece karakter tasarrufu için!):

Map rowToMap(row) {
    row.columns.inject([:]) {map, col -> map << [(col.name): col.val]}
}

+Operatör ayrıca yerine kullanılabilir <<.


28

"Enjekte" ye bakın. Gerçek işlevsel programlama, buna "katlama" demez.

columns.inject([:]) { memo, entry ->
    memo[entry.name] = entry.val
    return memo
}

Ve bunu yaparken, muhtemelen yöntemleri doğrudan metaClass üzerinde değil, Kategoriler olarak tanımlamak istersiniz. Bu şekilde, tüm Koleksiyonlar için bir kez tanımlayabilirsiniz:

class PropertyMapCategory {
    static Map mapProperty(Collection c, String keyParam, String valParam) {
        return c.inject([:]) { memo, entry ->
            memo[entry[keyParam]] = entry[valParam]
            return memo
        }
    }
}

Örnek kullanım:

use(PropertyMapCategory) {
    println columns.mapProperty('name', 'val')
}

Inject: into: in Smalltalk: | liste toplamı | list: = OrderedCollection yeni ekle: 1; ekle: 2; ekle: 3; kendin. toplam: = enjekte listesi: 0 içine: [: a: b | a + b]. Transkript cr; göster: toplam. "6 basar"
— OlliP

13

Bu soru sorulduğunda groupBy yöntemi kullanılamıyor muydu ?


Hayır gibi görünüyor - 1.8.1, 2011'den beri. Soru 2008'de sorulmuştu. Ama her durumda, groupBy artık gerçekten de gidilecek yoldur.
— mvmn

GroupBy belgelerinde görebileceğiniz gibi, temelde öğeleri gruplar halinde gruplandırır, burada her grup belirli bir anahtarla eşleşen öğe içerir. Dolayısıyla, dönüş türü şu şekildedir Map<K, List<V>>Görünüşe göre OP dönüş türüne sahip bir yöntem arıyor Map<K, V>, bu nedenle groupBy bu durumda çalışmıyor.
— Krzysiek Przygudzki

6

İhtiyacınız olan şey basit bir anahtar / değer çifti ise, o zaman yöntem collectEntriesyeterli olmalıdır. Örneğin

def names = ['Foo', 'Bar']
def firstAlphabetVsName = names.collectEntries {[it.charAt(0), it]} // [F:Foo, B:Bar]

Ancak, anahtar başına birden çok değer bulunan bir Multimap'e benzer bir yapı istiyorsanız, groupByyöntemi kullanmak isteyebilirsiniz.

def names = ['Foo', 'Bar', 'Fooey']
def firstAlphabetVsNames = names.groupBy { it.charAt(0) } // [F:[Foo, Fooey], B:[Bar]]


5

tamam ... Bununla biraz daha oynadım ve bence bu oldukça havalı bir yöntem ...

def collectMap = {Closure callback->
    def map = [:]
    delegate.each {
        def r = callback.call(it)
        map[r[0]] = r[1]
    }
    return map
}
ExpandoMetaClass.enableGlobally()
Collection.metaClass.collectMap = collectMap
Map.metaClass.collectMap = collectMap

artık herhangi bir Harita veya Koleksiyon alt sınıfı bu yönteme sahip ...

burada bir Harita'daki anahtarı / değeri tersine çevirmek için kullanıyorum

[1:2, 3:4].collectMap{[it.value, it.key]} == [2:1, 4:3]

ve burada bir listeden harita oluşturmak için kullanıyorum

[1,2].collectMap{[it,it]} == [1:1, 2:2]

şimdi bunu uygulamam başlarken çağrılan bir sınıfa yerleştiriyorum ve bu yöntem kodum boyunca kullanılabilir.

DÜZENLE:

yöntemi tüm dizilere eklemek için ...

Object[].metaClass.collectMap = collectMap

1

Yerleşik bir şey bulamıyorum ... ama ExpandoMetaClass kullanarak bunu yapabilirim:

ArrayList.metaClass.collectMap = {Closure callback->
    def map = [:]
    delegate.each {
        def r = callback.call(it)
        map[r[0]] = r[1]
    }
    return map
}

bu, CollectMap yöntemini tüm ArrayListlere ekler ... Bunu Listeye veya Koleksiyona eklemenin neden işe yaramadığından emin değilim .. Sanırım bu başka bir soru için ... ama şimdi bunu yapabilirim ...

assert ["foo":"oof", "42":"24", "bar":"rab"] ==
            ["foo", "42", "bar"].collectMap { return [it, it.reverse()] }

Listeden hesaplanmış haritaya tek bir kapanışla ... tam olarak aradığım şey.

Düzenleme: Yöntemi arayüzler Listesine ve Koleksiyona ekleyemememin nedeni, bunu yapmamış olmamdı:

List.metaClass.enableGlobally()

bu yöntem çağrısından sonra, arabirimlere yöntemler ekleyebilirsiniz .. Bu durumda, CollectMap yöntemimin aşağıdaki gibi aralıklarda çalışacağı anlamına gelir:

(0..2).collectMap{[it, it*2]}

bu haritayı verir: [0: 0, 1: 2, 2: 4]


0

Böyle bir şeye ne dersin?

// setup
class Pair { 
    String k; 
    String v; 
    public Pair(def k, def v) { this.k = k ; this.v = v; }
}
def list = [ new Pair('a', 'b'), new Pair('c', 'd') ]

// the idea
def map = [:]
list.each{ it -> map.putAt(it.k, it.v) }

// verify
println map['c']

Bu temelde sorumdakiyle aynı ... Sadece .putAt yerine map [it.k] = it.v vardı. Tek satırlık bir yer arıyordum.
— danb
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.