2011-11-16 7 views
2

J'essaie d'effectuer un remplacement println qui génère des collections imbriquées dans un format plus lisible. Ceci est mieux illustré par un exemple: Je voudrais List(Set(Vector(1.0,1.1), Vector(0d)), Set(Vector("a", "b", "c"), Vector("x", "y"))) à imprimer commeTableaux dans la méthode d'impression récursive

List 
    Set 
    Vector(1.0, 1.1) 
    Vector(0.0) 
    Set 
    Vector(a, b, c) 
    Vector(x, y) 

Ce serait beaucoup plus facile sans effacement de type, mais je suis venu avec

def rprint(a: Any, indent: Int = 0): Unit = a match { 
    case x: Traversable[_] => 
    if (x.isEmpty) 
     rprint(x.toString, indent) 
    else x.head match { 
     case y: Traversable[_] => { 
     rprint(x.toString.takeWhile(_ != '('), indent) 
     x foreach {i => rprint(i, indent + 2)} 
     } 
     case y => rprint(x.toString, indent) 
    } 
    case x => println(" " * indent + x) 
} 

Je J'ai du mal à faire fonctionner cela avec Arrays, sans duplication de code substantielle. J'aimerais qu'ils travaillent de la même manière que pour les autres collections. Plus précisément:

  • Les tableaux ne sont pas Traversable

  • pourrait convertir des tableaux en utilisant genericArrayOps à ArrayOps qui est TraversableOnce, mais TraversableOnce n'a pas de méthode head, je ne peux pas voir comment obtenir un élément pour vérifier son type

  • toString ne fonctionne pas tout à fait comme les autres collections (utiliser .deep)

Quelle est la meilleure façon d'incorporer des tableaux dans cette méthode, ou existe-t-il une approche différente qui fonctionnerait mieux?

Répondre

1

Pour le rendre plus souple je définir un trait qui comprend des méthodes d'utilité et une méthode abstraite, de sorte qu'il peut être utilisé comme Array(1,2).print:

trait Printable[T] { 
    def str(indent: Int = 0): String 
    def str: String = str(0) 
    def print(indent: Int = 0): Unit = println(str(indent)) 
    def print: Unit = print(0) 
} 

Puis un implicite pour le faire ressembler str et print sont des méthodes de Any:

implicit def any2Printable(a: Any): Printable[Any] = new Printable[Any] { 
    import collection._ 
    def name = a match { 
    case a: Array[_] => "Array" 
    case g: GenTraversableLike[_, _] => g.stringPrefix 
    case i: Iterator[_] => "Iterator" 
    case _ => "" 
    } 
    object Iter { 
    def unapply(a: Any): Option[GenIterable[_]] = a match { 
     case a: Array[_] => Some(a.toIterable) 
     case t: GenTraversableOnce[_] => Some(t.toIterable) 
     case _ => None 
    } 
    } 
    object Nested { 
    def unapply(i: GenIterable[_]): Option[GenIterable[_]] = i match { 
     case nested if i.exists{case Iter(j) =>true case _ =>false} => Some(nested) 
     case _ => None 
    } 
    } 
    def str(indent: Int = 0) = " " * indent + (a match { 
    case Iter(i) if i.isEmpty => name + " <empty>" 
    case Iter(Nested(i)) => name + "\n" + i.map(_.str(indent+2)).mkString("\n") 
    case Iter(i) => name + i.map(_.toString).mkString("(", ", ", ")") 
    case _ => a.toString 
    }) 
} 

Cette imbrication profonde gère arbitraire, mais ne sera pas utiliser plusieurs lignes pour des collections qui ne contiennent pas de collections - par exemple que imprimerait:

Array 
    Set 
    Array(1.0, 1.1) 
    Vector <empty> 
    Array <empty> 
    Set 
     Vector(a, b, c) 
     Vector(x, y) 
     List 
     Set 
      Array(1.0, 1.1) 
      Vector(0.0) 
     Set 
      Vector(a, b, c) 
      Vector(x, y) 
+0

C'est très cool, et ça gère aussi les itérateurs, ce qui est génial! (Je peux y aller maintenant et bidouiller ma scala-library.jar pour l'inclure dans Predef) –

1

