Kotlin'in Yinelenebilir ve Sırası tamamen aynı görünüyor. Neden iki tür gereklidir?


88

Bu arayüzlerin her ikisi de yalnızca bir yöntemi tanımlar

public operator fun iterator(): Iterator<T>

Belgelerde Sequencetembel olması gerektiği yazıyor . Ama Iterabletembel de değil Collectionmi (a tarafından desteklenmedikçe )?

Yanıtlar:


138

Temel fark, anlambilimde ve stdlib uzantı işlevlerinin Iterable<T>ve Sequence<T>.

  • Çünkü Sequence<T>uzantı işlevleri, Java Streams ara işlemlerine benzer şekilde mümkün olduğunda tembel olarak çalışır. Örneğin, Sequence<T>.map { ... }başka bir tane döndürür Sequence<R>ve veya benzeri bir uçbirim işlemi çağrılıncaya kadar öğeleri işlemez .toListfold

    Bu kodu düşünün:

    val seq = sequenceOf(1, 2)
    val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
    print("before sum ")
    val sum = seqMapped.sum() // terminal
    

    Aşağıdakileri yazdırır:

    before sum 1 2
    

    Sequence<T>terminalde yapılan işi azaltmak istediğinizde tembel kullanım ve verimli ardışık düzen için tasarlanmıştırJava Streams'de olduğu gibi işlemlerinde mümkün olduğunca . Bununla birlikte, tembellik, daha küçük koleksiyonların ortak basit dönüşümleri için istenmeyen bir durum olan ve onları daha az performanslı hale getiren bazı ek yükler getirir.

    Genel olarak, ne zaman gerekli olduğunu belirlemenin iyi bir yolu yoktur, bu nedenle Kotlin stdlib'de tembellik açık hale getirilir ve varsayılan Sequence<T>olarak tüm s'lerde kullanmaktan kaçınmak için arayüze çıkarılır Iterable.

  • Bunun Iterable<T>tersine, ara işlem semantiğine sahip uzantı işlevleri hevesle çalışır, öğeleri hemen işler ve bir başkasını döndürür Iterable. Örneğin, içinde eşleme sonuçlarıyla birlikte Iterable<T>.map { ... }bir döndürür List<R>.

    Yinelenebilir için eşdeğer kod:

    val lst = listOf(1, 2)
    val lstMapped: List<Int> = lst.map { print("$it "); it * it }
    print("before sum ")
    val sum = lstMapped.sum()
    

    Bu çıktı:

    1 2 before sum
    

    Yukarıda belirtildiği gibi, Iterable<T>varsayılan olarak tembel değildir ve bu çözüm kendini iyi gösterir: çoğu durumda iyi bir referans konumuna sahiptir, bu nedenle CPU önbelleğinden, tahmininden, önceden getirmeden vb. Yararlanır, böylece bir koleksiyonun birden fazla kopyalanması bile hala iyi çalışır. yeterli ve küçük koleksiyonlarla basit durumlarda daha iyi performans gösterir.

    Değerlendirme işlem hattı üzerinde daha fazla kontrole ihtiyacınız varsa, Iterable<T>.asSequence()işlevli tembel bir diziye açık bir dönüşüm vardır .


3
Muhtemelen Java(çoğunlukla Guava) hayranlar için büyük bir sürpriz
Venkata Raju

@VenkataRaju işlevsel insanlar için varsayılan olarak tembel alternatifine şaşırabilirler.
Jayson Minard

9
Varsayılan olarak tembel, daha küçük ve daha sık kullanılan koleksiyonlar için genellikle daha az performans gösterir. Bir kopya, CPU önbelleğinden vb. Yararlanılıyorsa, tembel bir değerlendirmeden daha hızlı olabilir. Bu yüzden yaygın kullanım durumları için tembel olmamak daha iyidir. Ve maalesef map, filterve diğerleri gibi işlevler için ortak sözleşmeler , kaynak toplama türü dışında karar vermek için yeterli bilgi taşımaz ve çoğu koleksiyon aynı zamanda yinelenebilir olduğundan, bu "tembel olmak" için iyi bir işaret değildir çünkü genellikle HER YERDE. tembellik güvende olmak için açık olmalıdır.
Jayson Minard

1
@naki Yakın tarihli bir Apache Spark duyurusundan bir örnek, açıkça bunun için endişeleniyorlar, databricks.com/blog/2015/04/28/… adresindeki "Önbelleğe Duyarlı Hesaplama" bölümüne bakın ... ancak milyarlarca şeyler yineleniyor, bu yüzden en uç noktaya gitmeleri gerekiyor.
Jayson Minard

3
Ek olarak, tembel değerlendirmeyle ilgili yaygın bir tuzak, bağlamı yakalamak ve sonuçta ortaya çıkan tembel hesaplamayı, yakalanan tüm yerliler ve tuttukları her şeyle birlikte bir alanda depolamaktır. Bu nedenle, bellek sızıntılarında hata ayıklamak zordur.
Ilya Ryzhenkov

