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.