2009-09-03 23 views
14

Bu soru, Scala'nın şablonla eşleşmesini ve yinelenmesini listeler ve bunların performansı ile ilgilidir.Scala listesi yineleme performansı

Bir liste üzerinde recurses bir işlevi var ve ben gibi örnek bir şey için, bir eksileri üzerinde eşleşen yaparsam: I kullanıyorum

myFunction [] = [] 
myFunction (x:xs) = «something» : myFunction xs 

: Haskell'de

def myFunction(xs) = xs match { 
    case Nil => Nil 
    case x :: xs => «something» myFunction(xs) 
} 

örneğin, Haskell ile yaptığım gibi aynı semantik. Haskell uygulamasıyla ilgili herhangi bir soru olacağını düşünmüyorum, çünkü bu sadece listelere katılma şeklinizdir. Uzun bir liste için (birkaç bin düğüm içeren bir listede çalışacağım), Haskell göz kırpmıyordu (hayal ettim; hiç denemedim).

Ama Scala ile anladığım, maç deyimi listesine hiçbir şey yapmaz bir işleve örnek genişletmek için, eksileri etrafında listesini bölmek ve uygulamasını kaldırma çıkarıcı yöntemini çağırır dan:

def myFunction(xs) = xs match { 
    case Nil => Nil 
    case x :: xs => x :: myFunction(xs) 
} 
Haskell'de

:

myFunction [] = [] 
myFunction (x:xs) = x : myFunction xs 

o yeniden bir araya eksilerini çıkarıcı yöntemini uygulamak çağırır. Uzun bir liste için bunun çok pahalı olacağını hayal ediyorum.

Özel durumumda, bir karakter listesi üzerinde yinelemek ve çeşitli şeyleri biriktirmek istiyorum; giriş dizesi birkaç on kilobaytlık bir şey olduğunda.

Uzun bir listede tekrarlamak istiyorsam, yinelemenin her adımı için kurucuları ve çıkarıcıları gerçekten arayacak mıyım? Yoksa optimizasyonlar var mı? Ya da daha iyi yollar? Bu durumda ben birkaç akümülatör değişkenleri gerekir ve açıkçası ben sadece hiçbir şey yapmadan bir liste üzerinde recursing olmaz ...

(İki yıldır bir çizgi yazılmamış ettik benim Haskell bağışlayın.)

(Ve evet, kuyruk özümü için gidiyorum.)

cevap

19

İlk olarak, Haskell katı değildir, bu nedenle kuyruktaki bu işlev çağrıları hiç değerlendirilmeyebilir. Scala ise, geri dönmeden önce tüm listeyi hesaplayacaktır. Haskell olanlara daha yakından uygulama bu olacaktır:

sıkı bir List alır, ve non-sıkı bir Stream döndürür
def myFunction[T](l: List[T]): Stream[T] = l match { 
    case Nil => Stream.empty 
    case x :: xs => x #:: myFunction(xs) 
} 

. Şimdi

Eğer desen eşleştirme ve presi kaçınmak istiyorsanız, (hiçbiri bu özel durumda denir gerçi - aşağıya bakınız), sadece bu yapacağım:

def myFunction[T](xs: List[T]): Stream[T] = 
    if (xs.isEmpty) Stream.empty else xs.head #:: myFunction(xs.tail) 

fark ettim gitmek niyetinde kuyruk özyineleme için. Ne yazdınız, özyinelemeli değil, çünkü yinelemenin sonucuna x sonucunu eklediniz.listeleri işlemleri yürütürken, geriye doğru sonuçları hesaplamak eğer kuyruk özyinelemeye almak ve sonra ters edeceğiz:

class ListExample { 
    def test(o: Any): Any = o match { 
    case Nil => Nil 
    case x :: xs => xs 
    case _ => null 
    } 
} 

oluşturur:

def myFunction[T](xs: List[T]): List[T] = { 
    def recursion(input: List[T], output: List[T]): List[T] = input match { 
    case x :: xs => recursion(xs, x :: output) 
    case Nil => output 
    } 
    recursion(xs, Nil).reverse 
} 

Son, nasıl çalıştığı en görmek için bir örnek koda izin

public class ListExample extends java.lang.Object implements scala.ScalaObject{ 
public ListExample(); 
    Code: 
    0: aload_0 
    1: invokespecial #10; //Method java/lang/Object."<init>":()V 
    4: return 

public java.lang.Object test(java.lang.Object); 
    Code: 
    0: aload_1 
    1: astore_2 
    2: getstatic  #18; //Field scala/Nil$.MODULE$:Lscala/Nil$; 
    5: aload_2 
    6: invokestatic #24; //Method scala/runtime/BoxesRunTime.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z 
    9: ifeq 18 
    12: getstatic  #18; //Field scala/Nil$.MODULE$:Lscala/Nil$; 
    15: goto 38 
    18: aload_2 
    19: instanceof  #26; //class scala/$colon$colon 
    22: ifeq 35 
    25: aload_2 
    26: checkcast  #26; //class scala/$colon$colon 
    29: invokevirtual #30; //Method scala/$colon$colon.tl$1:()Lscala/List; 
    32: goto 38 
    35: aconst_null 
    36: pop 
    37: aconst_null 
    38: areturn 

public int $tag() throws java.rmi.RemoteException; 
    Code: 
    0: aload_0 
    1: invokestatic #42; //Method scala/ScalaObject$class.$tag:(Lscala/ScalaObject;)I 
    4: ireturn 

} 

Kod çözme, önce iletilen parametrede equals yöntemini ve Nil nesnesini çağırır. Doğru ise ikincisini döndürür. Aksi takdirde, nesne üzerinde instanceOf[::] çağırır. Doğruysa, nesneyi buna atar ve üzerinde tl yöntemini çağırır. Tüm bunlar başarısız olur, koztantı null yükler ve döndürür.

Yani, görüyorsunuz, x :: xs herhangi bir çıkarıcı çağırmıyor. biriken gelince

, sen düşünebilirsiniz başka desen var:

val list = List.fill(100)(scala.util.Random.nextInt) 
case class Accumulator(negative: Int = 0, zero: Int = 0, positive: Int = 0) 
val accumulator = list.foldLeft(Accumulator())((acc, n) => 
    n match { 
    case neg if neg < 0 => acc.copy(negative = acc.negative + 1) 
    case zero if zero == 0 => acc.copy(zero = acc.zero + 1) 
    case pos if pos > 0 => acc.copy(positive = acc.positive + 1) 
    }) 

varsayılan parametreleri ve yöntemler ı örnek kolaylaştırmak için sadece kullanılan bir Scala 2.8 özelliğidir kopyalar, ancak nokta kullanıyor Bir şeyleri bir liste üzerinde biriktirmek istediğinizde foldLeft yöntemini kullanın.

+0

Teşekkür ederiz! Evet, yazdığım şey kuyruk özyineli olacak, ikinci pasaj kötü bir örnekti! Yapmayı planladığım şey, giriş dizesini bazı önemsiz olmayan işlevlere dayanan çeşitli parçalara bölmektir: sonuç, herhangi bir harita veya katlama stili çıkışı olmayan akümülatörlerdir. Belki de bu özel durumda kullanabileceğim genel bir model aramasında soruyu çok genelleştirmeye çalışıyordum. – Joe

+0

Peki, eklediğim son örneğe bir bakın. –

+0

Mükemmel, her iki yüzeye de cevap veriyor. Çok teşekkür ederim! – Joe