50

Kısayol tuşunun cevabı tamamlanıyor:

Öğelerinizde Sıralı ve Yinelemeli yinelemenin nasıl olduğunu fark etmek önemlidir:

Sıra örneği:

list.asSequence().filter { field ->
    Log.d("Filter", "filter")
    field.value > 0
}.map {
    Log.d("Map", "Map")
}.forEach {
    Log.d("Each", "Each")
}

Günlük sonucu:

filtre - Harita - Her biri; filtre - Harita - Her biri

Tekrarlanabilir örnek:

list.filter { field ->
    Log.d("Filter", "filter")
    field.value > 0
}.map {
    Log.d("Map", "Map")
}.forEach {
    Log.d("Each", "Each")
}

filtre - filtre - Harita - Harita - Her biri - Her biri


5
Bu ikisi arasındaki farkın mükemmel bir örneğidir.
Alexey Soshin

Bu harika bir örnek.
frye3k

2

Iterablejava.lang.Iterableüzerindeki arayüze eşlenir JVMve List veya Set gibi yaygın olarak kullanılan koleksiyonlar tarafından uygulanır. Bunlar üzerindeki koleksiyon genişletme işlevleri hevesle değerlendirilir, yani hepsi girdilerindeki tüm öğeleri hemen işler ve sonucu içeren yeni bir koleksiyon döndürür.

Aşağıda, yaşı en az 21 olan bir listedeki ilk beş kişinin adlarını almak için koleksiyon işlevlerini kullanmanın basit bir örneği verilmiştir:

val people: List<Person> = getPeople()
val allowedEntrance = people
    .filter { it.age >= 21 }
    .map { it.name }
    .take(5)

Hedef platform: JVMRunning on kotlin v. 1.3.61 İlk olarak, listedeki her bir Kişi için yaş kontrolü yapılır ve sonuç yepyeni bir listeye eklenir. Ardından, filtre operatöründen sonra kalan her Kişi için adlarının eşleştirilmesi yapılır ve yeni bir liste daha elde edilir (bu şimdi a'dır List<String>). Son olarak, önceki listenin ilk beş öğesini içerecek şekilde oluşturulan son bir yeni liste var.

Aksine, Sıra, Kotlin'de tembel olarak değerlendirilen bir değerler koleksiyonunu temsil eden yeni bir kavramdır. SequenceArayüz için aynı koleksiyon uzantıları mevcuttur , ancak bunlar hemen tarihin işlenmiş durumunu temsil eden, ancak gerçekte herhangi bir öğeyi işlemeden Sekans örneklerini döndürür. İşlemeye başlamak için Sequence, bir terminal operatörüyle sonlandırılması gerekir, bunlar temelde, temsil ettiği verileri somut bir biçimde somutlaştırmak için Diziye yapılan bir taleptir. Örnekler arasında toList, toSetve sumbunlardan sadece birkaçı vardır. Bunlar çağrıldığında, istenen sonucu elde etmek için yalnızca gereken minimum sayıda eleman işlenecektir.

Mevcut bir koleksiyonu bir Sıraya dönüştürmek oldukça basittir, sadece asSequenceuzantıyı kullanmanız gerekir . Yukarıda belirtildiği gibi, ayrıca bir terminal operatörü eklemeniz gerekir, aksi takdirde Sıra hiçbir zaman işlem yapmaz (yine tembel!)

val people: List<Person> = getPeople()
val allowedEntrance = people.asSequence()
    .filter { it.age >= 21 }
    .map { it.name }
    .take(5)
    .toList()

Hedef platform: JVMRunning on kotlin v. 1.3.61 Bu durumda, Sıradaki Kişi örneklerinin her biri yaşlarına göre kontrol edilir, geçerlerse adları çıkarılır ve ardından sonuç listesine eklenir. Bu, beş kişi bulunana kadar orijinal listedeki her kişi için tekrarlanır. Bu noktada, toList işlevi bir liste döndürür ve içindeki kişilerin geri kalanı Sequenceişlenmez.

Bir Sıranın yapabileceği fazladan bir şey de vardır: sonsuz sayıda öğe içerebilir. Bu bakış açısıyla, operatörlerin çalıştıkları gibi çalışması mantıklıdır - sonsuz bir dizideki bir operatör, işini hevesle yaparsa asla geri dönemez.

Örnek olarak, terminal operatörünün gerektirdiği kadar 2'nin gücünü üretecek bir dizi (bunun hızlı bir şekilde taşacağı gerçeğini göz ardı ederek):

generateSequence(1) { n -> n * 2 }
    .take(20)
    .forEach(::println)

Daha fazlasını burada bulabilirsiniz .

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.