2016-04-06 20 views
7

Der folgende Code ist erfolgreich, aber gibt es eine bessere Möglichkeit, dasselbe zu tun? Vielleicht etwas Spezifisches für Fallklassen? Im folgenden Code durchläuft der Code für jedes Feld vom Typ String in meiner einfachen Fallklasse die Liste der Instanzen dieser Fallklasse und findet die Länge der längsten Zeichenfolge dieses Feldes.Scala: Reflexion und Fallklassen

case class CrmContractorRow(
          id: Long, 
          bankCharges: String, 
          overTime: String, 
          name$id: Long, 
          mgmtFee: String, 
          contractDetails$id: Long, 
          email: String, 
          copyOfVisa: String) 

object Go { 
    def main(args: Array[String]) { 
    val a = CrmContractorRow(1,"1","1",4444,"1",1,"1","1") 
    val b = CrmContractorRow(22,"22","22",22,"55555",22,"nine long","22") 
    val c = CrmContractorRow(333,"333","333",333,"333",333,"333","333") 
    val rows = List(a,b,c) 

    c.getClass.getDeclaredFields.filter(p => p.getType == classOf[String]).foreach{f => 
     f.setAccessible(true) 
     println(f.getName + ": " + rows.map(row => f.get(row).asInstanceOf[String]).maxBy(_.length)) 
    } 
    } 
} 

Ergebnis:

bankCharges: 3 
overTime: 3 
mgmtFee: 5 
email: 9 
copyOfVisa: 3 

Antwort

10

Wenn Sie so etwas mit Shapeless machen möchten, empfehle ich Ihnen, eine benutzerdefinierte Typklasse zu definieren, die den komplizierten Teil behandelt und Ihnen erlaubt, diese Dinge vom Rest Ihrer Logik zu trennen.

In diesem Fall klingt es nach dem kniffligen Teil dessen, was Sie gerade versuchen, die Zuordnung von Feldnamen zu String-Längen für alle String Mitglieder einer Fallklasse zu bekommen. Hier ist eine Art Klasse, die das tut:

import shapeless._, shapeless.labelled.FieldType 

trait StringFieldLengths[A] { def apply(a: A): Map[String, Int] } 

object StringFieldLengths extends LowPriorityStringFieldLengths { 
    implicit val hnilInstance: StringFieldLengths[HNil] = 
    new StringFieldLengths[HNil] { 
     def apply(a: HNil): Map[String, Int] = Map.empty 
    } 

    implicit def caseClassInstance[A, R <: HList](implicit 
    gen: LabelledGeneric.Aux[A, R], 
    sfl: StringFieldLengths[R] 
): StringFieldLengths[A] = new StringFieldLengths[A] { 
    def apply(a: A): Map[String, Int] = sfl(gen.to(a)) 
    } 

    implicit def hconsStringInstance[K <: Symbol, T <: HList](implicit 
    sfl: StringFieldLengths[T], 
    key: Witness.Aux[K] 
): StringFieldLengths[FieldType[K, String] :: T] = 
    new StringFieldLengths[FieldType[K, String] :: T] { 
     def apply(a: FieldType[K, String] :: T): Map[String, Int] = 
     sfl(a.tail).updated(key.value.name, a.head.length) 
    } 
} 

sealed class LowPriorityStringFieldLengths { 
    implicit def hconsInstance[K, V, T <: HList](implicit 
    sfl: StringFieldLengths[T] 
): StringFieldLengths[FieldType[K, V] :: T] = 
    new StringFieldLengths[FieldType[K, V] :: T] { 
     def apply(a: FieldType[K, V] :: T): Map[String, Int] = sfl(a.tail) 
    } 
} 

Das sieht kompliziert, aber sobald Sie anfangen zu arbeiten mit Shapeless ein bisschen Sie lernen, diese Art der Sache im Schlaf zu schreiben.

Jetzt können Sie die Logik Ihres Betriebs in einer relativ einfachen Art und Weise schreiben:

def maxStringLengths[A: StringFieldLengths](as: List[A]): Map[String, Int] = 
    as.map(implicitly[StringFieldLengths[A]].apply).foldLeft(
    Map.empty[String, Int] 
) { 
    case (x, y) => x.foldLeft(y) { 
     case (acc, (k, v)) => 
     acc.updated(k, acc.get(k).fold(v)(accV => math.max(accV, v))) 
    } 
    } 

Und dann (bei rows wie in der Frage festgelegt):

scala> maxStringLengths(rows).foreach(println) 
(bankCharges,3) 
(overTime,3) 
(mgmtFee,5) 
(email,9) 
(copyOfVisa,3) 

Dies wird für absolut arbeiten Jedenfalls Klasse. Wenn das eine einmalige Sache ist, können Sie auch die Laufzeitreflexion verwenden, oder Sie könnten den Ansatz Poly1 in Giovanni Caporaletti's Antwort verwenden - es ist weniger allgemein gehalten und es vermischt die verschiedenen Teile der Lösung in einer Weise, die ich nicht bevorzugen, aber es sollte gut funktionieren. Wenn Sie jedoch viel tun, würde ich Ihnen den Ansatz vorschlagen, den ich hier gegeben habe.

