2016-04-06 8 views
7

Следующий код преуспевает, но есть ли лучший способ сделать то же самое? Возможно, что-то конкретное для классов классов? В следующем коде для каждого поля типа String в моем классе простых случаев код проходит через мой список экземпляров этого класса case и находит длину самой длинной строки этого поля.Scala: классы отражений и корпусов

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)) 
    } 
    } 
} 

Результат:

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

ответ

10

Если вы хотите сделать это с помощью Shapeless, я бы настоятельно предложил определить класс пользовательского типа, который обрабатывает сложную часть и позволяет сохранить этот материал отдельно от остальной части вашей логики.

В этом случае это похоже на сложную часть того, что вы пытаетесь сделать, это получить сопоставление от имен полей до длин строк для всех членов класса String. Вот класс типа, который делает это:

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) 
    } 
} 

Это выглядит сложное, но как только вы начинаете работать с бесформенным немного вы научитесь писать такие вещи во сне.

Теперь вы можете написать логику вашей работы относительно простым способом:

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))) 
    } 
    } 

А затем (с учетом rows, как это определено в вопросе):

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

Это будет работать для абсолютно любой случай класс.

Если это одноразовая вещь, вы можете использовать отражение во время выполнения, или вы можете использовать подход Poly1 в ответе Джованни Капораретти - он менее общий, и он смешивает различные части решения так, как я не предпочитают, но он должен работать нормально. Если это то, что вы делаете много, я бы предложил подход, который я здесь привел.

+0

Большое спасибо.Я должен вернуться к этому, когда я освоил Безжизненное! –

+1

Не ждите до тех пор! Я не уверен, что кто-то овладел безжизненным! –

+0

приятно, гораздо лучше, чем у меня! –

2

Вы, вероятно, хотите использовать Scala отражение:

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") 
} 

Так что вы можете избежать проблем с таких случаях, как:

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

Приветствиях

+0

Это тот, который я, вероятно, буду использовать! По крайней мере, пока я не пойду безжизненным. –

3

Если вы хотите использовать бесформенные, чтобы получить строку поля в случае класса и избежать отражения вы можете сделать что-то вроде этого:

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

Я держу струны здесь, но вы можете легко заменить их своей длиной. –

0

Я рефакторинг кода к чему-то более многоразовых:

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) 
    } 
} 

Использование бесформенного избавится от .asInstanceOf, но суть будет такой же. Основная проблема с данным кодом заключалась в том, что он не был повторно использован, поскольку логика агрегации была смешана с логикой отражения, чтобы получить имена полей.