IO lento con datos grandes

Estoy tratando de encontrar una mejor manera de hacer esto, ¡ya que podría llevar años calcularlo! Necesito calcular un mapa que es demasiado grande para caber en la memoria, por lo que estoy tratando de hacer uso de IO de la siguiente manera.

Tengo un archivo que contiene una lista de Ints, aproximadamente 1 millón de ellos. Tengo otro archivo que contiene datos sobre mi colección de documentos (500,000). Necesito calcular una función del recuento, para cada Int en el primer archivo, de cuántos documentos (líneas en el segundo) aparece. Permítanme dar un ejemplo:

Archivo1:

-1
1
2
etc...

archivo2:

E01JY3-615,  CR93E-177 , [-1 -> 2,1 -> 1,2 -> 2,3 -> 2,4 -> 2,8 -> 2,... // truncated for brevity] 
E01JY3-615,  CR93E-177 , [1 -> 2,2 -> 2,4 -> 2,5 -> 2,8 -> 2,... // truncated for brevity]
etc...

Esto es lo que he intentado hasta ahora

def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) {
    val p = new java.io.PrintWriter(new BufferedWriter((new FileWriter(f))))
    try {
      op(p)
    } finally {
      p.close()
    }
  }

  def binarySearch(array: Array[String], word: Int):Boolean = array match {
    case Array() => false
    case xs      => if (array(array.size/2).split("->")(0).trim().toInt == word) {
      return true
    } else if (array(array.size/2).split("->")(0).trim().toInt > word){
      return binarySearch(array.take(array.size/2), word)
    } else {
      return binarySearch(array.drop(array.size/2 + 1), word)
    }
  }

  var v = Source.fromFile("vocabulary.csv").getLines()

  printToFile(new File("idf.csv"))(out => {
    v.foreach(word =>{
      var docCount: Int = 0
      val s = Source.fromFile("documents.csv").getLines()
      s.foreach(line => {
        val split = line.split("\\[")
        val fpStr = split(1).init
        docCount = if (binarySearch(fpStr.split(","), word.trim().toInt)) docCount + 1 else docCount
      })
      val output = word + ", " + math.log10(500448 / (docCount + 1))
      out.println(output)
      println(output)
    })
  })

Debe haber una forma más rápida de hacer esto, ¿alguien puede pensar en una forma?

preguntado el 08 de noviembre de 11 a las 15:11

¿Podemos comprobar algunas de sus suposiciones? ¿Quiere decir que el mapa [Int, Int] de 1 millón de enteros no cabe en la memoria? Iterator.from(1).take(1000000).map(i => i -> i).toMap no parece demasiado grande y funciona en el REPL. La otra cosa es que para cada una de sus entradas en el primer documento, parece que procesa el segundo archivo, ¿entonces está procesando 1M * 0.5M líneas? ¿Está bien? No tengo más tiempo para investigar esto, pero ¿por qué no puede procesar el segundo archivo solo una vez? -

En realidad, no es el mapa lo que es demasiado grande para guardarlo en la memoria en sí mismo, es demasiado grande cuando necesito hacer otras operaciones con uso intensivo de la memoria. Sin embargo, su segundo punto es bueno, y lo estoy probando ahora. -

1 Respuestas

Por lo que entiendo de su código, está tratando de encontrar cada palabra en el diccionario en la lista de documentos. Por lo tanto, está haciendo comparaciones N * M, donde N es el número de palabras (en el diccionario con números enteros) y M es el número de documentos en la lista de documentos. Al crear una instancia de sus valores, está tratando de calcular 10 ^ 6 * 5 * 10 ^ 5 comparaciones, que es 5 * 10 ^ 11. Imposible.

¿Por qué no crear un mapa mutable con todos los enteros en el diccionario como claves (1000000 ints en la memoria son aproximadamente 3.8M de mis mediciones) y pasar por la lista de documentos solo una vez, donde para cada documento extrae los enteros e incrementa el conteo respectivo? valores en el mapa (para los cuales el número entero es clave).

Algo como esto:

import collection.mutable.Map
import scala.util.Random._

val maxValue = 1000000

val documents = collection.mutable.Map[String,List[(Int,Int)]]()

// util function just to insert fake input; disregard
def provideRandom(key:String) ={ (1 to nextInt(4)).foreach(_ => documents.put(key,(nextInt(maxValue),nextInt(maxValue)) :: documents.getOrElse(key,Nil)))}

// inserting fake documents into our fake Document map 
(1 to 500000).foreach(_ => {val key = nextString(5); provideRandom(key)})

// word count map
val wCount = collection.mutable.Map[Int,Int]()

// Counting the numbers and incrementing them in the map
documents.foreach(doc => doc._2.foreach(k => wCount.put(k._1, (wCount.getOrElse(k._1,0)+1))))

scala> wCount
res5: scala.collection.mutable.Map[Int,Int] = Map(188858 -> 1, 178569 -> 2, 437576 -> 2, 660074 -> 2, 271888 -> 2, 721076 -> 1, 577416 -> 1, 77760 -> 2, 67471 -> 1, 804106 -> 2, 185283 -> 1, 41623 -> 1, 943946 -> 1, 778258 -> 2...

el resultado es un mapa cuyas claves son un número en el diccionario y el valor el número de veces que aparece en la lista de documentos

Esto está muy simplificado ya que

  • No verifico si el número existe en el diccionario, aunque solo necesita iniciar el mapa con los valores y luego incrementar el valor en el mapa final si tiene esa clave;
  • No hago IO, lo que acelera todo

De esta manera, solo pasa por los documentos una vez, lo que hace que la tarea sea factible nuevamente.

respondido 08 nov., 11:21

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.