scala - स्काला: स्ट्रीम/इटरेटर को कई अलग-अलग संग्रहों में एकत्रित करने का तरीका पार करना




iterator traversal (2)

मैं लॉग फ़ाइल के माध्यम से जा रहा हूं जो स्मृति में फिट होने और 2 प्रकार के अभिव्यक्तियों को इकट्ठा करने के लिए बहुत बड़ा है, नीचे मेरे चलने वाला स्निपेट का बेहतर कार्यात्मक विकल्प क्या है?

def streamData(file: File, errorPat: Regex, loginPat: Regex): List[(String, String)]={
  val lines : Iterator[String] = io.Source.fromFile(file).getLines()

  val logins: mutable.Map[String, String] = new mutable.HashMap[String, String]()
  val errors: mutable.ListBuffer[(String, String)] = mutable.ListBuffer.empty

  for (line <- lines){
    line match {
      case errorPat(date,ip)=> errors.append((ip,date))
      case loginPat(date,user,ip,id) =>logins.put(ip, id)
      case _ => ""
    }
  }

  errors.toList.map(line => (logins.getOrElse(line._1,"none") + " " + line._1,line._2))
}

मेरे पास कुछ सुझाव हैं:

  • एक जोड़ी / ट्यूपल के बजाय, अपनी खुद की कक्षा का इस्तेमाल करना बेहतर होता है। यह दोनों प्रकार और इसके क्षेत्रों के लिए अर्थपूर्ण नाम देता है, जो कोड को और अधिक पठनीय बना देता है।
  • कोड को छोटे भागों में विभाजित करें विशेष रूप से, कोड के टुकड़े को छानने का प्रयास करें, जिन्हें एक साथ बांधा जाने की आवश्यकता नहीं है। यह आपके कोड को समझने में आसान बनाता है, अधिक मजबूत, त्रुटियों की संभावना कम और परीक्षण करने में आसान। आपके मामले में यह आपके इनपुट (लॉग फ़ाइल की लाइन) को अलग करना और परिणाम पेश करने के लिए उपभोग करना अच्छा होगा। उदाहरण के लिए, आप फ़ाइल में नमूना डेटा को संग्रहित किए बिना अपने फ़ंक्शन के लिए स्वत: परीक्षण करने में सक्षम होंगे।

एक उदाहरण और व्यायाम के रूप में, मैंने स्केलज़ इरेटेरेस के आधार पर एक समाधान करने की कोशिश की यह थोड़ी देर है ( IteratorEnumerator के लिए कुछ सहायक कोड भी शामिल है) और शायद यह कार्य के लिए थोड़ा अधिक है, लेकिन संभवतः किसी को यह उपयोगी मिलेगा।

import java.io._;
import scala.util.matching.Regex
import scalaz._
import scalaz.IterV._

object MyApp extends App {
  // A type for the result. Having names keeps things
  // clearer and shorter.
  type LogResult = List[(String,String)]

  // Represents a state of our computation. Not only it
  // gives a name to the data, we can also put here
  // functions that modify the state.  This nicely
  // separates what we're computing and how.
  sealed case class State(
    logins: Map[String,String],
    errors: Seq[(String,String)]
  ) {
    def this() = {
      this(Map.empty[String,String], Seq.empty[(String,String)])
    }

    def addError(date: String, ip: String): State =
      State(logins, errors :+ (ip -> date));
    def addLogin(ip: String, id: String): State =
      State(logins + (ip -> id), errors);

    // Produce the final result from accumulated data.
    def result: LogResult =
      for ((ip, date) <- errors.toList)
        yield (logins.getOrElse(ip, "none") + " " + ip) -> date
  }

  // An iteratee that consumes lines of our input. Based
  // on the given regular expressions, it produces an
  // iteratee that parses the input and uses State to
  // compute the result.
  def logIteratee(errorPat: Regex, loginPat: Regex):
            IterV[String,List[(String,String)]] = {
    // Consumes a signle line.
    def consume(line: String, state: State): State =
      line match {
        case errorPat(date, ip)           => state.addError(date, ip);
        case loginPat(date, user, ip, id) => state.addLogin(ip, id);
        case _                            => state
      }

    // The core of the iteratee. Every time we consume a
    // line, we update our state. When done, compute the
    // final result.
    def step(state: State)(s: Input[String]): IterV[String, LogResult] =
      s(el    = line => Cont(step(consume(line, state))),
        empty = Cont(step(state)),
        eof   = Done(state.result, EOF[String]))
    // Return the iterate waiting for its first input.
    Cont(step(new State()));
  }


  // Converts an iterator into an enumerator. This
  // should be more likely moved to Scalaz.
  // Adapted from scalaz.ExampleIteratee
  implicit val IteratorEnumerator = new Enumerator[Iterator] {
    @annotation.tailrec def apply[E, A](e: Iterator[E], i: IterV[E, A]): IterV[E, A] = {
      val next: Option[(Iterator[E], IterV[E, A])] =
        if (e.hasNext) {
          val x = e.next();
          i.fold(done = (_, _) => None, cont = k => Some((e, k(El(x)))))
        } else
          None;
       next match {
         case None => i
         case Some((es, is)) => apply(es, is)
       }
    }
  }


  // main ---------------------------------------------------
  {
    // Read a file as an iterator of lines:
    // val lines: Iterator[String] =
    //    io.Source.fromFile("test.log").getLines();

    // Create our testing iterator:
    val lines: Iterator[String] = Seq(
      "Error: 2012/03 1.2.3.4",
      "Login: 2012/03 user 1.2.3.4 Joe",
      "Error: 2012/03 1.2.3.5",
      "Error: 2012/04 1.2.3.4"
    ).iterator;

    // Create an iteratee.
    val iter = logIteratee("Error: (\\S+) (\\S+)".r, 
                           "Login: (\\S+) (\\S+) (\\S+) (\\S+)".r);
    // Run the the iteratee against the input
    // (the enumerator is implicit)
    println(iter(lines).run);
  }
}

यहां एक संभावित समाधान है:

def streamData(file: File, errorPat: Regex, loginPat: Regex): List[(String,String)] = {
  val lines = Source.fromFile(file).getLines
  val (err, log) = lines.collect {
        case errorPat(inf, ip) => (Some((ip, inf)), None)
        case loginPat(_, _, ip, id) => (None, Some((ip, id)))
      }.toList.unzip
  val ip2id = log.flatten.toMap
  err.collect{ case Some((ip,inf)) => (ip2id.getOrElse(ip,"none") + "" + ip, inf) }
}






collect