+0

Vielen Dank.Ich muss darauf zurückkommen, wenn ich Shapeless beherrsche! –

+1

Warten Sie nicht bis dann! Ich bin mir nicht sicher, ob jemand Shapeless beherrscht! –

+0

schön, viel besser als meins! –

2

Sie wahrscheinlich Scala Reflexion verwenden möchten:

import scala.reflect.runtime.universe._ 

val rm = runtimeMirror(getClass.getClassLoader) 
val instanceMirrors = rows map rm.reflect 
typeOf[CrmContractorRow].members collect { 
  case m: MethodSymbol if m.isCaseAccessor && m.returnType =:= typeOf[String] => 
    val maxValue = instanceMirrors map (_.reflectField(m).get.asInstanceOf[String]) maxBy (_.length) 
    println(s"${m.name}: $maxValue") 
} 

Damit Sie Probleme mit solchen Fällen vermeiden:

case class CrmContractorRow(id: Long, bankCharges: String, overTime: String, name$id: Long, mgmtFee: String, contractDetails$id: Long, email: String, copyOfVisa: String) { 
    val unwantedVal = "jdjd" 
} 

Prost

+0

Dies ist die, die ich wahrscheinlich verwenden werde! Zumindest bis ich Shapeless verstehe. –

3

Wenn Sie die String-Felder eines Falles Klasse zu erhalten und vermeiden Reflexion formlos verwenden möchten, können Sie etwas tun:

import shapeless._ 
import labelled._ 

trait lowerPriorityfilterStrings extends Poly2 { 
    implicit def default[A] = at[Vector[(String, String)], A] { case (acc, _) => acc } 
} 

object filterStrings extends lowerPriorityfilterStrings { 
    implicit def caseString[K <: Symbol](implicit w: Witness.Aux[K]) = at[Vector[(String, String)], FieldType[K, String]] { 
    case (acc, x) => acc :+ (w.value.name -> x) 
    } 
} 

val gen = LabelledGeneric[CrmContractorRow] 


val a = CrmContractorRow(1,"1","1",4444,"1",1,"1","1") 
val b = CrmContractorRow(22,"22","22",22,"55555",22,"nine long","22") 
val c = CrmContractorRow(333,"333","333",333,"333",333,"333","333") 
val rows = List(a,b,c) 

val result = rows 
    // get for each element a Vector of (fieldName -> stringField) pairs for the string fields 
    .map(r => gen.to(r).foldLeft(Vector[(String, String)]())(filterStrings)) 
    // get the maximum for each "column" 
    .reduceLeft((best, row) => best.zip(row).map { 
    case ([email protected](_, v1), (_, v2)) if v1.length > v2.length => kv1 
    case (_, kv2) => kv2 
    }) 

result foreach { case (k, v) => println(s"$k: $v") } 
+0

Ich behalte die Saiten hier, aber Sie können sie leicht durch ihre Längen ersetzen –

0

Ich habe Refactoring Code, um etwas mehr wiederverwendbarer:

import scala.reflect.ClassTag 

case class CrmContractorRow(
          id: Long, 
          bankCharges: String, 
          overTime: String, 
          name$id: Long, 
          mgmtFee: String, 
          contractDetails$id: Long, 
          email: String, 
          copyOfVisa: String) 

object Go{ 
    def main(args: Array[String]) { 
    val a = CrmContractorRow(1,"1","1",4444,"1",1,"1","1") 
    val b = CrmContractorRow(22,"22","22",22,"55555",22,"nine long","22") 
    val c = CrmContractorRow(333,"333","333",333,"333",333,"333","333") 
    val rows = List(a,b,c) 
    val initEmptyColumns = List.fill(a.productArity)(List()) 

    def aggregateColumns[Tin:ClassTag,Tagg](rows: Iterable[Product], aggregate: Iterable[Tin] => Tagg) = { 

     val columnsWithMatchingType = (0 until rows.head.productArity).filter { 
     index => rows.head.productElement(index) match {case t: Tin => true; case _ => false} 
     } 

     def columnIterable(col: Int) = rows.map(_.productElement(col)).asInstanceOf[Iterable[Tin]] 

     columnsWithMatchingType.map(index => (index,aggregate(columnIterable(index)))) 
    } 

    def extractCaseClassFieldNames[T: scala.reflect.ClassTag] = { 
     scala.reflect.classTag[T].runtimeClass.getDeclaredFields.filter(!_.isSynthetic).map(_.getName) 
    } 

    val agg = aggregateColumns[String,String] (rows,_.maxBy(_.length)) 
    val fieldNames = extractCaseClassFieldNames[CrmContractorRow] 

    agg.map{case (index,value) => fieldNames(index) + ": "+ value}.foreach(println) 
    } 
} 

Mit formlos würde die .asInstanceOf loswerden, aber die Essenz wäre die gleiche. Das Hauptproblem mit dem gegebenen Code war, dass er nicht wiederverwendbar war, da die Aggregationslogik mit der Reflexionslogik gemischt wurde, um die Feldnamen zu erhalten.

Verwandte Themen