Interfaz a una base de datos de dinosaurios CSV -- ruby campo con programming-challenge campo con csv camp codereview Relacionados El problema

Interface to a CSV dinosaur database


6
vote

problema

Español

Acabo de terminar con el primer ejercicio de rubí en los rieles de nivel, y quería tener una idea sobre cómo puedo refactorizar el código.

Puede encontrar la Ejercicio original en GitHub - Incluyendo los requisitos que yo Hay que implementar, así como los archivos de datos CSV (hay dos).

Requisitos

Vea el chequeo de los CSV y vuelva. ¿Hecho? Genial, acabo de recibir algunas características que necesito:

  1. Cargé mis dinosaurios favoritos en un archivo CSV que necesitarás para analizar. Sin embargo, no sé mucho sobre los dinosaurios africanos, así que descargué uno de la bahía de Pirate. No es formateado, así como el mío, por lo que deberá manejar ambos formatos.
  2. Tengo amigos que me hacen muchas preguntas sobre los dinosaurios (estoy un poco importante). Por favor, asegúrese de que el dinodex sea capaz de responder a estas cosas para mí:
    • agarra todos los dinosaurios que fueron bípedos.
    • Agarrar todos los dinosaurios que fueron carnívoros (recuento de peces e insectos).
    • agarrar dinosaurios por períodos específicos (sin necesidad de diferenciar entre los cretácidos tempranos y tardíos.
    • agarre solo grande (& gt; 2 toneladas) o pequeños dinosaurios.
    • Solo para estar seguro, me encantaría poder combinar los criterios a voluntad, incluso mejor si puedo cadenar llamadas de filtro juntos.
  3. Para un dino dado, me gustaría poder imprimir todos los hechos conocidos sobre ese dinosaurio. Si faltan hechos, no imprima los valores vacíos, simplemente salte a ese encabezado. Asegúrese de imprimir temprano / tarde, etc. para los períodos.
  4. también, probablemente querré imprimir todos los dinosaurios en una colección determinada (después de filtrar, etc.).

Mi código es el siguiente:

  require 'csv'  # handle filtering on weight def weight_filter(data, property, value)   if value.downcase == "large"     data.delete_if do |row|       weight = row["weight_in_lbs"]       weight.nil? ? true : weight <= 2000     end   else     data.delete_if do |row|       weight = row["weight_in_lbs"]       weight.nil? ? true : weight > 2000     end   end end  # properly format the arguments def format_args(property, value)    # make Insectivores and Piscivores into Carnivores   if value == "Carnivore"     value = Array.new     value << "Carnivore" << "Insectivore" << "Piscivore"   end    return property, value end  def filter_on(filters = {})   # check arguments   if filters.empty?     message = <<-EOS        Usage: filter_on( {property => value} )       Where property can be:'WALKING' | 'DIET' | 'PERIOD' | 'SIZE'        Example Usage: filter_on({ "WALKING" => "Biped", "DIET" => "Carnivore"})      EOS     message   else     # read data     data = read_data      # filter     filters.each do |property, value|       property = property.downcase        if property == "size"         # special handler for weight         weight_filter(data, property, value)       else         property, value = format_args(property, value)         data.delete_if { |row| !(value.include?(row[property])) }       end      end      # skip headers when printing     no_headers = []      data.each { |row| no_headers << row }      no_headers   end end  def dinoinfo(dinosaur)   # rationalize argument   dinosaur.capitalize!    single_dino_data = ''     # load data into memory   data_set = read_data    # extract single dinosaur row from data_set   read_data.each do |row|     single_dino_data = row if row["name"] == dinosaur   end    formatted_output = " "    # did we find a match?    if single_dino_data.empty?     formatted_output << " We did not find a match for "#{dinosaur}" in our Dinodex!  "   else     # format extraction     single_dino_data.each do |property, value|       if !value.nil?         # add colon         property = "#{property}:"          formatted_output << "#{property.upcase.rjust(15)}   #{value}  "       end     end   end    formatted_output end  def read_data   # load dinodex.csv into memory   dinodex = CSV.read('dinodex.csv', headers: true, converters: :numeric,             header_converters: :downcase)    # append information from african dinos    CSV.foreach('african_dinosaur_export.csv', headers: true, converters: :numeric,                header_converters: :downcase) do |row|      formatted_input = []      formatted_input << row["genus"] << row["period"] << nil       # handle carnivore     row["carnivore"] == "Yes" ? formatted_input << "Carnivore" : formatted_input << nil       # continue adding to formatted input     formatted_input << row["weight"] << row["walking"]       # add to dinodex     dinodex << formatted_input   end    dinodex end   

Me encantaría escuchar algunos comentarios sobre lo que se puede hacer mejor.

Original en ingles

I have just finished with the first Ruby exercise on Level Up Rails, and wanted to get an idea on how I can refactor the code.

You can find the original exercise on github - including the requirements that I have to implement as well as the CSV data files (there are two).

Requirements

Go check out the CSVs and come back. Done? Cool, I've just got a few features I need:

  1. I loaded my favorite dinosaurs into a CSV file you'll need to parse. I don't know a lot about African Dinosaurs though, so I downloaded one from The Pirate Bay. It isn't formatted as well as mine, so you'll need to handle both formats.
  2. I have friends who ask me a lot of questions about dinosaurs (I'm kind of a big deal). Please make sure the dinodex is able to answer these things for me:
    • Grab all the dinosaurs that were bipeds.
    • Grab all the dinosaurs that were carnivores (fish and insects count).
    • Grab dinosaurs for specific periods (no need to differentiate between Early and Late Cretaceous, btw).
    • Grab only big (> 2 tons) or small dinosaurs.
    • Just to be sure, I'd love to be able to combine criteria at will, even better if I can chain filter calls together.
  3. For a given dino, I'd like to be able to print all the known facts about that dinosaur. If there are facts missing, please don't print empty values, just skip that heading. Make sure to print Early / Late etc for the periods.
  4. Also, I'll probably want to print all the dinosaurs in a given collection (after filtering, etc).

My code is as follows:

require 'csv'  # handle filtering on weight def weight_filter(data, property, value)   if value.downcase == "large"     data.delete_if do |row|       weight = row["weight_in_lbs"]       weight.nil? ? true : weight <= 2000     end   else     data.delete_if do |row|       weight = row["weight_in_lbs"]       weight.nil? ? true : weight > 2000     end   end end  # properly format the arguments def format_args(property, value)    # make Insectivores and Piscivores into Carnivores   if value == "Carnivore"     value = Array.new     value << "Carnivore" << "Insectivore" << "Piscivore"   end    return property, value end  def filter_on(filters = {})   # check arguments   if filters.empty?     message = <<-EOS        Usage: filter_on( {property => value} )       Where property can be:'WALKING' | 'DIET' | 'PERIOD' | 'SIZE'        Example Usage: filter_on({ "WALKING" => "Biped", "DIET" => "Carnivore"})      EOS     message   else     # read data     data = read_data      # filter     filters.each do |property, value|       property = property.downcase        if property == "size"         # special handler for weight         weight_filter(data, property, value)       else         property, value = format_args(property, value)         data.delete_if { |row| !(value.include?(row[property])) }       end      end      # skip headers when printing     no_headers = []      data.each { |row| no_headers << row }      no_headers   end end  def dinoinfo(dinosaur)   # rationalize argument   dinosaur.capitalize!    single_dino_data = ''     # load data into memory   data_set = read_data    # extract single dinosaur row from data_set   read_data.each do |row|     single_dino_data = row if row["name"] == dinosaur   end    formatted_output = "\n"    # did we find a match?    if single_dino_data.empty?     formatted_output << "\tWe did not find a match for \"#{dinosaur}\" in our Dinodex!\n\n"   else     # format extraction     single_dino_data.each do |property, value|       if !value.nil?         # add colon         property = "#{property}:"          formatted_output << "#{property.upcase.rjust(15)}   #{value}\n\n"       end     end   end    formatted_output end  def read_data   # load dinodex.csv into memory   dinodex = CSV.read('dinodex.csv', headers: true, converters: :numeric,             header_converters: :downcase)    # append information from african dinos    CSV.foreach('african_dinosaur_export.csv', headers: true, converters: :numeric,                header_converters: :downcase) do |row|      formatted_input = []      formatted_input << row["genus"] << row["period"] << nil       # handle carnivore     row["carnivore"] == "Yes" ? formatted_input << "Carnivore" : formatted_input << nil       # continue adding to formatted input     formatted_input << row["weight"] << row["walking"]       # add to dinodex     dinodex << formatted_input   end    dinodex end 

I would love to hear some feedback on what can be done better.

        

Lista de respuestas

2
 
vote

Tengo poca idea de cómo usaría esto. Clasta vive los requisitos por partes, pero no de una manera útil. Por ejemplo, la salida de formato solo funciona para un solo dinosaurio llamado, mientras que el filtrado devuelve algo que no puedo formatear fácilmente.

En general, propondría una estructura diferente por completo, pero simplemente pasaré a través de tus métodos actuales y comentaré en cada uno. En general, hay muchos efectos secundarios que están pasando y su nombramiento podría usar algún trabajo. Sus métodos también son inconsistentes en que pueden devolver cosas muy diferentes en lugar de ser predecibles. A menudo es porque tratan de hacer más de una cosa, aunque esa es una mala idea.


map (map fromIntegral)7 tiene mucha repetición. Y tiene un argumento ( map (map fromIntegral)8 ) que nunca se usa! También usa map (map fromIntegral)9 , que me desalentan firmemente, ya que altera la matriz que lo está llamando; Soy. Tiene efectos secundarios. Sería más lejano usar fmap fromIntegral0 o fmap fromIntegral31 para producir una nueva matriz de subconjuntos de la matriz inicial.
En términos de nombramiento, fmap fromIntegral2 no es demasiado descriptivo: simplemente lo llamaría fmap fromIntegral3 Dado que es lo que es.

Una reescritura recta (es decir, mantener la API intacta, las verrugas y todas) del código en sí podría ser:

  fmap fromIntegral4  

fmap fromIntegral5 es un nombre muy obtuso. ¿Qué args se formatea exactamente? Método Argumentos? Argumentos de la línea de comandos? Bueno, no, nada del tipo. Todo lo que hace es "normalizar" el atributo de la dieta / propiedad. Y, nuevamente, hay un argumento 998877766555443336655443336 que parece superfluo. El método solo tiene sentido en el contexto del atributo / propiedad "Dieta", pero el método en sí no comprueba eso. El fmap fromIntegral7 argumento solo pasa. También devuelve una matriz o simplemente su valor de entrada, lo que significa que realmente no sabe lo que va a recuperar.

En términos de código, si tuviera que hacer una reescritura directa, haría esto:

  fmap fromIntegral8  

fmap fromIntegral9 es realmente extraño. Volverá un 99887766655443340 o un bloque de texto que describe el uso. Esto, francamente, no tiene sentido. Claro, un programa de línea de comandos a menudo imprimirá su uso si no se les da argumentos, o de lo contrario, imprimirá la salida. Pero esto no es un programa de línea de comandos; Es solo un método dentro de un programa. Y devolver una tabla no está imprimiendo la salida. Como con zipWith (zipWith ..)1 no sabes lo que vas a volver. Si es algo, tal vez puede lanzar un 99887766655443342 Si no se le dan filtros, pero tendría más sentido para simplemente devolver el conjunto de datos completo cuando no hay filtros. Y debido a que zipWith (zipWith ..)3 también lee todos los datos, siempre comenzará desde cero, no le permitirá encadenar los filtros, aunque es un requisito suave.
No me molestaré con una reescritura, porque francamente no ayudaría mucho.

zipWith (zipWith ..)4 tendría sentido si podría darle una fila de datos, y tenerlo en formato que y solo eso. Pero eso no es lo que hace. En su lugar, es un método de filtro que solo se ve solo en el nombre, y formats lo que encuentra. Por lo tanto, no puede combinarlo con zipWith (zipWith ..)5 , que parece ser el mejor caso de uso.
También tiene efectos secundarios en la primera línea, cuando zipWith (zipWith ..)6 el argumento; Este es un no-no. No sabes de dónde viene esa cadena, sin embargo, lo estás cambiando en el lugar. En otras palabras, estás alterando los datos que no te pertenecen.
También lee los datos dos veces por alguna razón. Primero leíste todo en zipWith (zipWith ..)7 , pero nunca usa eso. Luego lo lees todo nuevamente, y usas zipWith (zipWith ..)8 para encontrar el dinosaurio, cuando debe usar zipWith (zipWith ..)9 .
Nuevamente, no se molestará con una reescritura.

elementwise0 asume que el archivo 99887766555443351 es prístino, correcto y representa el formato canónico, y luego intenta cambiar el elementwise2 archivo en eso. Ese un poco tiene sentido, pero te has perdido una cosa: en elementwise3 El período de Yangchuanosaurus es "Oxfordiano", pero eso no es un período geológico. "Oxfordian" es una etapa dentro del período jurásico tardío (C.F. Wikipedia). Entonces, de hecho, el archivo elementwise4 tampoco es perfecto.

En realidad, ambos archivos necesitan algún procesamiento para que coincida con un formato común.


así que dije que elegiría un di Enfoque en total. A saber, modelaría un dinosaurio.

Cuando digo modelo, me refiero a hacer una clase llamada elementwise5 con todos los métodos y atributos necesarios. No solo tenga un gran objeto CSV.

La idea es hacer que los datos se ajusten a los accesorios y proporcionen accesorios que le permitan consultar los datos de diferentes maneras.

Por ejemplo, es posible que tenga una clase con una interfaz como esta:

  elementwise6  

Dada una matriz de objetos elementwise7 , puede filtrarlo fácilmente usando Ruby básico:

  elementwise8  

y puede envolver la matriz en una clase elementwise9

 

I have little idea of how I would use this. It sorta lives up the requirements piecemeal, but not in a useful way. For instance, formatting output only works for a single, named dinosaur, while filtering returns something that I can't readily format.

Overall, I'd propose a different structure altogether, but I'll just go through your current methods and comment on each. In general, there are a lot of side-effects going on and your naming could use some work. Your methods are also inconsistent in that they can return very different things rather than being predictable. Often it's because they try to do more than one thing, though that's a bad idea.


#weight_filter has a lot of repetition. And it has an argument (property) that is never used! You also use delete_if, which I strongly discourage, since it alters the array you're calling it on; i.e. it has side-effects. It'd be neater to use #select or #reject to produce a new subset array from the initial array.
In terms of naming, data isn't too descriptive - I'd just call it dinosaurs since that's what it is.

A straight-up rewrite (i.e. keeping the API intact, warts and all) of the code itself might be:

def weight_filter(data, property, value)   target = value.downcase == "large" ? :large : :small   data.delete_if do |row|     size = row["weight_in_lbs"].to_i > 2000 ? :large : :small     size != target   end end 

#format_args is a very obtuse name. What args does it format exactly? Method arguments? Command line arguments? Well, no, nothing of the sort. All it does is "normalize" the diet attribute/property. And, again, there's a property argument that seems superfluous. The method only makes sense in the context of the "diet" attribute/property, yet the method itself doesn't check for that. The property argument just passes through. It also returns either an array or simply its input value, which means you really don't know what you're going to get back.

In terms of code, if you were to do a straight-up rewrite, I'd do this:

def format_args(property, value)   if value == "Carnivore"     property, %w{Carnivore Insectivore Piscivore}   else     property, value   end end 

#filter_on is really strange. It'll either return a CSV::Table or a block of text describing usage. This, frankly, makes no sense. Sure, a command line program will often print its usage if given no arguments, or otherwise it'll print output. But this isn't a command line program; it's just a method within a program. And returning a table is not printing output. Like with #format_args you don't know what you're going to get back. If anything, you could maybe throw an ArgumentError if no filters are given, but it'd make more sense to just return the full data set when there are no filters. And because #filter_on also reads all the data, it'll always start from scratch, not allowing you to chain filters, though that's a soft requirement.
I won't bother with a rewrite, because frankly it wouldn't really help much.

#dinoinfo would make sense if you could give it a row of data, and have it format that and only that. But that's not what it does. Instead it's a filter method that just looks only at the name, and formats what it finds. So you can't combine it with #filter_on, which would seem to be the best use case.
You also have side-effects in the first line, when you capitalize! the argument; this is a no-no. You don't know where that string comes from, yet you're changing it in-place. In other words, you're altering data that does not belong to you.
You also read data twice for some reason. First you read everything into data_set, but you never use that. Then you read it all again, and use #each to find the dinosaur, when you should use #detect.
Again, won't bother with a rewrite.

#read_data assumes that the dinodex.csv file is pristine, correct, and represents the canonical format, and then attempts to change the african_dinosaur_export.csv file into that. That kinda makes sense, but you've missed one thing: In dinodex.csv the Yangchuanosaurus' period is "Oxfordian" - but that's not a geological period. "Oxfordian" is a stage within the Late Jurassic period (c.f. wikipedia). So in fact, the dinodex.csv file isn't perfect either.

So really, both files need some processing in order to match a common format.


So I said I'd choose a different approach altogether. Namely, I'd model a dinosaur.

When I say model, I mean make a class called Dinosaur with all the necessary methods and attributes. Don't just have a big CSV object.

The idea is to make the data set consistent and provide accessors that let you query the data in different ways.

For instance, you might have a class with an interface like this:

class Dinosaur   attr_reader :name, :weight, :diet, :period, :continent, :locomotion, :description    # create a new dinosaur from a hash of attributes   def initialize(attributes); end    # Is this dinosaur bipedal?   def biped?; end    # Is this dinosaur a carnivore (incl. piscivores and insectivores)?   def carnivore?; end    # Does this dino weight more than 2000lbs   def large?; end    # Is this dinosaur from the given period?   def from_period?(period); end    # Formatted string   def to_s; end    # A hash that can be serialized as JSON   def as_json; end end 

Given an array of Dinosaur objects, you can easily filter it using basic Ruby:

# find a specific dinosaur dino = dinosaurs.detect { |dino| dino.name == "Yangchuanosaurus" }  # find large, carnivorous, bipedal dinosaurs badasses = dinosaurs.select(&:biped?).select(&:large?).select(&:canivore?)  # print the badasses badasses.each { |dino| puts dino.to_s } 

And you can wrap the array in a Dinodex class to make the above filtering simpler.

 
 
 
 

Relacionados problema

1  Copiando CSV a SQL_Table  ( Copying csv to sql table ) 
Escribí el código que lleva CSV y expórtelo a la tabla SQL. Parece esto: output_leftBound()1 Este método creará una tabla basada en la variable de nombr...

8  Python CSV a XML Converter  ( Python csv to xml converter ) 
Estoy creando una aplicación que se lee en los datos de un archivo CSV y crea un archivo XML usando LXML. El siguiente código funciona como se esperaba. Sin e...

6  Herramientas de línea de comandos para formatear tablas  ( Command line tools to format tables ) 
Soy un gran fanático de un revestimiento usando sed awk 9988777665544332 y otras herramientas. Pero hay cosas difíciles de hacer en una sola línea, como...

4  73 líneas de mayhem - analizar, ordenar y guardar en CSV en PHP CLI  ( 73 lines of mayhem parse sort and save to csv in php cli ) 
Dentro de una carpeta llamada dispatch_sync(dispatch_get_main_queue(), ^{1 Tengo 138 archivos de texto (un total de 349 MB) lleno de direcciones de correo e...

3  Cómo pedir a un usuario a guardar y regenerar algunas entradas en un archivo  ( Prompting a user to save and regenerate some entries in a file ) 
Quiero eliminar mi goto en este código, pero no estoy seguro de cómo. Debe haber una forma más limpia de escribir mi código. Tal vez mueva la declaración "IF"...

24  Generando cuerdas CSV para varias utilidades de terceros  ( Generating csv strings for various 3rd party utilities ) 
Estoy generando cuerdas de CSV para varias utilidades de terceros y esta sección del código se repite en muchas clases. ¿Hay una mejor manera de generar esta ...

5  Analice un archivo CSV y devuelva un objeto o matriz  ( Parse a csv file and return an object or array ) 
Recientemente publiqué un módulo de JavaScript para NPM que también está disponible on Github por el nombre de Rawiki-Pars-CSV. El código lleva datos RAW C...

3  Determinar los promedios de columnas  ( Determine column averages ) 
Estoy tratando de escribir código de TERSE PERL para calcular el promedio de cada columna en un archivo. El archivo puede tener & gt; = 1 columnas. use str...

4  Vectorizamos la prueba exacta de Fisher  ( Vectorize fishers exact test ) 
Tengo dos marcos de datos / listas de datos, humanSplit 9988776655544331 , y son del formulario > ratSplit$Kidney_F_GSM1328570 ratGene ratRepli...

7  La forma más rápida de escribir un archivo CSV grande en Python  ( Fastest way to write large csv file in python ) 
Soy bastante nuevo para Python y Pandas, pero tratando de mejorar con él para analizar y procesar archivos de datos grandes. Actualmente estoy trabajando en u...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos