2017-03-10 1 views
1

J'essaie de parcourir deux tableaux de tailles potentiellement différentes et de composer un nouveau tableau d'éléments sélectionnés au hasard (pour le croisement dans un algorithme génétique) (childGeneCount est juste le longueur du tableau le plus long).Scala pour/yield s'exécute mais ne se termine pas

Dans l'extrait de code suivant, chaque gène.toString se connecte, mais mon code ne semble pas exécuter le dernier journal. Quelle bêtise je fais?

val genes = for (i <- 0 to childGeneCount) yield { 
    val gene = if (Random.nextBoolean()) { 
    if (i < p1genes.length) { 
     p1genes(i) 
    } else { 
     p2genes(i) 
    } 
    } else { 
    if (i < p2genes.length) { 
     p2genes(i) 
    } else { 
     p1genes(i) 
    } 
    } 
    Logger.debug(gene.toString) 
    gene 
} 
Logger.debug("crossover finishing - never gets here??") 

Nouveau à scala, et serait heureux pour une claque sur le poignet accompagné d'un « faire de cette façon tout à fait différente au lieu » le cas échéant.

+0

si' childGeneCount' est longueur du tableau plus, vous devez utiliser '0 jusqu'à la place childGeneCount'' 0 à childGeneCount'. 'array (length)' soulève une exception IndexOutOfBoundsException. – harry0000

Répondre

1

Bien sûr, la réponse de harry0000 est correcte: j'utilisais "to" comme "until", et je suis tellement habitué à des exceptions lancées que je ne pensais pas y regarder! J'ai fini par passer de for/yield à List.tabulate(childGeneCount){ i => {, ce qui a probablement corrigé l'erreur pour la même raison.

1

Puisque vous avez demandé des améliorations de style possibles, voici deux implémentations suggérées. Le premier est moins idiomatique, mais plus performant. Le second est plus joli mais fait plus de travail.

def crossover[E : ClassTag](a: Array[E], b: Array[E]): Array[E] = { 
    val (larger, smaller) = if(a.length > b.length) (a, b) else (b, a) 
    val result = Array.ofDim[E](larger.length) 
    for(i <- smaller.indices) 
     result(i) = if(Random.nextBoolean()) larger(i) else smaller(i) 
    for(i <- smaller.length until larger.length) 
     result(i) = larger(i) 
    result 
    } 


    def crossoverIdiomatic[E : ClassTag](a: Array[E], b: Array[E]): Array[E] = { 
    val randomPart = (a zip b).map { case (x,y) => if(Random.nextBoolean()) x else y } 
    val (larger, smaller) = if(a.length > b.length) (a, b) else (b, a) 
    randomPart ++ larger.drop(smaller.length) 
    } 

    val a = Array("1", "2", "3", "4", "5", "6") 
    val b = Array("one", "two", "three", "four") 

    // e.g. output: [one,2,three,4,5,6] 
    println(crossover(a, b).mkString("[", ",", "]")) 
    println(crossoverIdiomatic(a, b).mkString("[", ",", "]")) 

Notez que le E : ClassTag ne sont là que pour rendre le compilateur heureux sur l'utilisation Array[E], si vous avez besoin Int pour votre travail, vous pouvez déposer tous les génériques de fantaisie.

2

Vous avez raison, le problème était avec "à" aurait dû être "jusqu'à". J'ai un peu changé votre code pour le rendre plus scala.

val p1genes = "AGTCTC" 
    val p2genes = "ATG" 

    val genePair = p1genes.zipAll(p2genes, None, None) 
    val matchedGene = for (pair <- genePair) yield { 
    pair match { 
     case (p1Gene, None) => p1Gene 
     case (None, p2Gene) => p2Gene 
     case (p1Gene, p2Gene) => if (Random.nextBoolean()) p1Gene else p2Gene 
    } 
    } 
    println(matchedGene) 

Le processus est le suivant:

  1. Première zip deux séquences d'ADN dans une.
  2. Remplissez la séquence la plus courte avec None.
  3. Maintenant boucler sur les séquences zippées et peupler la nouvelle séquence.
+0

En combinant des lettres avec 'None's dans une séquence, le résultat est de type' IndexedSeq [Any] '. – Suma

+0

C'est gentil, je ne connaissais pas 'zipAll'! Une chose dont il ne tient pas compte est que vous pouvez savoir exactement quand 'None' apparaîtra (toute la fin de la séquence initialement plus petite). Un autre problème est que vous mélangez 'Char's avec' Option', donc vous devez utiliser 'genes.map (Some.apply)' sur 'p1genes' et' p2genes' pour préserver vos types. – francoisr

+0

Une fois pourrait utiliser un caractère spécifique (comme '-') connu pour ne pas apparaître dans l'ADN au lieu du marqueur 'None', ou transformer toute la séquence en un premier (en entier dans ma réponse). – Suma

2

réponse de Reworked Tawkir, avec un nettoyant manutention None:

val p1genes = "AGTCTC" 
val p2genes = "ATG" 

val genePair = p1genes.map(Some.apply).zipAll(p2genes.map(Some.apply), None, None) 
val matchedGene = genePair.map { 
    case (Some(p1Gene), None) => p1Gene 
    case (None, Some(p2Gene)) => p2Gene 
    case (Some(p1Gene), Some(p2Gene)) => if (Random.nextBoolean()) p1Gene else p2Gene 
} 
println(matchedGene) 

Si vous voulez éviter d'envelopper la séquence avec Some, une autre solution est d'utiliser un personnage connu ne pas apparaître dans la séquence en tant que " none » marqueur:

val p1genes = "AGTCTC" 
val p2genes = "ATG" 

val none = '-' 
val genePair = p1genes.zipAll(p2genes, none, none) 
val matchedGene = genePair.map { 
    case (p1Gene, `none`) => p1Gene 
    case (`none`, p2Gene) => p2Gene 
    case (p1Gene, p2Gene) => if (Random.nextBoolean()) p1Gene else p2Gene 
} 
println(matchedGene) 
+0

super, merci pour le retravailler. Je suppose que la solution de marqueur semble mieux. Le Some.apply se sent à beaucoup d'ingénierie. – Tawkir