Au lieu de convertir en un ArrayOps, essayez un WrappedArray, qui a une méthode de tête et est traversable.

1

Essayez d'utiliser des types plus généraux dans la phrase de correspondance, par exemple GenTraversable pour qu'il existe une conversion implicite.

import scala.collection.GenTraversable 

def rprint[T](a: T, indent: Int = 0): Unit = a match { 
    case x: String => println(" "*indent + '"' + x + '"') 
    case x: GenTraversable[_] => 
    println(" "*indent + (x.stringPrefix)) 
    x foreach (rprint(_, indent+2)) 
    case x: Array[_] => 
    println(" "*indent + (a.getClass.getSimpleName)) 
    x foreach (rprint(_, indent+2)) 
    case x => println(" "*indent + x) 
} 

rprint(Map(1->"one",2->"two")) 
rprint(List("one", "two"))   //I don't know why this type is printed weird: $colon$colon 
rprint(Array(1,2))     //This prints less weird: int[] 

Vous pouvez ajouter plus de cas pour les types manquants spécifiques (comme tuples)

Je ne peux pas comprendre quel est le problème avec l'impression des noms de type (comme List et Array - voir l'exemple après la fonction définition). Les deux a.getClass.getSimpleName et a.ToString retours chaîne câblés

J'ai essaye aussi de se connecter et de type ArrayGenTraversable - parce qu'il ya conversion implicite Array-WrapperArray <: GenTraversable.
La solution ne satisfait pas ci-dessous car il imprime « WrappedArray » au lieu « Array », quand nous appelons la fonction avec Array exemple

case x: Array[_] => rprint(x:GenTraversable[_]) 

Edit:
J'ai changé de nom pour extracteur GenTraversable à a.stringPrefix

+0

Cela ne compile pas (pour moi, en REPL, v2.9.0-1) 'erreur: incompatibilité de type; trouvé: a.type (avec le type T sous-jacent) requis:? {val getClass:?} '. Je recherche des noms d'affichage comme dans les méthodes 'toString', ou' stringPrefix', plutôt que des noms de classes, qui peuvent être différents (par exemple, le type concret de 'List' est' :: '). –

+0

Je ne sais pas. Pour moi tout va bien. –

+0

Je l'ai eu fonctionnant après la mise à niveau à 2.9.1. Je remplacerais '(a.getClass.getSimpleName)' avec simplement '" Array "'. Cela ne fait pas exactement ce qui est spécifié car il sort les éléments de collection/tableau chacun sur leur propre ligne, plutôt qu'une chaîne représentant la collection (voir OP pour le cas de test). C'est ce qui rend le problème non trivial. –

0

Je n'avais pas l'intention de répondre à ma propre question, mais j'ai quelque chose qui fonctionne. Je pense que cela peut être amélioré beaucoup, donc les suggestions sont appréciées. Il y a encore de la répétition et une distribution plutôt laide.

def rprint(a: Any, indent: Int = 0): Unit = { 
    val (typeStr, fullStr) = a match { 
     case x: collection.mutable.WrappedArray[_] => ("Array", x.deep.toString) 
     case x: Traversable[_] => (x.stringPrefix, x.toString) 
     case _ => ("", "") 
    } 
    a match { 
     case x: Traversable[_] => 
     if (x.isEmpty) 
      rprint(typeStr + " <empty>", indent) 
     else 
      x.head match { 
      case _: Array[_] => { 
       rprint(typeStr, indent) 
       x foreach {i => rprint(genericWrapArray(
             i.asInstanceOf[Array[_]]), indent + 2)} 
      } 
      case _: Traversable[_] => { 
       rprint(typeStr, indent) 
       x foreach {i => rprint(i, indent + 2)} 
      } 
      case _ => rprint(fullStr, indent) 
      } 
     case x: Array[_] => rprint(genericWrapArray(x)) 
     case x => println(" " * indent + x) 
    } 
    } 

test:

scala> rprint(List(Array(Vector(1.0,1.1), Vector(0d)), Array(Array("a", "b", "c"), Array("x", "y")))) 
List 
    Array 
    Vector(1.0, 1.1) 
    Vector(0.0) 
    Array 
    Array(a, b, c) 
    Array(x, y) 
Questions connexes