jalan pintas untuk membuat Peta dari Daftar di groovy?

106

Saya ingin beberapa seno untuk ini:

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

mengingat hal-hal GDK, saya berharap dapat melakukan sesuatu seperti:

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

tapi saya belum melihat apa pun di dokumen ... apakah saya melewatkan sesuatu? atau apakah saya terlalu malas?

danb
sumber
2
Komentar Amir sekarang adalah jawaban yang benar: stackoverflow.com/a/4484958/27561
Robert Fischer

Jawaban:

119

Baru-baru ini saya menemukan kebutuhan untuk melakukan hal itu: mengubah daftar menjadi peta. Pertanyaan ini diposting sebelum Groovy versi 1.7.9 keluar, jadi metodenya collectEntriesbelum ada. Ini bekerja persis seperti collectMapmetode yang diusulkan :

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

Jika karena alasan tertentu Anda terjebak dengan versi Groovy yang lebih lama, injectmetode ini juga dapat digunakan (seperti yang diusulkan di sini ). Ini adalah versi yang sedikit dimodifikasi yang hanya membutuhkan satu ekspresi di dalam closure (hanya untuk menyimpan karakter!):

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

The +Operator juga dapat digunakan sebagai pengganti <<.

epidemi
sumber
28

Lihat "suntik". Pemrograman fungsional nyata menyebutnya "lipat".

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

Dan, saat Anda melakukannya, Anda mungkin ingin mendefinisikan metode sebagai Kategori, bukan langsung di metaClass. Dengan begitu, Anda dapat menentukannya sekali untuk semua Koleksi:

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

Contoh penggunaan:

use(PropertyMapCategory) {
    println columns.mapProperty('name', 'val')
}
Robert Fischer
sumber
Saya kira itu dinamai inject in Groovy karena mungkin terinspirasi oleh inject: into: in Smalltalk: | daftar jumlah | list: = OrderedCollection new add: 1; tambahkan: 2; tambahkan: 3; dirimu sendiri. jumlah: = daftar masukkan: 0 ke: [: a: b | a + b]. Transkrip cr; tampilkan: jumlah. "mencetak 6"
OlliP
13

Apakah metode groupBy tidak tersedia saat pertanyaan ini diajukan?

Amir Raminfar
sumber
Sepertinya tidak - ini sejak 1.8.1, 2011. Pertanyaan itu ditanyakan pada tahun 2008. Tapi bagaimanapun, groupBy sekarang adalah cara yang tepat.
mvmn
Seperti yang Anda lihat di dokumentasi untuk groupBy, pada dasarnya ini mengelompokkan elemen ke dalam grup, di mana setiap grup berisi elemen yang cocok dengan kunci tertentu. Oleh karena itu, tipe kembaliannya adalah Map<K, List<V>>Tampaknya OP sedang mencari metode dengan tipe kembalian Map<K, V>, jadi groupBy tidak berfungsi dalam kasus ini.
Krzysiek Przygudzki
6

Jika yang Anda butuhkan adalah key-value pair sederhana, maka metode ini collectEntriessudah cukup. Sebagai contoh

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

Tetapi jika Anda menginginkan struktur yang mirip dengan Multimap, di mana ada beberapa nilai per kunci, maka Anda ingin menggunakan groupBymetode ini.

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

ok ... Saya telah bermain dengan ini sedikit lagi dan saya pikir ini adalah metode yang cukup keren ...

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

sekarang setiap subkelas Peta atau Koleksi memiliki metode ini ...

di sini saya menggunakannya untuk membalikkan kunci / nilai dalam Peta

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

dan di sini saya menggunakannya untuk membuat peta dari daftar

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

sekarang saya hanya memasukkan ini ke dalam kelas yang dipanggil saat aplikasi saya dimulai dan metode ini tersedia di seluruh kode saya.

EDIT:

untuk menambahkan metode ke semua larik ...

Object[].metaClass.collectMap = collectMap
danb
sumber
1

Saya tidak dapat menemukan apa pun yang ada di dalamnya ... tetapi menggunakan ExpandoMetaClass saya dapat melakukan ini:

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

ini menambahkan metode collectMap ke semua ArrayLists ... Saya tidak yakin mengapa menambahkannya ke Daftar atau Koleksi tidak berhasil .. Saya rasa itu untuk pertanyaan lain ... tapi sekarang saya bisa melakukan ini ...

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

dari Daftar ke Peta yang dihitung dengan satu penutupan ... persis seperti yang saya cari.

Sunting: alasan saya tidak dapat menambahkan metode ke Daftar dan Koleksi antarmuka adalah karena saya tidak melakukan ini:

List.metaClass.enableGlobally()

setelah pemanggilan metode itu, Anda dapat menambahkan metode ke antarmuka .. yang dalam hal ini berarti metode collectMap saya akan bekerja pada rentang seperti ini:

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

yang menghasilkan peta: [0: 0, 1: 2, 2: 4]

danb
sumber
0

Bagaimana dengan sesuatu yang seperti ini?

// 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']
Michael Easter
sumber
Itu pada dasarnya sama seperti dalam pertanyaan saya ... Saya hanya memiliki map [it.k] = it.v alih-alih .putAt Saya mencari satu liner.
danb