Weblogs Código

Fixed Buffer

ClosedXML, una manera fácil de dar formato a nuestros .xlsx

diciembre 18, 2018 09:00

closedxml

Hace ya unos meses que empezamos en este blog, con una entrada sobre ClosedXML, pero si que es cierto, que se puedo quedar un poco corta, aunque daba las ideas más básicas. Hoy, vamos a ampliar esa pequeña píldora sobre ClosedXML aprendiendo a dar formato a nuestras hojas.

Nuestro objetivo

Vamos a intentar hacer una tabla de colores como la que tenemos a continuación:

Resultado ClosedXML

Creación del proyecto

Por cambiar un poco desde de la primera parte, vamos a crear un proyecto en .NetCore (ahora que también sabemos instalar el framework en linux o como depurar sobre SSH), para ello, creamos un proyecto de consola:

consola .net core

O desde el CLI de NetCore:

dotnet new console

Lo siguiente que tenemos que hacer es añadir el paquete ClosedXML a través de nuget. Esto se puede hacer a través de la “Consola de Administrador de Paquetes” con el comando:

PM->Install-Package ClosedXML

O también desde el CLI de NetCore:

dotnet add package ClosedXML

Como siempre, utilizando el administrador que integra VS:

Generando y formateando el fichero con ClosedXML

Teniendo claro el objetivo, vamos a ponernos con en faena y analizar el código que hemos utilizado:

using ClosedXML.Excel;
using System.Collections.Generic;

namespace PostClosedXML2
{
  class Program
  {
    /// <summary>
    /// Lista de colores para el ejemplo
    /// </summary>
    /// <returns></returns>
    static IEnumerable<XLColor> GetColors()
    {
      yield return XLColor.Red;
      yield return XLColor.Amber;
      yield return XLColor.AppleGreen;
      yield return XLColor.AtomicTangerine;
      yield return XLColor.BallBlue;
      yield return XLColor.Bittersweet;
      yield return XLColor.CalPolyPomonaGreen;
      yield return XLColor.CosmicLatte;
      yield return XLColor.DimGray;
      yield return XLColor.ZinnwalditeBrown;
    }

    static void Main(string[] args)
    {
      using (var workbook = new XLWorkbook())
      {
        //Generamos la hoja
        var worksheet = workbook.Worksheets.Add("FixedBuffer");
        //Generamos la cabecera
        worksheet.Cell("A1").Value = "Nombre";
        worksheet.Cell("B1").Value = "Color";

        //-----------Le damos el formato a la cabecera----------------
        var rango = worksheet.Range("A1:B1"); //Seleccionamos un rango
        rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
        rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
        rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; //Alineamos horizontalmente
        rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente
        rango.Style.Font.FontSize = 14; //Indicamos el tamaño de la fuente
        rango.Style.Fill.BackgroundColor = XLColor.AliceBlue; //Indicamos el color de background
        

        //-----------Genero la tabla de colores-----------
        int nRow = 2;
        foreach (var color in GetColors())
        {
          worksheet.Cell(nRow, 1).Value = color.ToString(); //Indicamos el valor en la celda nRow, 1
          worksheet.Cell(nRow, 2).Style.Fill.BackgroundColor = color; //Cambiamos el color de background de la celda nRow,2
          nRow++;
        }

        //Aplico los formatos
        rango = worksheet.Range(2, 1, nRow-1, 2); //Seleccionamos un rango
        rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
        rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
        rango.Style.Font.SetFontName("Courier New"); //Utilizo una fuente monoespacio
        rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right; //Alineamos horizontalmente
        rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente


        worksheet.Columns(1, 2).AdjustToContents(); //Ajustamos el ancho de las columnas para que se muestren todos los contenidos

        workbook.SaveAs("CellFormating.xlsx");  //Guardamos el fichero
      }
    }
  }
}

Lo primero de todo, hemos creado una lista con los colores que queremos utilizar para nuestra tabla. Para ello, utilizamos los colores propios del paquete, que trae una lista enorme de colores:

static IEnumerable<XLColor> GetColors()
{
  yield return XLColor.Red;
  yield return XLColor.Amber;
  yield return XLColor.AppleGreen;
  yield return XLColor.AtomicTangerine;
  yield return XLColor.BallBlue;
  yield return XLColor.Bittersweet;
  yield return XLColor.CalPolyPomonaGreen;
  yield return XLColor.CosmicLatte;
  yield return XLColor.DimGray;
  yield return XLColor.ZinnwalditeBrown;
}

Una vez que tenemos eso controlado, vamos a entrar al jugo del código. En primero lugar, podemos ver que todo el código esta dentro de un using:

using (var workbook = new XLWorkbook())
{
   //Código
}

Con esto conseguimos que los recursos utilizados para generar el fichero se liberen correctamente al acabar de usarlos. Lo siguiente que vemos, es como generamos la hoja dentro del libro:

//Generamos la hoja
var worksheet = workbook.Worksheets.Add("FixedBuffer");

Una vez que tenemos la hoja creada, añadimos las dos primeras celdas que nos servirán de cabecera para la tabla:

//Generamos la cabecera
worksheet.Cell("A1").Value = "Nombre";
worksheet.Cell("B1").Value = "Color";

Vamos a darle formato a la cabecera de la tabla:

//-----------Le damos el formato a la cabecera----------------
var rango = worksheet.Range("A1:B1"); //Seleccionamos un rango
rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; //Alineamos horizontalmente
rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente
rango.Style.Font.FontSize = 14; //Indicamos el tamaño de la fuente
rango.Style.Fill.BackgroundColor = XLColor.AliceBlue; //Indicamos el color de background

Para trabajar con mayor comodidad, seleccionamos un rango (A1:B1), de modo que todo lo que hagamos sobre el rango, se va a aplicar a todas las celdas del rango, en este caso a A1 y a B1. Lo que hacemos es asignarle los bordes extreriores e interiores, alinear el contenido horizontal y verticalemnte, le modificamos el tamaño de la fuente, y por ultimo, pintamos el color de fondo. Sin ningun problema, generamos la tabla de colores:

//-----------Genero la tabla de colores-----------
int nRow = 2;
foreach (var color in GetColors())
{
    worksheet.Cell(nRow, 1).Value = color.ToString(); //Indicamos el valor en la celda nRow, 1
    worksheet.Cell(nRow, 2).Style.Fill.BackgroundColor = color; //Cambiamos el color de background de la celda nRow,2
    nRow++;
}

Como en casos anteriores, asignamos un valor a una celda, y un backcolor a la otra. Con esto, solo nos queda aplicar los formatos y guardar. Vamos a aplicar los formatos:

//Aplico los formatos
    rango = worksheet.Range(2, 1, nRow-1, 2); //Seleccionamos un rango
    rango.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thick); //Generamos las lineas exteriores
    rango.Style.Border.SetInsideBorder(XLBorderStyleValues.Medium); //Generamos las lineas interiores
    rango.Style.Font.SetFontName("Courier New"); //Utilizo una fuente monoespacio
    rango.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right; //Alineamos horizontalmente
    rango.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;  //Alineamos verticalmente

Pero ojo, esta vez, hemos asignado también la fuente:

rango.Style.Font.SetFontName("Courier New");

Esto lo hacemos para que la tabla nos quede perfectamente cuadrada al utilizar letras monoespacio. Por ultimo, queremos que las celdas se queden perfectamente a la vista todas, aunque el contenido sea mayor que el ancho de la columna, por lo tanto, vamos a ajustar el ancho de columnas. Eso lo hacemos con:

worksheet.Columns(1, 2).AdjustToContents(); //Ajustamos el ancho de las columnas para que se muestren todos los contenidos

Lo que conseguimos así, es que el ancho de las columnas 1 y 2, se ajuste de modo que se vea el contenido de todas las celdas. Con todo hecho, simplemente guardamos:

workbook.SaveAs("CellFormating.xlsx");  //Guardamos el fichero

Y al abrir el excel, veremos que se nos ha quedado una tabla como la que presentábamos al principio. Como siempre, si queréis probar el código fuente, os dejo el enlace al repositorio de GitHub. Esto solo es una pequeña pincelada de todo los que se puede conseguir con ClosedXML y con OpenXML en general. En futuras entradas, seguiremos ampliando su uso.

**La entrada ClosedXML, una manera fácil de dar formato a nuestros .xlsx se publicó primero en Fixed Buffer.**

» Leer más, comentarios, etc...

Adrianistán

Algoritmos genéticos: un caso práctico

diciembre 18, 2018 08:32

Existen muchos tipos de algoritmos. Hoy vamos a hablar de un tipo de algoritmo no demasiado conocido, los algoritmos genéticos, que son de la familia de los algoritmos evolutivos. Además veremos su aplicación práctica en el vectorizado de imágenes con un programa que he realizado en Rust.

¿Qué es un algoritmo genético?

Un algoritmo genético es un algoritmo inspirado en el mecanismo de la naturaleza de la evolución biológica, descrita por Darwin allá por 1859 en el libro El Origen de las Especies. La idea es tener un conjunto de individuos (posibles soluciones). Estos individuos son evaluados para ver qué tan buenos son. Quedarnos con los mejores y proceder a la creación de nuevos individuos como resultados de la recombinación genética de dos individuos (como en la reproducción sexual). Posteriormente añadir alguna mutación genética para explorar nuevas posibilidades ligeramente diferentes. Proceder de nuevo a la selección natural hasta que tengamos individuos lo suficientemente buenos para nosotros.

Normalmente no son los algoritmos más eficientes ni tienen por qué dar un resultado óptimo, pero pueden servir para dar aproximaciones bastante buenas al resultado óptimo. Existen estrategias para mejorar los algoritmos genéticos como la gestión de la antigüedad de los individuos, para evitar máximos locales; la conservación de individuos con mal desempeño, para conservar mayor variedad genética; …

Como veremos más adelante, uno de los elementos más importante de estos algoritmos es la función de evaluación, que muchas veces es más complicada de programar de lo que parece.

Un caso práctico: vectorizado de imágenes

Para ver como estos conceptos teóricos funcionan en la práctica, vamos a hacer un programa que vectorice imágenes. ¿Esto qué es? Digamos que hay dos tipos de imágenes en informática. Por un lado tenemos las imágenes que almacenan los colores en píxeles (rasterizadas) y por otro lado aquellas que almacenan la imagen como fórmulas matemáticas, que se aplican cuando se quiere ver la imagen (vectoriales). Los formatos más comunes de imágenes rasterizadas son JPEG, PNG, GIF y WEBP. Los formatos más comunes de imágenes vectoriales son SVG y AI.

A las imágenes rasterizadas no se les puede hacer zoom hasta el infinito, se ven borrosas
A las imágenes vectoriales se les puede hacer zoom infinito, no pierden calidad

Pasar de una imagen vectorial a una rasterizada es trivial, pero el proceso inverso no lo es, y es justo donde vamos a aplicar el algoritmo genético.

En nuestro caso, vamos a tomar siluetas, negras sobre fondo blanco y tratar de vectorizarlas con curvas de Bézier.


Ejemplo de ejecución en la versión final de Mendel Vectorizer. La curva azul es la imagen vectorial generada.

Curvas de Bézier

En los años 60, Pierre Bézier, que trabajaba para Renault, diseñó un tipo de curva para el diseño asistido por ordenador (CAD). Estas son las conocidas como curvas de Bézier. Nuestro algoritmo tratará de encontrar la curva de Bézier más similar a la curva de la imagen rasterizada. Para ello necesitamos un punto de inicio, un punto final y dos puntos de control.

Curva cúbica de Bézier

En nuestro algoritmo, las curvas serán los individuos, y las coordenadas de los puntos de control serán los genes (habrá 4 genes por tanto).

Este es el código que he usado para definir las curvas de Bézier en Rust.

#[derive(Copy, Clone)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}
impl Point {
    pub fn distance(&self, other: &Point) -> f64 {
        ((self.x - other.x).powf(2.0) + (self.y - other.y).powf(2.0)).sqrt()
    }

    pub fn middle(&self, other: &Point) -> Point {
        Point {
            x: (self.x + other.x) / 2.0,
            y: (self.y + other.y) / 2.0,
        }
    }
}

#[derive(Clone)]
/* Bezier */
pub struct Bezier {
    pub start: Point,
    pub control1: Point,
    pub control2: Point,
    pub end: Point,
}

impl<'a> Bezier {
    pub fn iter(&self) -> BezierIter {
        BezierIter {
            bezier: self,
            position: 0.0,
        }
    }
}

pub struct BezierIter<'a> {
    bezier: &'a Bezier,
    position: f64,
}

impl<'a> Iterator for BezierIter<'a> {
    type Item = Point;

    fn next(&mut self) -> Option<Point> {
        if self.position > 1.0 {
            return None;
        }
        let x = self.bezier.start.x * (1.0 - self.position).powf(3.0)
            + 3.0 * self.bezier.control1.x * self.position * (1.0 - self.position).powf(2.0)
            + 3.0 * self.bezier.control2.x * self.position.powf(2.0) * (1.0 - self.position)
            + self.bezier.end.x * self.position.powf(3.0);

        let y = self.bezier.start.y * (1.0 - self.position).powf(3.0)
            + 3.0 * self.bezier.control1.y * self.position * (1.0 - self.position).powf(2.0)
            + 3.0 * self.bezier.control2.y * self.position.powf(2.0) * (1.0 - self.position)
            + self.bezier.end.y * self.position.powf(3.0);
        self.position += 0.01;
        Some(Point { x, y })
    }
}

Encontrar puntos iniciales

El primer paso de nuestro algoritmo es buscar los puntos iniciales (y finales) de las curvas. En definitiva esto es un problema de búsqueda de esquinas.

Ejemplo de aplicación de FAST9 a una imagen

Existen varios algoritmos de búsqueda de esquinas: Harris, FAST9, FAST12, … No obstante, no tuve muy buenos resultados en las imágenes con las que trabajaba. Así que esta parte del algoritmo se la dejo al humano. El humano se encargará de poner los puntos, en orden, que tiene que tratar de unir el algoritmo con curvas de Bézier.

Función de evaluación

Muchas veces la función de evaluación es el punto más delicado de estos algoritmos. En este caso la idea fundamental es identificar si la curva de Bézier está encima de la curva original. Para ello tomamos 100 puntos equidistantes de la curva de Bézier (con la ecuación paramétrica de la curva es muy sencillo).

\(\mathbf{B}(t)=\mathbf{P}_0(1-t)^3+3\mathbf{P}_1t(1-t)^2+3\mathbf{P}_2t^2(1-t)+\mathbf{P}_3t^3 \mbox{ , } t \in [0,1].\)

Estos puntos se comparan con la imagen real, si ahí en la imagen original había una línea no pasa nada, si no, se resta 100. De este modo se penaliza gravemente salirse de la curva. Esto se hace así ya que la otra opción evidente (bonificar el estar sobre en la línea) podría dar lugar a resultados inesperados.

Ejemplificación de la función de evaluación. Los puntos amarillos están dentro de la línea. Los puntos verdes están fuera de la línea, penalizando a la curva en su “adaptación al medio”.

Pongamos como ejemplo una función de evaluación que bonifique por estar sobre la línea y no penalice por salirse de esta. Una línea bien adaptada a estas condiciones podría recorrerse toda la imagen, cruzando todas las líneas posibles, generando un garabato totalmente inútil pero muy bueno según esta función. Por ello, nuestra función de evaluación se basa en penalizar las salidas de la línea.

La función de evaluación presentada no es perfecta, ya que puede pasar de largo el punto final y dar la vuelta. Esta curva es más larga que la óptima, pero al estar completamente dentro de la línea negra original, la función de evaluación no la puede clasificar como peor que otras alternativas. Para solventar este problema una idea es que la longitud de la curva tenga una implicación en la función. No obstante, el cálculo de la longitud de una curva de Bezier es demasiado complejo y no lo he codificado. También podría aproximarse a través de segmentos rectos entre los 100 puntos calculados anteriormente.

Ejemplo de curva con puntuación máxima pero no óptima desde el punto de vista humano
pub fn evaluate(image: &amp;GrayImage, line: &amp;Bezier) -> f64 {
    let mut eval = 0.0;
    for point in line.iter() { // va generando los 100 puntos equidistantes
        let x = point.x as u32;
        let y = point.y as u32;
        if image.in_bounds(x, y) {
            let pixel = image.get_pixel(x, y);
            if pixel.data[0] > 200 {
                eval -= 100.0;
            }
        } else {
            eval -= 100.0;
        }
    }
    eval
}

Selección natural

La función de selección natural deja únicamente las 500 mejores curvas, de acuerdo a la función de evaluación, es decir, las mejor adaptadas de momento. Para la ordenación, Rust usa un algoritmo denominado Timsort, con coste O(nlogn). Sin embargo, en todo el algoritmo trabajamos con poblciones finitas, por lo que puede asimilarse a una constante, con coste O(1). 

pub fn natural_selection(image: &GrayImage, mut population: Vec<Bezier>) -> Vec<Bezier> {
    population.sort_by(|a, b| {
        let a = evaluate(&image, &a);
        let b = evaluate(&image, &b);
        b.partial_cmp(&a).unwrap()
    });

    population.into_iter().take(500).collect()
}

Población inicial

La población inicial se forma con 1000 curvas generadas con parámetros totalmente aleatorios. Los valores de cada coordenada, eso sí, están comprendidos entre -d y d siendo d la distancia en línea recta entre los puntos de inicio y final.

let mut population = Vec::new();
        let mut rng = thread_rng();
        let distancia = start.distance(&amp;end);
        for _ in 0..1000 {
            let xrand: f64 = rng.gen_range(-distancia, distancia);
            let yrand: f64 = rng.gen_range(-distancia, distancia);
            let mut control1 = start.middle(&amp;end);
            control1.x += xrand;
            control1.y += yrand;
            let mut control2 = start.middle(&amp;end);
            control2.x += xrand;
            control2.y += yrand;
            population.push(Bezier {
                start,
                end,
                control1,
                control2,
            });
        }

Recombinado

El proceso de recombinación permite mezclar los mejores especímenes tratando de conseguir uno mejor. Este algoritmo genético es de tipo RCGA (Real Coded Genetic Algorithm) ya que los genes son números reales, en contraposición a los típicos genes binarios.
Para estos algoritmos existen distintas variantes, aquí se usa el sistema de blend. El sistema de blend implica que de entre los dos padres se toman los valores mínimos y máximos para cada coordenada. Posteriormente se elige un nuevo valor de forma aleatoria con la condición de que esté dentro del rango de mínimo y máximo definido anteriormente.

// CROSSOVER
            
            let mut i: usize = 0;
            let mut babies = Vec::new();
            while i < GOOD_ONES {
                // 250 extra
                let line1 = &population[i];
                let line2 = &population[i + 1];

                let min_x = line1.control1.x.min(line2.control1.x);
                let max_x = line1.control1.x.max(line2.control1.x);
                let min_y = line1.control1.y.min(line2.control1.y);
                let max_y = line1.control1.y.max(line2.control1.y);
                let control1 = Point {
                    x: rng.gen_range(min_x, max_x),
                    y: rng.gen_range(min_y, max_y),
                };

                let min_x = line1.control2.x.min(line2.control2.x);
                let max_x = line1.control2.x.max(line2.control2.x);
                let min_y = line1.control2.y.min(line2.control2.y);
                let max_y = line1.control2.y.max(line2.control2.y);
                let control2 = Point {
                    x: rng.gen_range(min_x, max_x),
                    y: rng.gen_range(min_y, max_y),
                };

                babies.push(Bezier {
                    start,
                    end,
                    control1,
                    control2,
                });

                i += 2;
            }
            population.append(&mut babies);

Mutación

La fase de mutación permite generar pequeñas variaciones aleatorias respecto a la población. Afecta al 10% de la población aunque solo afecta a una coordenada a la vez.

Al ser un algoritmo RCGA, no vale simplemente con cambiar el valor de un bit. El enfoque utilizado en este algoritmo es el de una distribución normal de cambios de media 0. La distribución tiene la forma N(0,d/2). Esto implica que en la mayoría de las ocasiones la variación (tanto positiva como negativa) en la coordenada será bastante pequeña.

Distribución normal, aunque esta no es de media 0
// MUTATION
            // TASA DE MUTACION DEL 10%
            population = population
                .into_iter()
                .map(|mut line| {
                    if rng.gen::<f64>() < 0.10 {
                        let normal = Normal::new(0.0, distancia / 2.0);
                        let mutation_where: u32 = rng.gen_range(1, 5);
                        // Solo muta un gen, respecto a una Normal
                        match mutation_where {
                            1 => line.control1.x += rng.sample(normal),
                            2 => line.control1.y += rng.sample(normal),
                            3 => line.control2.x += rng.sample(normal),
                            4 => line.control2.y += rng.sample(normal),
                            _ => (),
                        }
                    }
                    line
                })
                .collect();

El programa: Mendel Vectorizer

El programa definitivo, Mendel Vectorizer, disponible en GitHub, tiene más detalles. La interfaz gráfica está hecha usando GTK, la interfaz de línea de comandos usa Clap y el algoritmo se ejecuta en paralelo usando paso de mensajes entre los hilos. El programa genera un fichero SVG resultado que puede ser abierto en programas como Inkscape o Adobe Illustrator.

El fichero generado por Mendel Vectorizer en Inkscape

Para usar Mendel Vectorizer en tu ordenador, sigue los siguientes pasos.

Descárgate una copia del código con Git. Compila con Rust (una edición que soporte la edición 2018, mínimo la 1.31) y ejecútalo.

git clone https://github.com/aarroyoc/mendel-vectorizer
cd mendel-vectorizer
cargo build
cargo run

También podéis descargaros la memoria en PDF del programa.

La entrada Algoritmos genéticos: un caso práctico se publicó primero en Adrianistán.

» Leer más, comentarios, etc...

Variable not found

Otras formas de obtener dependencias en controladores ASP.NET Core MVC

diciembre 18, 2018 07:55

ASP.NET Core MVC Hace unos días publicaba un post sobre la mala idea que era tener controladores con demasiadas responsabilidades y cómo un constructor con demasiadas dependencias podía ser una señal (un 'code smell') de que las cosas no estaban yendo bien en ese sentido.

Por ejemplo, echando un vistazo al siguiente controlador podemos ver claramente una violación del Principio de Responsabilidad Única (SRP) en un controlador que conoce demasiados detalles sobre la forma de proceder al registrar un pedido:
public class OrderController: Controller
{
private readonly IOrderServices _orderServices;
[...] // Other private members

public OrderController(IOrderServices orderServices, IUserServices userServices,
IMailingService mailingServices, ISmsServices smsServices,
IPdfGenerator pdfGenerator, IMapper mapper
)
{
_orderServices = orderServices;
_userServices = userServices;
[...] // Other assignments...
}

[HttpPost]
public Task<IActionResult> Submit(OrderViewModel orderViewModel)
{
if(!ModelState.IsValid)
{
return View(orderViewModel);
}
var orderDto = _mapper.Map<OrderDto>(orderViewModel);
var orderResult = await _orderServices.AddAsync(orderDto);
if(!orderResult.Success)
{
return RedirecToAction("OrderError", new { error = orderResult.Error }));
}
var userPreferences = await _userServices.GetNotificationPreferencesAsync(CurrentUserId);
var pdfUrl = await _pdfGenerator.GenerateOrderAsync(orderResult.Details);
if(userPreferences.NotificationMode == NotificationMode.Sms)
{
await _smsServices.NotifyNewOrderAsync(orderResult.Details, pdfUrl);
}
else
{
await _mailingServices.NotifyNewOrderAsync(orderResult.Details);
}
return RedirectToAction("ThankYou", new { id = orderResult.Details.OrderId } );
}
...
}
En dicho post comentaba también algunas cosas que podíamos hacer para solucionar el problema, así como una recomendación de lo que no debíamos hacer: disimular dependencias instanciando directamente componentes o utilizando otros "sabores" de inyección que hicieran menos evidente la relación de nuestra clase con otras de las que depende.

Pues bien, como hoy estamos algo rebeldes, vamos a ver las técnicas que nos permitirían hacerlo cuando sea necesario o, dicho de otra forma, qué alternativas tenemos para usar dependencias desde los controladores sin que estas sean suministradas mediante inyección en su constructor.
Precaución: estas técnicas son consideradas antipatrones o, como mínimo, code smells en la mayoría de los escenarios, así que usadlas poco, con precaución y siempre con conocimiento de causa ;)

Instanciación manual de componentes

En primer lugar, atentando directamente contra las buenas prácticas de diseño con bajo acoplamiento y la mismísima "D" de los principios SOLID, tenemos la posibilidad de instanciar los objetos que necesitemos, justo en el momento en que lo necesitemos.

El siguiente código muestra cómo podríamos reescribir el ejemplo anterior utilizando esta técnica:
public OrderController()
{
// Nothing to do...
}

[HttpPost]
public Task<IActionResult> Submit(OrderViewModel orderViewModel)
{
if(!ModelState.IsValid)
{
return View(orderViewModel);
}

var mapper = new Mapper();
var orderDto = mapper.Map<OrderDto>(orderViewModel);

using (var ctx = new ShoppingCartContext())
{
var orderServices = new OrderServices(ctx);
var orderResult = await orderServices.AddAsync(orderDto);
if(!orderResult.Success)
{
return RedirecToAction("OrderError", new { error = orderResult.Error }));
}
var userServices = new UserServices(ctx);
var userPreferences = await userServices.GetNotificationPreferencesAsync(CurrentUserId);
var prfGenerator = new PdfGenerator();
var pdfUrl = await pdfGenerator.GenerateOrderAsync(orderResult.Details);
if(userPreferences.NotificationMode == NotificationMode.Sms)
{
var smsServices = new SmsServices();
await smsServices.NotifyNewOrderAsync(orderResult.Details, pdfUrl);
}
else
{
var mailingServices = new MailingServices();
await mailingServices.NotifyNewOrderAsync(orderResult.Details);
}
return RedirectToAction("ThankYou", new { id = orderResult.Details.OrderId } );
}
}
Lo primero que habréis notado es que, obviamente, tendremos controladores con menos dependencias en el constructor, o incluso ninguna. También, las dependencias son creadas sólo cuando las necesitamos, por lo que se evitará la creación innecesaria de objetos que ocurre si nuestro constructor tiene muchos parámetros.

Sin embargo, hemos creado un monolito, una clase totalmente acoplada a sus dependencias. También hemos disminuido notablemente su legibilidad, al añadir mucha más complejidad al código. Ahora tenemos que encargarnos nosotros de gestionar instancias, sus liberaciones, suministrar las dependencias requeridas... todo un trabajazo que difícilmente será justificable teniendo otras opciones más sencillas como el uso de contenedores de inversión de control.

Y por último, no olvidemos que no podremos conocer las dependencias de un controlador a no ser que leamos detalladamente el código, puesto que éstas no estarán visibles en el constructor.

En general, por la gran cantidad de problemas que puede generar y el trabajo que supone, es una técnica que deberíamos usar rara vez en nuestros componentes para consumir dependencias.

Uso de Service locator

En segundo lugar, como una evolución de la anterior en un entorno guiado por la inyección de dependencias como es ASP.NET Core, podemos optar por el uso del patrón (y antipatrón al mismo tiempo) service locator.

Básicamente, esta técnica consigue desacoplar la clase de sus dependencias eliminando los new que la atan a implementaciones específicas mediante el uso de inversión de control. En lugar de instanciar nosotros las dependencias, usaremos un contenedor, en este caso el proveedor de servicios de .NET Core, para que se encargue de crear estos objetos y gestionar su ciclo de vida completo de forma automática.

Para ello, a nivel de constructor lo único que necesitaríamos en este caso es recibir una referencia hacia el proveedor de servicios que usaremos para obtener dependencias, como sigue:
public OrderController(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
Y hecho esto, en las acciones simplemente lo usaremos en los puntos donde necesitemos obtener una referencia a dependencias para utilizarlas desde nuestro código:
// Before:
...
using (var ctx = new ShoppingCartContext())
{
var userServices = new UserServices(ctx);
var userPreferences = await userServices.GetNotificationPreferencesAsync(CurrentUserId);
...
}

// Now:
...
var userServices = _serviceProvider.GetService<IUserServices>();
var userPreferences = await userServices.GetNotificationPreferencesAsync(CurrentUserId);
...
Recordad que para que esto sea posible debemos registrar y configurar apropiadamente los servicios en el contenedor de ASP.NET Core.
Como ventaja sobre la primera opción que hemos visto (instanciación manual), nuestro controlador estará desacoplado de sus dependencias y el código será más limpio porque todo el trabajo sucio de creación y liberación de objetos lo realizará el contenedor de servicios proporcionado por la infraestructura.

Eso sí, al igual que ocurría antes, seguiremos teniendo una clase cuyas dependencias no se manifiestan de forma explícita y clara, y para conocerlas habría que leer todo el código.

Asimismo, añadimos problemas derivados del uso de un service locator, como la posibilidad de que se produzcan errores en tiempo de ejecución, por ejemplo, si las dependencias requeridas en determinados escenarios no pueden ser satisfechas.

Inyección en parámetros de acciones

En tercer lugar, como una leve mejora respecto al punto anterior, es interesante saber que ASP.NET Core nos permite aplicar inyección de dependencias directamente como parámetros de las acciones.

Para ello, basta con decorar con [FromServices] los parámetros que deseamos inyectar desde el contenedor de servicios de ASP.NET Core, como en el siguiente ejemplo:
[HttpPost]
public Task<IActionResult> Submit(OrderViewModel orderViewModel,
[FromServices] IOrderServices orderServices,
[FromServices] IUserServices userServices,
[FromServices] IMapper mapper,
...
)
{
if(!ModelState.IsValid)
{
return View(orderViewModel);
}

var orderDto = _mapper.Map<OrderDto>(orderViewModel);
var orderResult = await orderServices.AddAsync(orderDto);
if(!orderResult.Success)
{
return RedirecToAction("OrderError", new { error = orderResult.Error }));
}
var userPreferences = await userServices.GetNotificationPreferencesAsync(CurrentUserId);
...
}
Bien, aunque básicamente seguimos teniendo los mismos problemas que antes, es cierto que las dependencias quedan un poco más a la vista porque forman parte de la firma de las acciones.

Por último, es interesante destacar que, obviamente, [FromServices] se puede usar en acciones de controladores que también reciben dependencias en el constructor, lo cual puede sernos de utilidad cuando queramos hilar muy fino en términos de rendimiento. Por ejemplo, podríamos hacer que un controlador reciba en su constructor las dependencias que usaremos de forma general en todas sus acciones, y luego añadir con [FromServices] las dependencias exclusivas de cada acción:
public class OrderController: Controller
{
...
public OrderController(IOrderServices orderServices, IUserServices userServices)
{
// We need these dependencies in all actions
_orderServices = orderServices;
_userServices = userServices;
}

public Task<IActionResult> Submit([FromServices] IPaymentServices paymentServices)
{
// We use payment services only when submitting an order
...
}
}

En resumen

En este post hemos visto fórmulas para consumir dependencias desde un controlador sin necesidad de que éstas nos sean suministradas en su constructor: instanciación directa, service locator e inyección de dependencias en acciones.

Estas técnicas pueden ser interesantes en determinados escenarios, pero recordad que si el constructor de un controlador recibe más parámetros de la cuenta, probablemente se trata de un smell que nos está avisando de la ruptura del Principio de Responsabilidad Única y de que estamos creando una clase demasiado compleja. Y como vimos en su momento, la solución a este escenario es refactorizar.

Publicado en Variable not found.

» Leer más, comentarios, etc...

Metodologías ágiles. De lo racional a la inspiración.

Si llevo Kanban, también llevo los principios ágiles

diciembre 17, 2018 10:59

Recientemente se está hablando de si kanban es una metodología ágil o no. Creo que la primera vez que me lo plantee fue al leer una serie de magníficos posts de Michael Sahota sobre la cultura de las organizaciones. NOTA: Es realmente curioso que un tema tan específico haya llegado a una revista como Forbes. Steve Denning, al que debeis seguir atentamente si no lo haceis ya, está "fusilando" las

» Leer más, comentarios, etc...

Blog Bitix

Monitorizar procesos que realizan lectura o escritura al almacenamiento en GNU/Linux

diciembre 17, 2018 10:00

GNU
linux
Firefox

Hoy día los precios de los SSD son asequibles y a cada mes que pasa su coste por GB se reduce de forma significativa, son mucho más rápidos que un disco duro mecánico y es una de las mejores mejoras que se le pueden hacer a un ordenador para aumentar el rendimiento si el problema es la tasa de transferencia del almacenamiento. El sistema se inicia mucho más rápido, en segundos en vez de minutos, y las aplicaciones también tardan mucho menos en cargarse. Cambiar a un SSD significa aumentar el rendimiento notablemente y parecer que se tiene equipo nuevo.

Aunque los SSD son muchísimo más rápidos que los discos duros mecánicos tradicionales tanto en lectura como en escritura adolecen de un tiempo de vida de unos 1000 ciclos de escritura por cada celda. Aunque 1000 ciclos de escritura pueden parecer pocos son más que suficientes para que el disco SSD y equipo se quede obsoleto antes de que pueda fallar según el ritmo al que avanza la tecnología. Aunque en un principio no debamos preocuparnos demasiado de la fiabilidad no está demás cuidarlo en lo posible.

Según Samsung la cantidad de datos que se pueden escribir en un 860 EVO y 970 EVO son según la capacidad 250, 500 GB y 1 TB de 150 TBW, 300 y 600 respectivamente (TBW = Terabytes escritos, 1 TB = 1000 GB). La esperanza de vida estimada antes de que se empiecen a producir fallos para el SSD de 250 GB es de de unos 20 años estimando unos 20 GB de escritura al día (150 TBW * 1000 GB/TB / (20 GB * 365 dias/año), en ese tiempo el equipo se quedará obsoleto y el coste del almacenamiento se habrá reducido significativamente.

Para cuidar el SSD hay que conocer la cantidad de datos que se están escribiendo en la unidad. En GNU/Linux es sencillo, el siguiente comando da la cantidad de datos en megabytes leídos y escritos desde que que se ha encendido el sistema.

En este caso se han escrito 326 MB en el dispositivo nvme0n1p2 que corresponde a la partición root en una hora de actividad del sistema realizando tareas ofimáticas y de navegación.

Datos escritos al almacenamiento desde el inicio del sistema

También es interesante conocer los datos escritos en la unidad en total desde su instalación, en realidad lo siguiente nos dará los GiB escritos desde la creación de la partición, si se han hecho varios particionados no será el total del tiempo de vida de la unidad. Al usar LVM on LUKS el nombre que Linux le a la unidad en mi caso es dm-1, según la configuración del sistema otro nombre que se le asigna es sda2. En esta captura de 129 GB.

Datos escritos a una partición

Después de comprar un Intel NUC junto con SSD e instalarle Arch Linux me he dado cuenta que de forma periódica, cada 5 o 10 segundos, parpadea la luz de actividad del disco duro (o simplemente almacenamiento al tener un SSD) sin hacer ninguna actividad salvo tener algunas aplicaciones abietas. He instalado iotop para descubrir el origen de esta actividad y he encontrado dos. Por un lado Firefox, varios procesos de él, y otro proceso del sistema jdb2-dm-1-8 que corresponde al journaling del sistema de archivos. La opción -a de iotop muestra la cantidad de E/S en la sesión por proceso y la opción –only solo aquellos que han realizado E/S.

Datos escritos por procesos del sistema

Investigando sobre los motivos de escritura de Firefox he encotrado algunos artículos y tres recomendaciones a cambiar en la configuración de Firefox para optimizar su uso en unidades SSD.

Las opciones a cambiar en la configuración son:

  • browser.sessionstore.interval: para recuperar las pestañas abiertas en caso de cierre inesperado Firefox las guarda en disco cada cierto tiempo, por defecto 15000 ms o 15 segundos que es un tiempo muy bajo y que ocasiona escrituras al disco constantemente.
  • browser.cache.disk.enable: para no realizar tŕafico de red en un futuro los recursos ya descargados como imágenes, archivos de estilo o javascript los almacena en una caché en el disco. Evita tráfico de red y mejora el rendimiento pero la caché ocupa espacio en disco y ocasiona escrituras. Cambiando el valor de esta propiedad a false se inhabilita la cache y las escrituras.
  • browser.cache.memory.capacity: para no perder la funcionalidad de la cache al menos en una sesión si se dispone de memoria RAM suficiente se puede activar una cache en memoria. Esta opción indica en KiB el espacio reservado para esa caché. Un valor de 307200 (300 * 1024) corresponde a 300 MiB de caché en memoria. Aún teniendo una cache de sesión y en memoria no he notado diferencia velocidad de carga en las páginas al navegar aún usando ADSL. Esa opción se ha de crear de tipo entero.

Estas opciones se cambian el la sección de configuración de Firefox introduciendo about:config en la barra de direcciones.

Configuración de Firefox optimizado para SSD

Con la configuración por defecto y cambiando estas opciones esta es la diferencia de los procesos de Firefox que causan E/S después de un tiempo de uso. Por defecto se observa que hay un proceso relacionado que debe estar relacionado con la caché a raíz de su nombre que escribe en el almacenamiento y cambiadas las opciones solo los procesos mozStorage escriben, estos procesos están relacionados con las cookies y almacenamiento local o bases de datos que los navegadores ofrecen a las páginas web pero los procesos relacionados con la caché han desaparecido. La cantidad de datos escritos con las opciones por defecto y cambiadas son significativamente menores en el mismo periodo de tiempo.

Dado que la caché ocasiona escrituras en el almacenamiento el proceso de journaling del sistema también realiza escrituras amplificando el problema. Con Firefox optimizado para SSD el proceso firefox [Cache2 I/O] desaparece y al escribir la sesión no cada 15 segundos Firefox deja de escribir de forma regular.

Entrada y salida de Firefox por defecto y optimizado para SSD

En el entorno de escritorio GNOME con la aplicación Monitor del sistema es posible ver la cantidad de datos escritos por un proceso en total y en tiempo real.

Monitor del sistema de GNOME

Dado que los SSD no hacen ruido no se ha pedido la alerta sonora del «rascar» del disco cuando algún programa está escribiendo o leyendo de forma intensiva y por ello quizá no nos demos cuenta de que algún programa está teniendo un mal comportamiento y esté escribiendo muchos datos al SSD, peor aún si el sistema ni siquiera dispone de este LED de actividad.

Para visualizar el uso del disco de forma constante en GNOME hay una extensión que hace de monitor del sistema que agregará en la barra superior un área de estado visible en todo momento en la que se puede ver la información del estado del sistema, entre esa información se puede visualizar la cantidad de lectura y escritura que está realizando al almacenamiento.

Extensión de GNOME de monitor del sistema

» Leer más, comentarios, etc...

Poesía Binaria

Entorno de pruebas para nuestra web en función de una cookie

diciembre 17, 2018 09:18

Hace unas semanas hablábamos de cómo hacer una redirección transparente en Apache en función de una cookie. Uno de los ejemplos que puse es el acceso a un entorno de test de nuestra aplicación. Un entorno de test al que podamos acceder nosotros como desarrolladores o incluso se lo podamos enseñar a un cliente final sin afectar al funcionamiento normal de la web (que por otro lado está funcionando).
A veces, podemos crear una dirección paralela tipo test.miweb.com, pero otras veces no es posible. Y decirle a todo el mundo que añada una dirección IP a su archivo hosts, y que nos haga caso, es complicado. Pensemos en aplicaciones que tienen una fuerte dependencia del host al que están vinculada. Un ejemplo sencillo es WordPress, muchas veces nos encontramos con plugins o temas que, tanto en su configuración como en otras tablas almacenan el host y se complicado estar todo el rato haciendo cambios en la base de datos. Pero no solo WordPress, esta necesidad nos puede surgir con multitud de aplicaciones.

Un poco de historia (personal)

Todo empezó buscando una manera de cambiar el DocumentRoot de un VirtualHost en función de una cookie. Aunque la propiedad DocumentRoot no soporta condiciones ni se puede cambiar tan fácilmente. Lo que debemos hacer es una redirección con proxy en función de una condición. Esta redirección puede ser a la misma máquina, o a otra diferente, incluso máquinas virtuales o contenedores docker, siempre que tengamos acceso por red. Estas máquinas también pueden ser privadas. Por lo que nuestro servidor principal puede tener un segundo interfaz de red que esté conectado a otro servidor y hacer el proxy hacia ese segundo servidor.

Así que, si en este momento tenemos suficiente con la máquina donde tenemos el servidor web principal podemos utilizar otro puerto en el que también levantaremos el servidor web. A mí me gusta el 180, por poner un puerto. Ese puerto debemos mantenerlo privado, para que el acceso sea solo dentro del host local y no esté expuesto a Internet (cosa que no queremos). Podemos elegir, si la página de pruebas está en otro host, perfecto. Si queremos que dicha web de pruebas esté en el mismo host, seguid leyendo.

Escuchando por el puerto 180

Lo primero es que Apache escuche en el puerto 180, aunque podríamos utilizar otro servidor web como Nginx, lighthttpd, etc. Aunque si lo que queremos es utilizarlo como entorno de test, es conveniente que utilicemos siempre el mismo servidor web (por lo que pueda pasar).

Aunque nuestra web la sirvamos por HTTPs. La redirección no hace falta hacerla también por HTTPs. Ya que la conexión segura se seguirá haciendo entre Apache y el usuario final que pide la web, y luego Apache hará una conexión no segura con él mismo para acceder a la web de pruebas. Puede que, según nuestros contratos con el cliente, o según lo paranoicos que seamos con la seguridad, nos interese también cifrar esta comunicación interna, pero eso ya es cuestión de gustos.

En Apache, la configuración puertos suele estar en /etc/apache2/httpd.conf, /etc/apache2/apache2.conf, /etc/apache2/ports.conf o incluso los archivos pueden estar situados en /etc/httpd/ . En mi caso, en la instalación en Ubuntu, suele estar en /etc/apache2/ports.conf, y finalmente lo podemos dejar así:

1
2
3
4
5
6
7
8
9
Listen 80
Listen 180
<IfModule ssl_module>
        Listen 443
</IfModule>

<IfModule mod_gnutls.c>
        Listen 443
</IfModule>

O incluso podríamos cercar más el acceso así, haciendo que solo escuchemos en el host local:

1
Listen 127.0.0.1:180

Ahora, nuestro Virtualhost de la web quedaría así (lo pongo completo, con SSL y todo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<VirtualHost *:80>
     ServerName www.miweb.com
     ServerAlias miweb.com

     ServerAdmin administrador@miweb.com
     DocumentRoot /var/www/miweb.com/www

     RewriteEngine On
     RewriteCond %{HTTPS} off
     RewriteCond %{REQUEST_URI} "!/.well-known/acme-challenge/"
     RewriteRule (.*) https://www.%{HTTP_HOST}%{REQUEST_URI} [NE,L,R=301]

     ErrorLog ${APACHE_LOG_DIR}/error.log
     CustomLog ${APACHE_LOG_DIR}/access.log ombined

# Include conf-available/php7.0-fpm.conf

</VirtualHost>

<VirtualHost *:180>
     ServerName www.miweb.com

     ServerAlias miweb.com

     DocumentRoot /var/www/miweb.com_testing/www

     ErrorLog ${APACHE_LOG_DIR}/error.log
     CustomLog ${APACHE_LOG_DIR}/access.log combined
     Include conf-available/php7.0-fpm.conf
</VirtualHost>

<IfModule mod_ssl.c>
     <VirtualHost _default_:443>

          ServerName www.miweb.com
          ServerAlias miweb.com

          ServerAdmin administrador@miweb.com
          DocumentRoot /var/www/miweb.com/www

          ProxyPreserveHost On
          RewriteEngine On

          RewriteCond %{HTTP_COOKIE} cookie=([^;]+)
          RewriteCond %1 ^pruebas$
          RewriteRule ^/(.*) http://127.0.0.1:180/$1 [P,L]

          ErrorLog ${APACHE_LOG_DIR}/error.log
          CustomLog ${APACHE_LOG_DIR}/access.log combined

          Include conf-available/php7.0-fpm.conf

          SSLEngine on

          SSLCertificateFile /etc/letsencrypt/live/miweb.com/cert.pem
          SSLCertificateKeyFile /etc/letsencrypt/live/miweb.com/privkey.pem

          SSLCertificateChainFile /etc/letsencrypt/live/miweb.com/fullchain.pem

          <FilesMatch "\.(cgi|shtml|phtml|php)$">
               SSLOptions +StdEnvVars
          </FilesMatch>
          <Directory /usr/lib/cgi-bin>
               SSLOptions +StdEnvVars
          </Directory>
     </VirtualHost>
</IfModule>

Posibles cambios

Al estar utilizando otro VirtualHost, en este caso, el del puerto 180, podemos utilizar otra versión de PHP (En este caso, la web usa PHP), por supuesto otro DocumentRoot diferente, por lo que podríamos tener otra rama del repositorio de nuestra aplicación y la ésta podrá tener configuración de caché/logs/base de datos diferente a la aplicación principal.

Este sistema nos podría ayudar mucho a la hora de probar nuestra aplicación web en un entorno real o incluso con datos reales (si es posible), justo antes de desplegarla definitivamente.

Seguridad

Como dije antes, el puerto 180 no debe estar expuesto al exterior. No nos interesa que personas no autorizadas entren a nuestro sistema de pruebas. Podríamos tener contraseñas débiles, datos no reales o información de depuración activada que pueda dar pistas para atacar nuestra aplicación. Este puerto lo podemos bloquear desde nuestro proveedor de servicio, o desde la misma máquina, aplicando una restricción con UFW o desde iptables, así:

iptables -A INPUT -i eth0 -p tcp --dport 180 -j DROP

Ahora, debemos ser cuidadosos con las personas a las que les establecemos los valores de la cookie, porque podrán entrar a una zona privada de nuestro sistema. Sería conveniente no tener el acceso activado constantemente, sino activarlo solo cuando sea necesario, o poner también reglas por IP. Yo también, en los servicios donde utilizo esta técnica utilizo cookies como:
accesodepruebas=hn8JV9V1IfJylvQCmo+VUJb8ENgEIs/cbA8pWa7j1mE

La cadena aleatoria la podemos obtener así:

openssl rand -base64 32

De manera que sea muy muy difícil para alguien hallar el valor adecuado de la cookie.

Foto principal: unsplash-logoPietro De Grandi

The post Entorno de pruebas para nuestra web en función de una cookie appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 342

diciembre 17, 2018 07:55

Enlaces interesantes Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en: www.variablenotfound.com.

» Leer más, comentarios, etc...

Una sinfonía en C#

Azure DevOps: ¿Cómo consumir un feed nuget de Azure DevOps en nuestra app local?

diciembre 16, 2018 10:14

Dentro de la característica de artefactos de Azure DevOps podemos crear nuestros propios feeds de paquetes nuget para organizar nuestros componentes y reutilizarlos.

Acceder al feed

La pregunta es cómo hacer para desde nuestro entorno local Visual Studio o Visual Studio Code o mejor dicho el compilador de .NET pueda acceder al feed.

Para esto necesita como mínimo conocer la URL del feed, esto se obtiene fácilmente desde Azure DevOps, vamos al feed y presionando el botón “Connect to feed” vemos la URL.

 

image

Obtener credenciales de acceso al feed.

Con la URL mucho no vamos a poder hacer si no tenemos permisos de acceso, dentro de Azure DevOps esto se logra creando tokens de acceso a nivel usuario. Es por esto vamos directamente a la configuración de seguridad del usuario.

image

Y vamos a la parte de security tokens.

image

En la sección “Personal access token” seleccionamos “New Token”, definimos nombre y la duración y lo más importante, el scope.

El scope nos permite definir para qué puede ser usado el token y solo para eso, en este caso le daremos un único scope, que será Packaging.Read.

Por defecto este scope no se ve por defecto hasta que hagamos click sobre el link debajo que dice “Show all”.

Ventaja de los tokens:

  • Usar tokens en lugar de usar el password tiene varias ventajas, vamos a enumerarlas
  • Podemos usarlo en un entorno sin poner en riesgo el password.
  • Tienen un acceso limitado a los recursos, por ejemplo los feeds.
  • Tienen un vencimiento definido, con lo cual si lo perdemos lo borramos y listo.

Paso final, crear el Nuget.config

Con estos datos lo último que tenemos que hacer es ponerlos en un archivo que colocaremos a nivel de nuestra solución, el formato del archivo es el siguiente (es un xml):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="Azure" value="https://pkgs.dev.azure.com/leomicheloni/_packaging/MyNuget/nuget/v3/index.json" />    
  </packageSources>
  <packageSourceCredentials>
    <Azure>
      <add key="Username" value="mail@mail.com" />
      <add key="ClearTextPassword" value="prrryxncm6drvvruhmrph3rm5jiwrpp73viemx4n3kdo6dxwzlita" />
    </Azure>
  </packageSourceCredentials>  
</configuration>

Pueden descargar un ejemplo de un Nuget.config desde acá.

Y con esto tenemos acceso a nuestro feed, nos leemos.

Leonardo.

» Leer más, comentarios, etc...

Adrianistán

Advent of Code 2018: segunda semana

diciembre 16, 2018 11:00

Segunda semana del Advent of Code. En esta semana ya hemos tenido algunos problemas muy interesantes. Intentaré comentarlos de la mejor manera posible. Todo el código está aquí y en este otro post comenté mis soluciones de la primera semana.

Día 8

El día 8 se nos propone un problema de grafos también. Básicamente se nos define un árbol, donde cada nodo puede tener hijos y metadatos. En la primera parte nos piden sumar todos los metadatos.

Aquí al contrario que en el día 7, no voy a usar networkx. Era más difícil adaptar networkx al problema que hacer el árbol a mano. Uno de los puntos complicados de este problema es el parseo de la entrada, que hice de forma recursiva. Cada nodo es un diccionario con las propiedades tamaño, una lista de metadatos y una lista de nodos hijo.

En la segunda parte se define el concepto de valor de nodo y como calcularlo. También es bastante sencillo de implementar. Finalmente hacemos un recorrido por el árbol de tipo primero en anchura (BFS) con una deque de Python.

from collections import deque

def read_node(start,numbers):
    length = 2
    child_nodes = numbers[start]
    metadata_entries = numbers[start+1]
    children = list()
    while child_nodes > 0:
        child_node = read_node(start+length,numbers)
        children.append(child_node)
        length += child_node["length"]
        child_nodes -= 1
    metadata = list()
    while metadata_entries > 0:
        metadata.append(numbers[start+length])
        length += 1
        metadata_entries -= 1
    node = dict([("length",length),("metadata",metadata),("children",children)])
    return node
        


def read_file(file):
    with open(file) as f:
        line = f.readline()
    numbers = [int(x) for x in line.split()]
    G = read_node(0,numbers)
    return G

def node_value(N):
    if len(N["children"]) == 0:
        return sum(N["metadata"])
    else:
        s = 0
        for i in N["metadata"]:
            if i-1 &lt; len(N["children"]):
                s += node_value(N["children"][i-1])
        return s
        

def day8(file):
    G = read_file(file)

    to_visit = deque()
    to_visit.append(G)
    metadata_sum = 0
    while len(to_visit) > 0:
        N = to_visit.popleft()
        metadata_sum += sum(N["metadata"])
        to_visit.extend(N["children"])
    print("METADATA SUM: %d" % metadata_sum)
    print("NODE VALUE: %d" % node_value(G))

Día 9

Este día fue muy interesante. Se nos explica un juego, que consiste en ir añadiendo canicas en una circunferencia y cuando el número de canica que añadimos es múltiplo de 23, obtenemos puntos y quitamos una canica 7 puestos por detrás.

Aquí tuve una mala decisión de diseño ya que al principio quise hacer esto con una lista de Python (el equivalente a vector en otros lenguajes de programación). La idea era sencilla y funcionaba hasta que llegó la parte 2. La parte 2 te pedría calcular los puntos teniendo en cuenta 100 veces más canicas. Esto fue un grave problema para mi código. Calculo que tardaría 6 horas en calcularlo, pero antes optimicé. La optimización consistía en usar una lista circular doblemente enlazada. ¿Esto qué es? Se trata de una lista enlazada, doblemente, porque cada nodo tiene referencia al elemento siguiente y al anterior. Y es circular porque ambos extremos están unidos. Esto permite las inserciones y borrados en O(1). Además los movimientos relativos (en este problema todos son así) son extremadamente sencillos. La implementación de esta estructura de datos en Python es muy sencilla (en otros lenguajes es más complicado). No me molesté en hacer funciones que me hiciesen sencilla la vida y las conexiones/desconexiones las hago a mano directamente en el código del problema.

from collections import defaultdict

class Marble:
    def __init__(self,value,left=None,right=None):
        self.value = value
        self.left = left
        self.right = right
        if self.left == None:
            self.left = self
        if self.right == None:
            self.right = self

def day9(PLAYERS,LAST_MARBLE):
    SCORE = defaultdict(lambda: 0)
    player = 0
    marble = 0
    current_marble_pos = 0
    current_marble = None

    while marble &lt;= LAST_MARBLE:
        if marble > 0 and marble % 23 == 0:
            SCORE[player] += marble
            pivote = current_marble.left.left.left.left.left.left.left
            SCORE[player] += pivote.value
            pivote.left.right = pivote.right
            pivote.right.left = pivote.left
            current_marble = pivote.right

        else:
            if current_marble == None:
                current_marble = Marble(marble)
            else:
                current_marble = Marble(marble,current_marble.right,current_marble.right.right)
                current_marble.left.right = current_marble
                current_marble.right.left = current_marble
        player += 1
        player = player % PLAYERS
        marble += 1
    return max(SCORE.values())

Curiosamente, en la propia librería de Python deque tiene una operación llamada rotate que permite hacer este problema en poquísimas líneas y de forma muy eficiente. Pero desconocía la existencia de esa función (que lo que hace es mover la “cabeza” de la lista enlazada que es deque).

Día 10

Este problema es muy interesante. Se nos da una serie de puntos que van moviéndose por la pantalla. En un determinado momento estos puntos se juntan y forman un mensaje en pantalla.

Aquí lo interesante no es mover los puntos, eso es trivial, simplemente es sumar la velocidad cada vez las coordenadas. Lo interesante es saber cuando parar. Existen varias ideas:

  • Revisión humana de cada iteración
  • Comprobar que no haya puntos separados del resto (con grafos)
  • Comprobar que el área de concentración de puntos es mínima

Y alguna más. Para el ejemplo la primera idea servía. Pero en la prueba real, era más complicado. A mí se me ocurrió la tercera opción, la cuál es bastante eficiente. En cada iteración calculamos el área que contiene a todos los puntos, cuando ese área ya no se reduce más, hemos llegado al mensaje.

import re

def read_file(file):
    stars = list()
    p = re.compile("position=&lt;([ -][0-9]+), ([ -][0-9]+)> velocity=&lt;([ -][0-9]+), ([ -][0-9]+)>")
    with open(file) as f:
        lines = f.readlines()
    for line in lines:
        m = p.match(line.strip())
        try:
            pos_x = int(m.group(1))
        except:
            print(line)
        pos_y = int(m.group(2))
        vel_x = int(m.group(3))
        vel_y = int(m.group(4))
        stars.append([pos_x,pos_y,vel_x,vel_y])
    return stars
def print_stars(stars):
    stars = sorted(stars,key=lambda x: x[0],reverse=True)
    min_width = stars[-1][0]
    max_width = stars[0][0]
    min_height = min(stars,key=lambda x: x[1])[1]
    max_height = max(stars,key=lambda x: x[1])[1]
    s = str()
    for j in range(min_height,max_height+1):
        p = [star for star in stars if star[1] == j]
        for i in range(min_width,max_width+1):
            if len(p) == 0:
                s += "."
            else:
                if any(map(lambda star: star[0] == i and star[1] == j,p)):
                    s += "#"
                else:
                    s += "."
        s += "\n"

    return s

def step(stars):
    a = map(lambda x: [x[0]+x[2],x[1]+x[3],x[2],x[3]],stars)
    return list(a)

# LA RESPUESTA CORRECTA TIENE AREA MINIMA
def area(stars):
    stars = sorted(stars,key=lambda x: x[0], reverse=True)
    min_width = stars[-1][0]
    max_width = stars[0][0]
    min_height = min(stars,key=lambda x: x[1])[1]
    max_height = max(stars,key=lambda x: x[1])[1]
    area = (max_width-min_width)*(max_height-min_height)
    return area

def day10(file):
    stars = read_file(file)
    a = area(stars)
    steps = 0
    while area(step(stars)) &lt; a:
        stars = step(stars)
        steps += 1
        a = area(stars)
    print_stars(stars)
    print(steps)

La parte de dibujado me costó y ahí tuve un fallo que me costó media hora aproximadamente en resolver. Una mejor opción, pero que no se me ocurrió, hubiese sido usar Pillow y crear una imagen. Es mucho más fácil que dibujar sobre una terminal (y posiblemente más rápido).

Día 11

Para este problema hay 3 posibles algoritmos. En la primera parte nos piden que de una matriz extraigamos el cuadrado de 3×3 con mayor valor. La matriz hay que construirla pero es trivial. Yo decido usar un diccionario, con clave la tupla de coordenadas. Vamos recorriendo todas las posiciones y calculamos el valor. Ahora para buscar el cuadrado, simplemente vamos probando todos los posibles cuadrados.

En la segunda parte nos dicen que bsuquemos el cuadrado máximo pero el tamaño puede ser cualquiera. Aquí con la fuerza bruta ya tarda demasiado. Mi solución fue usar programación dinámica, para ello la clave pasa a tener un valor más, el tamaño del cuadrado. Cuando creamos la tabla estamos asignando valor al cuadrado 1×1 de posición X,Y. Representado es la tupla (x,y,1). Según vamos avanzando hasta 300×300 vamos guardando los resultados intermedios, de modo que podamos reutilizarlos. Por ejemplo, el valor de (x,y,4) solamente es la suma de (x,y,2), (x+2,y,2), (x,y+2,2) y (x+2,y+2,2). Evidentemente esto solo funciona en los tamaños pares. En los tamaños impares decidí coger el cuadrado de dimensión inmediatamente menor y calcular los laterales con los cuadrados de tamaño 1. Este sistema funciona mucho mejor que la fuerza bruta pero es lento. Los profesionales usaron el algoritmo Summed Area Table (del que desconocía su existencia). Este algoritmo es el óptimo para este problema.


def generate_fuel(x,y,idg):
    fuel = (((x+10)*y)+idg)*(x+10)
    fuel %= 1000
    fuel = (fuel // 100) - 5
    return fuel

def generate_table(idg):
    fuel = {(x,y,size):0 for x in range(1,301) for y in range(1,301) for size in range(1,301)} 
    for x in range(1,301):
        for y in range(1,301):
            fuel[(x,y,1)] = generate_fuel(x,y,idg)
    return fuel

def find_best(fuel):
    max_point = [-1,-1]
    max_score = -1
    for x in range(1,301):
        for y in range(1,301):
            if x+3 > 301 or y+3 > 301:
                continue
            score = fuel[(x,y,1)]+fuel[(x+1,y,1)]+fuel[(x+2,y,1)]+fuel[(x,y+1,1)]+fuel[(x+1,y+1,1)]+fuel[(x+2,y+1,1)]+fuel[(x,y+2,1)]+fuel[(x+1,y+2,1)]+fuel[(x+2,y+2,1)]
            if score > max_score:
                max_score = score
                max_point = [x,y]
    return max_point[0],max_point[1]

def find_best_any_size(fuel):
    max_score = -1
    max_point = [-1,-1,-1]
    for size in range(2,300+1):
        for x in range(1,301):
            for y in range(1,301):
                if x+size > 301 or y+size > 301:
                    continue
                if size % 2 == 0:
                    mid = size // 2
                    fuel[(x,y,size)] = fuel[(x+mid,y,mid)]+fuel[(x,y+mid,mid)]+fuel[(x+mid,y+mid,mid)]+fuel[(x,y,mid)]
                else:
                    fuel[(x,y,size)] = fuel[(x,y,size-1)]
                    for i in range(x,x+size-1):
                        fuel[(x,y,size)] += fuel[(i,y+size-1,1)]
                    for j in range(y,y+size-1):
                        fuel[(x,y,size)] += fuel[(x+size-1,j,1)]
                    fuel[(x,y,size)] += fuel[(x+size-1,y+size-1,1)]
                score = fuel[(x,y,size)]
                if score > max_score:
                    max_score = score
                    max_point = [x,y,size]
    return max_point[0],max_point[1],max_point[2]


def day11():
    fuel = generate_table(1133)
    x,y = find_best(fuel)
    print("BEST POINT: %d,%d" % (x,y))
    x,y,size = find_best_any_size(fuel)
    print("BEST POINT ANY SIZE: %d,%d,%d" % (x,y,size))

if __name__ == "__main__":
    day11()

Día 12

El día 12 me trajo recuerdos de un algoritmo con el que me peleé mucho, el denominado HashLife. El problema es un autómata celular unidimensional. Las reglas vienen dadas como patrones. La única diferencia es que hay que guardar su posición para luego calcular un número. La primera parte es bastante sencilla.

import re
from collections import defaultdict

def read_file(file):
    rules = defaultdict(lambda: ".")
    rule_prog = re.compile("([.#]+) => ([.#])")
    with open(file) as f:
        lines = f.readlines()
    state = lines[0].split(": ")[1].strip()
    for line in lines[2:]:
        m = rule_prog.match(line.strip())
        rules[m.group(1)] = m.group(2)
    return state,rules

def parse_state(pots):
    state = dict()
    for i,p in enumerate(pots):
        state[i] = p
    return state

def find(rules,current):
    if current in rules:
        return rules[current]
    else:
        size = len(current)
        mid = size // 2
        left = find(rules,current[0:mid])
        right = find(rules,current[mid:])
        rules[current] = left + right
        return rules[current]


def iter(state,rules):
    new_state = dict()
    xmin = min(state.keys())
    xmax = max(state.keys())
    for x in range(xmin-2,xmax+3):
        current = ("%c%c%c%c%c" % (
                    state.get(x-2,"."),
                    state.get(x-1,"."),
                    state.get(x,"."),
                    state.get(x+1,"."),
                    state.get(x+2,".")
                    ))
        new = rules[current]
        if new == "#" or xmin &lt;= x &lt;= xmax:
            new_state[x] = new
    return new_state

def sum_pots(state):
    n = 0
    for pot in state:
        if state[pot] == "#":
            n += pot
    return n

def print_state(state):
    xmin = min(state.keys())
    xmax = max(state.keys())
    s = str("XMIN %d : " % xmin)
    for x in range(xmin-2,xmax+3):
        s += state.get(x,".")
    print(s)


def day12(file):
    state,rules = read_file(file)
    state = parse_state(state)
    for i in range(20):
        print_state(state)
        state = iter(state,rules)
    print_state(state)
    n = sum_pots(state)
    print(n)

if __name__ == "__main__":
    day12("input.txt")

La segunda parte nos pedía lo mismo pero para el número ¡50000000000! Inmediatamente pensé en optimizarlo de forma similar a HashLife. La idea consiste en almacenar patrones mayores a los de las reglas (que son todos de tamaño 5), para poder evitar cálculos innecesarios.Además añadí un recolector de basura para ir eliminando por la izquierda las celdas inútiles.

No obstante, y aunque es muchísimo más eficiente, sigue sin ser capaz de procesar tal bestialidad de número en un tiempo razonable.

Y he aquí lo que me ha cabreado, porque no he podido sacarlo. A partir de cierto momento, el dibujo siempre es el mismo pero desplazándose a la derecha. De modo que el valor del siguiente paso siempre es la suma de una constante. Finalmente modifiqué el código para que buscase una situación en la que el número fuese resultado de una suma de una constante. Una vez hecho eso, calcula con una multiplicación lo que valdría cuando llegase a 50000000000.

import re
from collections import defaultdict

XMIN = -2

def find(rules,current):
    if len(current) &lt; 5:
        return ""
    if current in rules:
        return rules[current]
    elif len(current) == 5:
        return "."
    else:
        size = len(current)
        left=find(rules,current[0:size-1])
        right=find(rules,current[size-5:])
        rules[current] = left+right
        return rules[current]

def read_file(file):
    rules = defaultdict(lambda: ".")
    rule_prog = re.compile("([.#]+) => ([.#])")
    with open(file) as f:
        lines = f.readlines()
    state = lines[0].split(": ")[1].strip()
    for line in lines[2:]:
        m = rule_prog.match(line.strip())
        rules[m.group(1)] = m.group(2)
    return state,rules


def print_state(state):
    print(state)

def sum_pots(state):
    n = 0
    for i,c in enumerate(state):
        if c == "#":
            n += i + XMIN
    return n

if __name__ == "__main__":
    state,rules = read_file("input.txt")
    XMAX = len(state)+1
    state = "..%s.." % state
    sums = list()
    i = 0
    while len(sums) &lt; 3 or sums[-1]-sums[-2] != sums[-2]-sums[-3]:
        state = find(rules,"..%s.." % state)
        if state[0] == "." and state[1] == "." and state[2] == "." and state[3] == ".":
            state = state[2:]
            XMIN += 2
        if state[0] == "#" or state[1] == "#":
            state = "..%s" % state
            XMIN -= 2
        if state[-1] == "#" or state[-2] == "#":
            state = "%s.." % state
        sums.append(sum_pots(state))
        i += 1
    diff = sums[-1]-sums[-2]
    missing = 50000000000 - i
    n = missing*diff + sums[-1]

    print(n)

Y con esto pude finalmente calcular el resultado.

Día 13

El día 13 teníamos unas vías de tren. En estas vías había unos trenecitos que se desplazaban siguiendo unas normas. El objetivo en la primera parte era conocer el donde se producía el primer choque.

from Pillow import Image

XMAX = 0
YMAX = 0
STEP = 0

nextDirection = dict()
nextDirection["start"] = "left"
nextDirection["left"] = "center"
nextDirection["center"] = "right"
nextDirection["right"] = "left"

def relative_direction(original,movement):
    print("CROSS")
    if movement == "center":
        return original
    if original == "v":
        if movement == "left":
            return ">"
        else:
            return "&lt;"
    elif original == ">":
        if movement == "left":
            return "^"
        else:
            return "v"
    elif original == "^":
        if movement == "left":
            return "&lt;"
        else:
            return ">"
    else:
        if movement == "left":
            return "v"
        else:
            return "^"

def day13(file):
    global XMAX
    global YMAX
    global STEP
    plano = dict()
    carts = list()
    with open(file) as f:
        lines = f.readlines()
    YMAX = len(lines)
    XMAX = len(lines[0])
    for y,line in enumerate(lines):
        for x,char in enumerate(line):
            # SI HAY UN CARRITO, DEDUCIR TIPO DE VIA
            if char == "^" or char == "v" or char == "&lt;" or char == ">":
                if (x,y-1) in plano:
                    plano[(x,y)] = "|"
                else:
                    plano[(x,y)] = "-"
                carts.append([x,y,char,"left"])
            else:
                plano[(x,y)] = char
    
    end = False
    while not end:
        carts.sort(key=lambda x: x[1])
        carts.sort(key=lambda x: x[0])

        for cart in carts:
            # CHECK CRASH
            for crt in carts:
                if cart[0] == crt[0] and cart[1] == crt[1] and id(cart) != id(crt):
                    print("CRASH AT %d-%d" % (cart[0],cart[1]))
                    end = True
            try:
                x = cart[0]
                y = cart[1]
                print(cart)
                if cart[2] == ">":
                    if plano[(x+1,y)] == "/":
                        cart[2] = "^"
                    elif plano[(x+1,y)] == "\\":
                        cart[2] = "v"
                    elif plano[(x+1,y)] == "+":
                        cart[2] = relative_direction(cart[2],cart[3])
                        cart[3] = nextDirection[cart[3]]
                    cart[0] += 1
                elif cart[2] == "&lt;":
                    if plano[(x-1,y)] == "/":
                        cart[2] = "v"
                    elif plano[(x-1,y)] == "\\":
                        cart[2] = "^"
                    elif plano[(x-1,y)] == "+":
                        cart[2] = relative_direction(cart[2],cart[3])
                        cart[3] = nextDirection[cart[3]]
                    cart[0] -= 1
                elif cart[2] == "^":
                    if plano[(x,y-1)] == "/":
                        cart[2] = ">"
                    elif plano[(x,y-1)] == "\\":
                        cart[2] = "&lt;"
                    elif plano[(x,y-1)] == "+":
                        cart[2] = relative_direction(cart[2],cart[3])
                        cart[3] = nextDirection[cart[3]]
                    cart[1] -= 1
                elif cart[2] == "v":
                    print()
                    if plano[(x,y+1)] == "/":
                        cart[2] = "&lt;"
                    elif plano[(x,y+1)] == "\\":
                        cart[2] = ">"
                    elif plano[(x,y+1)] == "+":
                        cart[2] = relative_direction(cart[2],cart[3])
                        cart[3] = nextDirection[cart[3]]
                    cart[1] += 1
            except:
                breakpoint()
        STEP += 1
        print_train(plano,carts)

def print_train(plano,carts):
    im = Image.new("RGB",(XMAX,YMAX))
    for x,y in plano:
        if plano[(x,y)] != " ":
            im.putpixel((x,y),(255,255,255))
        if plano[(x,y)] == "+":
            im.putpixel((x,y),(120,120,120))
    for cart in carts:
        if cart[2] == ">":
            im.putpixel((cart[0],cart[1]),(255,0,0))
        elif cart[2] == "&lt;":
            im.putpixel((cart[0],cart[1]),(0,255,0))
        elif cart[2] == "^":
            im.putpixel((cart[0],cart[1]),(0,0,255))
        else:
            im.putpixel((cart[0],cart[1]),(0,120,120))

    im.save("train-%d.png" % STEP,"PNG")

if __name__ == "__main__":
    day13("input_net.txt")

Para esta parte ya decidí usar Pillow para mostrar la salida. Esto fue muy útil para la depuración. Por lo demás, volví a usar un diccionario para guardar coordenadas. De hecho, no se guardan las casillas que no son vías. Las vías se guardan en ese diccionario y los carritos en una lista. El problema define que los trenecito que se mueven antes son los que están más arriba y en caso de empate, los que están más a la izquierda. Esto es muy sencillo de hacer en Python ya que la ordenación del método sort es estable, lo que quiere decir, que en caso de empate, se respeta el orden original. En cada trenecito se guarda su coordenada, su dirección (hacia el sitio donde se va a mover en el siguiente turno) y su estado para la elección de dirección en los cruces.

La segunda parte pedía que ante una situación de muchos trenecitos, ¿en qué posición quedaba el último carrito? Esto es algo más complicado. Aquí fui bastante tonto y cometí un error al borrar los carritos de la lista, que hacía que todo se estropease. La manera correcta de hacerlo es anotar en una lista que trenecitos hay que quitar y una vez haya finalizado la iteración se eliminan los trenecitos.

from Pillow import Image

XMAX = 0
YMAX = 0
STEP = 0

nextDirection = dict()
nextDirection["start"] = "left"
nextDirection["left"] = "center"
nextDirection["center"] = "right"
nextDirection["right"] = "left"

def relative_direction(original,movement):
    if movement == "center":
        return original
    if original == "v":
        if movement == "left":
            return ">"
        else:
            return "&lt;"
    elif original == ">":
        if movement == "left":
            return "^"
        else:
            return "v"
    elif original == "^":
        if movement == "left":
            return "&lt;"
        else:
            return ">"
    else:
        if movement == "left":
            return "v"
        else:
            return "^"

def day13(file):
    global XMAX
    global YMAX
    global STEP
    plano = dict()
    carts = list()
    with open(file) as f:
        lines = f.readlines()
    YMAX = len(lines)
    XMAX = len(lines[0])
    for y,line in enumerate(lines):
        for x,char in enumerate(line):
            # SI HAY UN CARRITO, DEDUCIR TIPO DE VIA
            if char == "^" or char == "v" or char == "&lt;" or char == ">":
                if (x,y-1) in plano:
                    plano[(x,y)] = "|"
                else:
                    plano[(x,y)] = "-"
                carts.append([x,y,char,"left"])
            else:
                plano[(x,y)] = char
    
    end = False
    while len(carts) != 1:
        carts.sort(key=lambda x: x[1])
        carts.sort(key=lambda x: x[0])  
        print_train(plano,carts)
        remove = list()
        i = 0
        while i &lt; len(carts):
            cart = carts[i]
            x = cart[0]
            y = cart[1]
            if cart[2] == ">":
                if plano[(x+1,y)] == "/":
                    cart[2] = "^"
                elif plano[(x+1,y)] == "\\":
                    cart[2] = "v"
                elif plano[(x+1,y)] == "+":
                    cart[2] = relative_direction(cart[2],cart[3])
                    cart[3] = nextDirection[cart[3]]
                cart[0] += 1
            elif cart[2] == "&lt;":
                if plano[(x-1,y)] == "/":
                    cart[2] = "v"
                elif plano[(x-1,y)] == "\\":
                    cart[2] = "^"
                elif plano[(x-1,y)] == "+":
                    cart[2] = relative_direction(cart[2],cart[3])
                    cart[3] = nextDirection[cart[3]]
                cart[0] -= 1
            elif cart[2] == "^":
                if plano[(x,y-1)] == "/":
                    cart[2] = ">"
                elif plano[(x,y-1)] == "\\":
                    cart[2] = "&lt;"
                elif plano[(x,y-1)] == "+":
                    cart[2] = relative_direction(cart[2],cart[3])
                    cart[3] = nextDirection[cart[3]]
                cart[1] -= 1
            elif cart[2] == "v":
                if plano[(x,y+1)] == "/":
                    cart[2] = "&lt;"
                elif plano[(x,y+1)] == "\\":
                    cart[2] = ">"
                elif plano[(x,y+1)] == "+":
                    cart[2] = relative_direction(cart[2],cart[3])
                    cart[3] = nextDirection[cart[3]]
                cart[1] += 1

            for crt in carts:
                if cart[0] == crt[0] and cart[1] == crt[1] and id(cart) != id(crt):
                    remove.append(cart)
                    remove.append(crt)
            i += 1

        for i in remove:
            carts.remove(i)
        STEP += 1
    print("Remaining cart at %d-%d" % (carts[0][0],carts[0][1]))

def print_train(plano,carts):
    im = Image.new("RGB",(XMAX,YMAX))
    for x,y in plano:
        if plano[(x,y)] != " ":
            im.putpixel((x,y),(255,255,255))
        if plano[(x,y)] == "+":
            im.putpixel((x,y),(120,120,120))
    for cart in carts:
        if cart[2] == ">":
            im.putpixel((cart[0],cart[1]),(255,0,0))
        elif cart[2] == "&lt;":
            im.putpixel((cart[0],cart[1]),(0,255,0))
        elif cart[2] == "^":
            im.putpixel((cart[0],cart[1]),(0,0,255))
        else:
            im.putpixel((cart[0],cart[1]),(0,120,120))

    im.save("train-%05d.png" % STEP,"PNG")
    #BUILD VIDEO
    #ffmpeg -framerate 10 -i 'train-%05d.png' -c:v libx264 -vf scale=1000x1000:flags=neighbor -r 30 -pix_fmt yuv420p train.mp4


if __name__ == "__main__":
    day13("input.txt")

Aprovechando las imágenes decidí generar una visualización de este problema. ¡Disfrutad los 20 minutos de puntitos moverse!

Día 14

El enunciado del día 14 es algo enrevesado pero básicamente se va generando una cadena de números ad infinitum. Tenemos que buscar los 10 elementos después de N posición. La solución de la parte 1 es bastante sencilla cuando se entiende el problema y se pueden usar sin problema listas normales de Python. En la parte 2 se pide lo contrario. Dado un patrón, encontrar la posición en la que aparece.

Aquí decidí usar la función find de str, que está optimizada para esto. No obstante, la cantidad es tan grande que no debemos comprobar todo si queremos que el programa acabe en un tiempo razonable. Para ello uso el argumento start y pido que solo busque entre los 10 últimos caracteres de la cadena. Tarde mucho en darme cuenta de esto y me desesperé bastante ya que no veía forma de optimizarlo más.

def day14(inp):
    recipes = list()
    recipes.append(3)
    recipes.append(7)

    a = len(recipes)-2
    b = len(recipes)-1

    while len(recipes) &lt; inp:
        #print("A: %d B: %d" % (a,b))
        #print(recipes)
        recipe = recipes[a]+recipes[b]
        recipe_a = recipe // 10
        recipe_b = recipe % 10
        if recipe_a != 0:
            recipes.append(recipe_a)
        recipes.append(recipe_b)
        a += recipes[a] +1
        b += recipes[b] +1
        a %= len(recipes)
        b %= len(recipes)

    k = len(recipes) - (len(recipes) - inp)

    for i in range(10):
        #print("A: %d B: %d" % (a,b))
        #print(recipes)
        recipe = recipes[a]+recipes[b]
        recipe_a = recipe // 10
        recipe_b = recipe % 10
        if recipe_a != 0:
            recipes.append(recipe_a)
        recipes.append(recipe_b)
        a += recipes[a] +1
        b += recipes[b] +1
        a %= len(recipes)
        b %= len(recipes)
    print(recipes[k:k+10])

def day14_reverse(inp):
    inp = str(inp)
    recipes = str()
    recipes += str(3)
    recipes += str(7)

    a = len(recipes)-2
    b = len(recipes)-1

    while True:
        recipe = int(recipes[a])+int(recipes[b])
        recipe_a = recipe // 10
        recipe_b = recipe % 10
        if recipe_a != 0:
            recipes += str(recipe_a)
        recipes += str(recipe_b)
        a += int(recipes[a]) +1
        b += int(recipes[b]) +1

        a %= len(recipes)
        b %= len(recipes)
        if recipes.find(inp,len(recipes)-10) > -1:
            break
    pos = recipes.find(inp)
    print(pos)

if __name__ == "__main__":
    day14(846021)
    day14_reverse(846021)

Conclusiones

Y con esto acabamos la segunda semana del Advnt of Code 2018. Estos problemas ya me están llevando mucho más tiempo y me estoy desesperando más con ellos. Lo que más rabia me da es que muchas cosas se me podrían haber ocurrido antes, ya que salvo el día 11, podía haber sabido perfectamente desde el principio la solución óptima.

La próxima semana los problemas serán más complicados y no estoy seguro de poder ofrecer mis soluciones a todos.

La entrada Advent of Code 2018: segunda semana se publicó primero en Adrianistán.

» Leer más, comentarios, etc...

Adrianistán

Las mejores librerías gratuitas para gráficas en PHP

diciembre 15, 2018 10:33

“Los datos son el nuevo petróleo” es algo que dicen muchas personas y que parece confirmarse si se leen noticias tecnológicas. Y en efecto, actualmente podemos generar una cantidad de datos inmensa de los que podemos extraer una información muy valiosa. Y ahí esta la clave, en “extraer”. Los datos como tal no sirven para nada. Al igual que el petróleo, no lo queremos en bruto, sino refinado, tratado. Con los datos pasa lo mismo. Para representar esta información desde hace muchos años estadística ha ido diseñando métodos para representar datos y extraer información de ellos.

Antiguamente estas gráficas se realizaban a mano y su creación era muy costosa. Hoy en día, como seguro que sabrás si sigues las noticias tecnológicas actuales, podemos usar herramientas para generar estas gráficas de forma sencilla, directamente en un servidor. En este artículo vamos a ver las mejores librerías para generar gráficas en PHP.

phpChart

La primera librería de la lista es phpChart. Se trata de una librería de pago, pero con muchas opciones para las gráficas. Desafortunadamente, solo las versiones más caras soportan gráficos que no sean de barras, de líneas y de sectores. La librería funciona generando código JavaScript para el cliente que le permite dibujar la gráfica usando Canvas. Esta gráfica es interactiva, pudiendo el usuario hacer zoom, cambiar de modo de gráfico y permitiendonos hacer animaciones.

pChart

Otra opción es generar imágenes para las gráficas en el servidor. Esto es precisamente lo que hace pChart. Originalmente era un mero wrapper sobre la librería estándar de gráficos de PHP (GD) para añadir anti-alising, pero con el tiempo se enfocó a las gráficas estadísticas. Sus principales cualidades son una gran calidad en las visualizaciones y un rendimiento óptimo. pChart es gratuita si nuestra aplicación es software libre y de pago si no lo es. Requiere las extensiones GD y FreeType para funcionar correctamente. pChart soporta gran cantidad de gráficas y es usado por empresas como Intel o Airbus e instituciones como la NASA y el CERN. La librería tiene capacidades interactivas, aunque algo más reducidas que otras soluciones.

JpGraph

Otra librería que permite renderizar en el servidor es JpGraph. Esta librería, gratuita para usos no-comerciales, está diseñada con orientación a objetos. Tiene una gran variedad de gráficas disponibles, de buena calidad y como ellos promocionan: ligeros. Existe una versión PRO con todavía más tipos de gráficos, aunque son bastante específicos y normalmente no harán falta. La librería necesita que GD esté instalado y es compatible con PHP7.

D3.js

Para acabar, una de mis personalmente favoritas, D3.js. D3 no es una librería para generar gráficas y tampoco es PHP. Se trata de una librería JavaScript con utilidades para los documentos basados en datos, la idea es incluir esta librería y usarla donde quieras mostrar las gráficas. La ventaja de D3 es que te permite hacer lo que quieras y tiene una gran comunidad. Es open-source, y aunque es algo más difícil de usar, genera unos resultados excelentes, usando SVG de por medio. De hecho, D3 no generará código visual, sino que nos ayudará a hacerlo nosotros mismos. He ahí la dificultad y a su vez, su gran flexibilidad.

La entrada Las mejores librerías gratuitas para gráficas en PHP se publicó primero en Adrianistán.

» Leer más, comentarios, etc...

Blog Bitix

Monitorizar una aplicación Java de Spring Boot con Micrometer, Prometheus y Grafana

diciembre 14, 2018 07:00

Java
Promehteus
Grafana

Los proyectos de Spring no son tan conservadores como Java EE o ahora Jakata EE y se desarrollan a una velocidad mayor cubriendo de forma más temprana las necesidades de los programadores según evolucionan las tecnologías y se adoptan nuevos modelos de arquitectura.

Con el advenimiento de los microservicios, contenedores, la nube y aplicaciones autocontenidas Spring se ha adaptado con proyectos como Spring Boot y Spring Cloud. En el asunto que ocupa este artículo de métricas con la versión 2 de Spring Boot se ha adoptado Micrometer como librería para proporcionar las métricas.

Micrometer permite exportar a cualquiera de los más populares sistemas de monitorización los datos de las métricas. Usando Micrometer la aplicación se abstrae del sistema de métricas empleado pudiendo cambiar en un futuro si se desea. Uno de los sistemas más populares de monitorización es Prometheus que se encarga de recoger y almacenar los datos de las métricas expuestas por las aplicaciones y ofrece un lenguaje de consulta de los datos con el que otras aplicaciones pueden visualizarlos en gráficas y paneles de control. Grafana es una de estas herramientas que permite visualizar los datos proporcionados por Prometheus. Estos sistemas de monitorización ofrecen un sistema de alertas que se integran entre otros con Slack.

En el artículo Información y métricas de la aplicación con Spring Boot Actuator mostraba como configurar Spring Boot y Spring Boot Actuator para exponer métricas en el endpoint /actuator/metrics, con estas herramientas solo se exponen la clave y valor de cada métrica y solo en un momento dado. Pueden ser métricas del servicio como cantidad de CPU usada, memoria consumida y libre, espacio en almacenamiento, etc… o métricas de aplicación como número de peticiones realizadas al servicio, tiempo de respuesta, etc… Una de las funcionalidades de Prometheus es recolectar cada cierto tiempo los valores de estas métricas que da lugar a una colección de datos que varía en el tiempo y que Grafana puede visualizar en gráficas para una mucha mayor facilidad de comprensión que la enorme cantidad de datos en crudo.

Usando Spring Boot 2 exportar los datos para Prometheus es realmente sencillo, basta con incluir la dependencia io.micrometer:micrometer-registry-prometheus mediante la herramienta de construcción, por ejemplo Gradle, y automáticamente se expone en el endpoint /actuator/prometheus con la información de las métricas en el formato que espera Prometheus para recolectarla.

Micrometer y Prometheus ofrecen varios tipos de métricas:

  • Counter: representa un valor que se va incrementando a lo largo del tiempo. Puede ser el número de invocaciones recibidas por servicio.
  • Gauge: representa un valor que arbitrariamente puede subir o bajar. Puede ser la cantidad de memoria usada.
  • Timer: mide periodos de tiempo. Puede ser el tiempo de respuesta empleado para atender una petición de un servicio.
  • Distribution summaries: recolecta la distribución de una serie de datos con los que se pueden obtener percentiles.

Utilizando el ejemplo que hice para la serie de artículos sobre Spring Cloud he añadido al micro servicio service un contador con el número de invocaciones que se le ha realizado. Este dato se expone en el endpoint con la clave service.invocations como se ha definido al registrar el contador en Micrometer con la clase MeterRegistry. Además de esta métrica propia del servicio Spring Boot Actuator añade otras muchas más del uso de la CPU, memoria, …

Una clase de una aplicación de Spring Boot que utiliza un Counter.

Las claves de las métricas por defecto exportadas por Spring Boot Actuator.

Los datos de una métrica en el enpoint /actuator/metrics/service.invocations.

Y las mismas métricas en el formato que espera Prometheus.

Para iniciar el ejemplo de Spring Cloud que consta de un servicio de registro y descubrimiento, un servicio de configuración, un servicio del que se pueden iniciar varias instancias y un cliente que hace peticiones hay que utilizar la siguiente serie de comandos.

Una vez expuestas las métricas en el formato que espera Prometheus este ya puede recolectarlas. Para usar Prometheus y posteriormente Grafana de forma fácil evitando tener que instalar y configurar nada se puede usar Docker, en este caso con Docker Compose. En la serie de artículos sobre Docker explico que proporciona Docker y como usar las varias herramientas que ofrece.

El archivo de Docker Compose contiene dos contenedores uno para Prometheus y otro para Grafana, con sus archivos de configuración. En la configuración de Prometheus se crean un job que recolecta las métricas cada pocos segundos del servicio a través del endpoint de métricas. En la configuración de Grafana se añade como una fuente de datos Prometheus, se puede añadir otras varias.

Prometheus posee la funcionalidad básica de crear gŕaficas con las métricas recogidas pero no tiene la habilidad de crear paneles que recogen una colección de gráficas relacionadas o un editor de consultas más avanzado como tiene Grafana.

Métrica de la aplicación en Prometheus y Grafana

Una vez que Prometheus recolecta los datos de las métricas al introducir las expresiones se proporciona asistencia de código. Por otro lado, en la sección Status > Targets de Prometheus se puede ver el estado de los servicios de los que recolecta métricas.

Estado de los servicios rastreados por Prometheus

Como Spring Boot Actuator exporta muchas métricas del funcionamiento del servicio Grafana puede crear gráficas de todas ellas. No hace falta crear un dashboard desde cero, se pueden descargar e importar dashboards. Este ejemplo para Micrometer recoge la memoria de la JVM (heap y no heap), uso de CPU, carga, hilos, estado de hilos, descriptores de archivos, recolector de basura, classloader y entrada/salida básica.

Dashboard de una aplicación Spring Boot en Grafana

Grafana tiene plugins para añadir como fuentes de datos bases de datos relacionales para extraer mediante sentencias SQL y visualizar datos almacenados en MySQL, PostgreSQL u Oracle.

Con la información de las métricas se conoce más en detalle cual es el comportamiento normal de una aplicación y observar de forma rápida cuando se introducen cambios como afectan al comportamiento de la misma tanto de forma negativa como de forma positiva. En cualquier aplicación que ofrece un servicio es importante conocer su estado y actuar incluso antes de que ofrezca un mal comportamiento e incluso deje de prestar su servicio. La monitorización no sustituye sino que complementa un sistema de trazas que en una aplicación Java es común que se realice con SLF4J o Log4j.

Aún quedan algunas preguntas por responder ¿como agregar los datos de múltiples instancias? ¿si se crean nuevas instancias del servicio como puede conocer Prometheus los nuevos targets que se han creado? En el ejemplo solo hay una instancia del servicio y la configuración de Prometheus es proporcionada por un archivo estático. Aún desconozco las respuestas, no lo he investigado en detalle, hay alguna pregunta sobre este tema en StackOverflow y por la respuesta Prometheus no tiene un adaptador para Eureka entre los varios servicios de descubrimiento que sí soporta en su configuración.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew-run.sh, docker-compose up.

» Leer más, comentarios, etc...

Frostq

Angular - Cómo hacer testing unitario con Jasmine

diciembre 14, 2018 12:00

Introducción

Los tests son una pieza fundamental en los proyectos de hoy en día. Si tienes un proyecto grande es esencial tener una buena suite de tests para poder probar la aplicación sin tener que hacerlo manualmente. Además si lo combinas con la integración continua puedes minimizar el riesgo y los bugs futuros.

Antes de meternos de lleno en testear aplicaciones Angular, es importante saber qué tipos de tests que existen:

  • Tests Unitarios: Consiste en probar unidades pequeñas (componentes por ejemplo).
  • Tests End to End (E2E): Consiste en probar toda la aplicación simulando la acción de un usuario, es decir, por ejemplo para desarrollo web, mediante herramientas automáticas, abrimos el navegador y navegamos y usamos la página como lo haría un usuario normal.
  • Tests de Integración: Consiste en probar el conjunto de la aplicación asegurando la correcta comunicación entre los distintos elementos de la aplicación. Por ejemplo, en Angular observando cómo se comunican los servicios con la API y con los componentes.

En este artículo vamos a cubrir el testeo unitario en Angular.

Testing unitarios con Jasmine

Para hacer tests unitarios en Angular se suele usar Jasmine. Jasmine es un framework Javascript (No es exclusivo de Angular, lo puedes usar en cualquier aplicación web), para la definición de tests usando un lenguaje natural entendible por todo tipo de personas.

Un test en Jasmine tiene esta pinta:

describe("A suite name", function() {
  it("contains spec with an expectation", function() {
    expect(true).toBe(true);
  });
});
  • describe: Define una suite de tests, es decir, una colección de tests. Ésta función recibe dos parámetros, un string con el nombre de la suite y una función donde definiremos los tests.

  • it: Define un test en particular. Recibe cómo parámetro el nombre del test y una función a ejecutar por el test.

  • expect: Lo que espera recibir el test. Es decir, con expect hacemos la comprobación del test. Si la comprobación no es cierta el test falla. En el ejemplo anterior comprobamos si true es true luego el test pasa. Cómo ves no podemos simplemente hacer la comprobación haciendo poniendo la operación ===, tenemos que usar las funciones que vienen con Jasmine, las cuales son:

    • expect(array).toContain(member);
    • expect(fn).toThrow(string);
    • expect(fn).toThrowError(string);
    • expect(instance).toBe(instance);
    • expect(mixed).toBeDefined();
    • expect(mixed).toBeFalsy();
    • expect(mixed).toBeNull();
    • expect(mixed).toBeTruthy();
    • expect(mixed).toBeUndefined();
    • expect(mixed).toEqual(mixed);
    • expect(mixed).toMatch(pattern);
    • expect(number).toBeCloseTo(number, decimalPlaces);
    • expect(number).toBeGreaterThan(number);
    • expect(number).toBeLessThan(number);
    • expect(number).toBeNaN();
    • expect(spy).toHaveBeenCalled();
    • expect(spy).toHaveBeenCalledTimes(number);
    • expect(spy).toHaveBeenCalledWith(…arguments);

Jasmine también viene con funciones que se pueden ejecutar antes de realizar un test, o después:

  • beforeAll: Se ejecuta antes de pasar todos los tests de una suite.
  • afterAll: Se ejecuta después de pasar todos los tests de una suite.
  • beforeEach: Se ejecuta antes de cada test de una suite.
  • afterEach: Se ejecuta después de cada test de una suite.

Por ejemplo:

describe('Hello world', () => {

  let expected = "";

  beforeEach(() => {
    expected = "Hello World";
  });

  afterEach(() => {
    expected = "";
  });

  it('says hello', () => {
    expect(helloWorld())
        .toEqual(expected);
  });
});

Antes de ejecutar el test definido mediante la función it se llama a la función beforeEach la cual cambia el valor de la variable expected, haciendo que el test pase.

Tests unitarios con Angular

Testing unitario en Angular

Si has creado el proyecto y los componentes usando Angular cli, te habrás dado cuenta de que al generar un componente, también se crea un archivo .spec.ts, y eso es porque Angular cli se encarga por nosotros de generar un archivo para testear cada uno de los componentes. Además mete en el archivo el código necesario para empezar a probar y testear los componentes. Por ejemplo, el archivo notes.component.spec.ts que se creó cuando generé un componente para crear y mostrar notas tiene esta pinta:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { NotesComponent } from './notes.component';

describe('NotesComponent', () => {
  let component: NotesComponent;
  let fixture: ComponentFixture<NotesComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ NotesComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(NotesComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Lo primero que hace es crear una suite de tests para el componente con el método describe. Tras crear la suite crea dos variables que va a necesitar para testear los componentes, el propio componente, que lo mete en la variable component y una variable fixture de tipo ComponentFixture del componente, la cual sirve para tener el componente pero añadiendo más información para que sea más fácil de testear.

A contunuación llama al método beforeEach con una función asíncrona (sirve para asegurar que se termina de ejecutarla función asíncrona antes de pasar un test) para crear todas las dependencias del componente, en tese caso, el componente en sí. Si usáramos en el componente un servicio, habría que incluirlo también, creando una sección llamada providers (como en el app.module.ts).

Después vuelve a llamar a la función beforeEach, esta vez, sin ser asíncrona. Crea una instancia fixture del componente usando TestBed, el cual se encargará de inyectar las dependencias definidas anteriormente mediante configureTestingModule. Para sacar el componente en sí del fixture usa componentInstance.

Por último crea un test para comprobar que el componente se crea correctamente, para ello, llama a la función expect y espera que se cree bien y tenga error mediante toBeTruthy().

Para correr los tests y ver los resultados con Angular cli el comando es:

ng test

Testeando clases en Angular

Imaginemos que tenemos un servicio inyectado a un componente que queremos testear. Podemos usar varisas técnicas para testear el servicio:

Usando el servicio real

  import {LoginComponent} from './login.component';
  import {AuthService} from "./auth.service";

  describe('Login component', () => {

    let component: LoginComponent;
    let service: AuthService;

    beforeEach(() => { 
      service = new AuthService();
      component = new LoginComponent(service);
    });

    afterEach(() => { 
      localStorage.removeItem('token');
      service = null;
      component = null;
    });


    it('canLogin returns true when the user is authenticated', () => {
      localStorage.setItem('token', '12345'); 
      expect(component.isLogged()).toBeTruthy();
    });

  
  });

En este caso, a diferencia de la estructura que crea Angular cli, no estoy usando TestBed, porque por el momento no me hace falta. Simplemente creo el componente y el servicio y paso el servicio como parámetro al componente para que se inyecte mediante inyección de dependencias. Cuando hago el test, simplemente llamo al método del componente y hago la comprobación.

Esta técnica puede venir bien para aplicaciones pequeñas, pero si el componente necesita muchas dependencias puede llegar a ser muy tedioso andar creando todos los servicios. Además esto no favorece la encapsulación porque estamos creando servicios y no estamos aislando el testeo del componente.

Además de esta forma, tenemos que meter a mano en el localStorage un valor para que el authService funciona y devuelva true.

Creando un servicio virtual (mockeando)

  import {LoginComponent} from './login.component';

  class MockAuthService { 
    authenticated = false;

    isAuthenticated() {
      return this.authenticated;
    }
  }

  describe('Login component', () => {

    let component: LoginComponent;
    let service: MockAuthService;

    beforeEach(() => { 
      service = new MockAuthService();
      component = new LoginComponent(service);
    });

    afterEach(() => {
      service = null;
      component = null;
    });


    it('canLogin returns true when the user is authenticated', () => {
      service.authenticated = true; 
      expect(component.isLogged()).toBeTruthy();
    });

  
  });

Esta vez, en lugar de usar el authService real, creamos nuestra propia clase MockAuthService dentro del propio test, la cual tendrá un método con el mismo nombre que el servicio real, pero en su lugar devuelve el valor directamente.

Como hacíamos antes, creamos el componente y le pasamos el servicio, en este caso, el servicio virtual que hemos creado. Usando este método no tenemos que usar el localStorage, de esta forma, solo testeamos el componente en sí y no tenemos que depender de lo que haga el servicio internamente.

Si aún asi crear el servicio virtual resulta costoso, siempre podemos extender del servicio real, sobreescribiendo los métodos que nos interesen:

  class MockAuthService extends AuthService {
    authenticated = false;

    isAuthenticated() {
      return this.authenticated;
    }
  }

También podemos sobreescribir la inyección de dependencias con nuevas clases, por ejemplo:

  TestBed.overrideComponent(
    LoginComponent,
    {set: {providers: [{provide: AuthService, useClass: MockAuthService}]}}
  );

Mediante del uso de spy de Jasmine

Jasmine también ofrece la posibilidad de coger una clase y devolver directamente lo que nos interese sin tener que ejecutar internamente sus métodos:

  import {LoginComponent} from './login.component';
  import {AuthService} from "./auth.service";

  describe('Component: Login', () => {

    let component: LoginComponent;
    let service: AuthService;
    let spy: any;

    beforeEach(() => { 
      service = new AuthService();
      component = new LoginComponent(service);
    });

    afterEach(() => { 
      service = null;
      component = null;
    });


    it('canLogin returns true when the user is authenticated', () => {
      spy = spyOn(service, 'isAuthenticated').and.returnValue(true);
      expect(component.isLogged()).toBeTruthy();   
    });
  });

Como ves, con la función spyOn de Jasmine podemos hacer que el servicio devuelva directamente true en la llamada a el nombre de función que le pasamos como parámetro al spy.

Testeando llamadas asíncronas

Si por ejemplo tenemos un test que testea un método asíncrono del componente o del servicio (una llamada a una API por ejemplo), podemos hacer lo siguiente:

  it('Should get the data', fakeAsync(() => {
  fixture.componentInstance.getData();
  tick();
  fixture.detectChanges();    
  expect(component.data).toEqual('new data');
}));

Angular proporciona el método fakeAsync para realizar llamadas asíncronas, dejándonos acceso a la llamada a tick() el cual simula el paso del tiempo para esperar a que la llamada asíncrona se realice.

Accediendo a la vista

Para acceder a los elementos html de la vista de un componente, podemos usar su fixture:

describe('Component: Login', () => {

  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let submitButton: DebugElement;
  

  beforeEach(() => {

    TestBed.configureTestingModule({
      declarations: [LoginComponent]
    });

    // create component and test fixture
    fixture = TestBed.createComponent(LoginComponent);

    // get test component from the fixture
    component = fixture.componentInstance;

    submitButton = fixture.debugElement.query(By.css('button_submit'));

  });
});

En este caso, accedemos al botón html de la vista del componente de login mediante el debugElement del fixture, el cual nos da acceso a hacer querys de elementos html de la vista.

Como en javascript podemos acceder o cambiar las propiedades de estos elementos:

submitButton.innerText = 'Testing the button';

El TestBed del componente nos proporciona una manera de provocar que la vista de actualice con la nueva información en caso de que hayamos cambiado algo en el componente:

fixture.detectChanges()

Testing de llamadas http

Para testear las llamdas HTTP podemos hacer dos tests, uno para comprobar que la petición se ha realizado correctamente y otro test para verificar que la información que llega de la API es correcta. Para lo segundo podemos usar la clase MockBackend de Angular, que es capaz de simular un backend con información creada por nosotros, para que cuando el servicio realice la llamada HTTP en realidad llame al MockBackend para que no tenga ni que hacer la llamada real y podamos comprobar la información que llega al servicio.

No me voy a meter mucho más en este tema, porque sinceramente es complejo de entender. Si aún asi tienes dudas y quieres aprender como testear servicios con llamadas http te dejo esta lista de artículos (en inglés):

Conclusiones

Realizar tests, como pasa todos los lenguajes, es un mundo completamente aparte, hay muchos conceptos que aprender, maneras de realizar tests, etc. Como siempre digo este artículo no es más que una pequeña introducción al mundo del testing en Angular, me estoy dejando un montón de técnicas y cosas por ver que nos puede ofrecer Jasmine y Angular. Si quieres ver todas las posibilidades que pueden ofrecer puedes echar un vistazo a su página web oficial y a estos artículos interesantes:

» Leer más, comentarios, etc...

Variable not found

Qué hay de nuevo en ASP.NET Core 2.2

diciembre 12, 2018 05:27

La pasada semana, en el contexto del evento Microsoft Connect(); 2018, se lanzaron las últimas versiones de la familia de productos "Core": .NET Core 2.2, ASP.NET Core 2.2 y Entity Framework Core 2.2.

En todos los casos son revisiones pequeñas y que no rompen nada de lo anterior, pero en cada uno de estos productos se han introducido mejoras que vale la pena conocer, por lo que, antes que nada, os recomiendo que echéis un vistazo a los artículos anteriores, que son los anuncios oficiales.

En este post vamos a ver rápidamente las novedades más destacables de ASP.NET Core 2.2.

Mejoras para Web API

Como recordaréis, ya en ASP.NET Core 2.1 se introdujeron algunas novedades orientadas a facilitar la alineación de nuestras API Web con Open API/Swagger, como el filtro [ApiController], la clase ActionResult o el soporte básico para Problem Details (RFC 7807). ASP.NET Core 2.2 continúa profundizando en esta línea añadiendo algunas características interesantes.

Por un lado, se introduce la posibilidad de usar convenciones predefinidas para especificar los tipos de respuestas de las acciones de forma global (por ensamblado), por controlador o por acción mediante el uso de atributos como ApiConventionType o ApiConventionMethod.

Por defecto el framework incorpora las convenciones DefaultApiConventions, que básicamente permiten especificar los códigos HTTP de retorno que utilizarán las habituales acciones de tipo CRUD (por ejemplo, una acción llamada Create retornará códigos 201-Created o 400-Bad request), pero también podemos crear convenciones personalizadas de forma muy sencilla.

¿Y para qué sirve esto? Pues básicamente para que herramientas como Swagger o los analizadores de código de los entornos de desarrollo puedan "entender" que es lo que hacen nuestras acciones y ayudarnos, por ejemplo, generando automáticamente documentación de nuestras APIs, generando código de clientes que la consuman, o para que los IDE nos informen mientras desarrollamos de posibles errores de programación.

Y hablando de analizadores de código, en ASP.NET Core 2.2 se han introducido algunos para mostrar warnings cuando estamos implementando controladores ApiController cuya documentación no corresponda con lo que dice su código. Por ejemplo, en la siguiente animación se observa cómo se sugiere añadir el atributo [ProducesResponseType(404)] a una acción que retorna un NotFound():

Code Analyzers en acción

Por último, también se ha mejorado el soporte de Problem Details, añadiendo información a los retornos HTTP 4xx, como un identificador que puede resultar útil para trazar el error en logs.

Mejoras en routing

Aunque aún se espera que esto cambie un poco para ASP.NET Core 3.0 (prevista para el año que viene), de momento en la versión 2.2 se han introducido algunas mejoras internas para conseguir un aumento importante de rendimiento en el proceso de selección de acciones o, en general, endpoints.

Además, se pone a disposición de los desarrolladores un servicio singleton llamado LinkGenerator que podemos utilizar para generar rutas hacia nuestras acciones (de momento, sólo a acciones, aunque se ampliará el alcance en la versión 3.0) desde cualquier parte de nuestra aplicación. Aunque es conceptualmente similar a otros componentes como IUrlHelper, se puede utilizar desde fuera de MVC (por ejemplo, en un middleware) y no requiere de un HttpContext.

Otra novedad interesante es la capacidad de inyectar código personalizado para transformar parámetros a la hora de generar rutas. Por ejemplo, podríamos hacer que una ruta generada con Url.Action("get", new { product='MacBookAir' }) se transforme en /products/mac-book-air. Podéis ver un ejemplo en este post de Hanselman.

Hosting in-process

Tras mucho tiempo de desarrollo y varias marchas atrás a la hora de incluirlo en una release oficial, por fin tenemos una versión mejorada de módulo ASP.NET Core para IIS que promete cuadruplicar el rendimiento de nuestras aplicaciones cuando se ejecutan sobre este servidor web.

El secreto de este incremento de rendimiento tan brutal está en sustituir la actual arquitectura en la que IIS actúa como proxy inverso hacia Kestrel, que es quien ejecuta nuestra aplicación, como se representa en el siguiente diagrama:

ASP.NET Core out of process

A partir de ASP.NET Core 2.2, por defecto pasaremos a un modo de hospedaje in process, donde nuestra aplicación se ejecuta dentro del worker de IIS (w3wp.exe), eliminando esa costosa conexión HTTP interna:

ASP.NET Core in-process
Nota: ojo si desplegáis en Azure y vais a upgradear rápidamente vuestra aplicación sólo por tener este incremento de rendimiento. El nuevo módulo ASP.NET Core no estará disponible en los App Services hasta ya bien entrado Diciembre, por lo que de momento tendremos que seguir usando el modelo out of process anterior.

Health monitoring

ASP.NET Core incluye ahora mecanismos para comprobar la salud de los componentes de nuestra aplicación (servidores, contenedores, servicios...). Aunque se pueden utilizar en cualquier tipo de proyecto, sin duda es especialmente interesante en entornos que requieren este tipo de comprobaciones, como orquestadores o balanceadores de carga que comprueban periódicamente el correcto funcionamiento de los servicios y son capaces de tomar medidas, como el reinicio de un contenedor o redirigir el tráfico, en caso de existir problemas.

Cabe destacar además el esfuerzo que los amigos del proyecto BeatPulse han realizado para portar esta biblioteca, que ya proporcionaba funcionalidades similares para versiones anteriores de ASP.NET Core, a la nueva infraestructura. Esto proporciona a los desarrolladores paquetes para comprobar el estado de una gran variedad de servicios (SQL Server, Redis, Oracle, MySQL, SqLite, RabbitMQ, Azure...), push hacia sistemas de telemetría como Application Insights o Prometheus, webhooks, e incluso un interfaz de usuario para visualizar el estado del sistema.

BeatPulse UI

Otras mejoras

Además de lo anterior, también cabe destacar las siguientes mejoras:
  • Incremento del rendimiento de hasta el 15% en validaciones del modelo.
  • Incremento del rendimiento entre el 20% (Windows) y 60% (Linux) al usar HTTP client.
  • Actualizadas las plantillas de proyecto a Bootstrap 4 y Angular 6.
Bueno, pues creo que no me dejo nada de calado por detrás, así que más o menos esto es lo que encontraremos al hacer el unboxing de ASP.NET Core 2.2. ¡Que aproveche!

Publicado en Variable not found.

» Leer más, comentarios, etc...

Poesía Binaria

Cómo actualizar /etc/hosts con todos los contenedores docker que hay en ejecución

diciembre 10, 2018 09:29

Si tenemos varios contenedores docker arrancados en nuestro ordenador. Muchas veces, nos interesará conectar con servicios corriendo dentro de cada uno de ellos. Algunos estarán lanzados simplemente con docker, otros con docker-compose, cada uno trabajando en un sistema distinto, y necesitamos una forma más o menos sencilla de acceder a cada uno de ellos.

Con un pequeño script podemos recorrer todos los contenedores, pedir la dirección IP de cada uno de ellos y añadirlas al nuestro archivo /etc/hosts de forma que este archivo se actualice automáticamente cada vez que lanzamos el comando.

El script

Yo lo suelo llamar docker_update_hosts.sh, y suele estar en /usr/local/bin. En realidad, es un enlace el que está ahí:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash

function panic()
{
    echo "$@" >&2
    exit 1
}

if [ "`whoami`" != "root" ]
then
    panic "This program must be ran as root"
fi

# Clear /etc/hosts file
HEADER="# Added automatically by docker_update_hosts"
sed -i '/docker\.local$/d' /etc/hosts
sed -i "/$HEADER\$/d" /etc/hosts
# Remove empty lines at the end of file
sed -i  -e :a -e '/^\n*$/{$d;N;ba' -e '}' /etc/hosts

echo -e "\n$HEADER" >> /etc/hosts

IFS=$'\n' && ALLHOSTS=($( docker ps --format '{{.ID}} {{.Names}}'))

for line in ${ALLHOSTS[*]}; do
    IFS=" " read  -r ID NAME <<< "$line"
    IP="$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $ID)"
    if [ -n "$IP" ]; then
        echo -e "$IP\t$NAME.docker.local" >> /etc/hosts
    fi
done

El script debe ser ejecutado como root, porque tiene que tener permiso para escribir en /etc/hosts. Para ello, en muchas distribuciones lo podremos ejecutar así:

sudo docker_update_hosts

Hosts

El script creará varios hosts llamados [contenedor].docker.local donde contenedor es el nombre de cada uno de nuestros contenedores. Al final, nos podremos juntar con algo como:

172.17.0.8 myphp5.6-fpm.docker.local
172.17.0.7 mongodb-testing.docker.local
172.17.0.6 wp-plugin-test.docker.local
172.17.0.5 myphp7.2-fpm.docker.local
172.17.0.4 mariadb-proyectos.docker.local
172.17.0.3 mariadb-testing.docker.local
172.17.0.2 redis.docker.local

De esta forma, podremos utilizar el nombre que le hemos dado en docker junto con “.docker.local” para referirnos al contenedor y llamar a un servicio encerrado en el mismo. Por ejemplo, para conectar con la base de datos mariadb-proyectos.docker.local podemos, desde el mismo ordenador host:

mysql -u root -p -h mariadb-proyectos.docker.local

Precauciones

Ya que el script está editando el archivo /etc/hosts y ese mismo fichero puede ser editable por nosotros, el script escribe un comentario:

1
# Added automatically by docker_update_hosts

Y debajo se escriben todos los hosts e IPs de contenedores docker que encontremos. Automáticamente, nada más ejecutar el script se busca el comentario y se buscan todos los hosts que terminen en .docker.local, por lo que si tenemos otras cosas que terminen en .docker.local serán eliminadas cuando se ejecute el script.

No tenemos que tener especial cuidado cuando editemos el fichero /etc/hosts manualmente. Podremos meter hosts nuevos al final, o encima de los hosts introducidos por el script y se respetarán.

Ejecutar automáticamente

Dependiendo de nuestra forma de trabajar, podemos ejecutar el script a mano siempre que sea necesario, ejecutarlo dentro de un script para lanzar contenedores o podemos ejecutarlo en un cron cada 10 minutos, por ejemplo.

Foto principal: unsplash-logoSherzod Max

The post Cómo actualizar /etc/hosts con todos los contenedores docker que hay en ejecución appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 341

diciembre 10, 2018 07:55

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Adrianistán

Advent of Code 2018: primera semana

diciembre 07, 2018 01:33

Este año, como ya viene siendo habitual, tiene lugar la competición de programación Advent of Code. El objetivo es resolver un problema de programación al día, hasta el día de navidad, a modo de un particular calendario de adviento. Este año, como ya no viene siendo tan habitual, me he propuesto hacerlos todos y explicar mis soluciones. Esta semana todos han sido resueltos en Python.

Los programas que he usado, así como los enunciados y los datos de entrada que me han tocado (a cada uno le tocan datos diferentes) están en este repositorio de GitHub.

Día 1

El primer reto que nos proponen es muy sencillo. Se nos pasa un archivo con sumas y restas y tenemos que calcular el resultado final. Pongamos un ejemplo trivial:

+1, +1, -2 = 0

El procesdimiento es sencillo, leer cada operación, extraer el signo, convertir a número y aplicar la operación detectada por el signo.

def apply_freq(freq):
    with open("input.txt") as f:
        lines = f.readlines()
    for line in lines:
        signo = line[0:1]
        numero = int(line[1:])
        if signo == "+":
            freq += numero
        elif signo == "-":
            freq -= numero
    return freq

if __name__ == "__main__":
    freq = 0
    freq = apply_freq(freq)
    print("FREQ FINAL: %d" % freq)

La segunda parte es más interesante, ya que nos dice que tenemos que aplicar la misma cadena de operaciones de forma indefinida hasta encontrar una resultado repetido.

Aquí la clave es conocer el funcionamiento de un Set o conjunto. Se trata de una estructura de datos que no permite elementos repetidos. La idea está en ir almacenando los resultados que van saliendo hasta encontrar uno que ya esté dentro. Al encontrarlo salir e indicarlo.

FREQS = set()

def apply_freq(freq):
    with open("input.txt") as f:
        lines = f.readlines()
    for line in lines:
        signo = line[0:1]
        numero = int(line[1:])
        if signo == "+":
            freq += numero
        elif signo == "-":
            freq -= numero
        if freq in FREQS:
            return (True,freq)
        else:
            FREQS.add(freq)
    return (False,freq)

if __name__ == "__main__":
    freq = 0
    while True:
        end,freq = apply_freq(freq)
        if end:
            break
    print("FREQ FINAL: %d" % freq)

Aquí viene una anécdota interesante. Estoy haciendo estos retos con unos amigos en un grupo de Telegram y al poco de yo haberlo terminado empezaron a preguntar cuánto tardaba en ejecutarse la segunda parte. Al parecer les iba muy lento, yo sorprendido les dije que había sido instantáneo y que estaba usando Python. Ellos se preguntaban como podía ser, ya que lo habían hecho en C y les estaba tardando varios minutos.

Una representación de un set o conjunto. No puede haber elementos repetidos. No existe orden definido

La respuesta tiene que ver con el set. Yo sinceramente fui a él directamente pero otros compañeros no, y usaron listas y arrays. La búsqueda que realizaban para saber si el elemento estaba dentro era lineal. Comparada con la búsqueda en un set implementado con tabla hash que es constante, el rendimiento es muy inferior. He aquí un ejemplo de como lo más importante de cara a la eficiencia es el algoritmo y las estructuras de datos que usamos para resolver un problema.

Día 2

El segundo día se nos propone otro problema de dificultad similar. Sobre una lista de palabras tenemos que contar cuantas palabras tienen 2 letras repetidas y 3 letras repetidas, para finalmente multiplicar ambos números.

En este caso fui bastante pragmático y opté por usar la clase Counter de Python. Counter cuenta cuantas veces aparece una letra y lo deja en un diccionario. Podemos ignorar las claves, ya que lo único que tenemos que hacer es buscar en los valores si está el 3 y el 2, y si es así, sumar uno a nuestro contador.

from collections import Counter

twice = 0
triple = 0

with open("input.txt") as f:
    lines = f.readlines()
for line in lines:
    c = Counter(line)
    if 3 in c.values():
        triple += 1
    if 2 in c.values():
        twice += 1
total = twice*triple
print("CHECKSUM: %d" % total)

La segunda parte ya es más interesante. Se nos pide que la misma lista de palabras, encontremos las dos palabras que solo se diferencian en una letra (se tiene en cuenta el orden). Aquí el algoritmo es un poco más lento ya que para cada palabra la comparamos con el resto de palabras, hasta encontrar aquella que efectivamente tiene un cambio de únicamente una palabra.

Para ello hace falta una función que nos diga cuantas palabras hay de diferencia entre dos palabras, que es bastante sencillita.

def changes(base,word):
    changes = 0
    for i,letter in enumerate(base.strip()):
        if not letter == word[i]:
            changes += 1
    return changes

def find_similar(lines):
    for base in lines:
        for word in lines:
            if changes(base,word) == 1:
                return (base,word)

with open("input.txt") as f:
    lines = f.readlines()
base,word = find_similar(lines)
final = str()
for i,letter in enumerate(base.strip()):
    if letter == word[i]:
        final += letter
print("FINAL %s"%final)

Una vez tengamos las dos palabras que solo se diferencian en una letra, hacemos un bucle similar al usado para encontrarlas, solo que esta vez para formar la palabra con todas las letras que tienen en común, ignorando la diferente.

Día 3

En el día 3 nos proponen un problema muy interesante. Tenemos una lista de parcelas rectangulares, definidas por la posición de su esquina superior-izquierda y su ancho y alto, todo en metros. Para la primera parte tenemos que encontrar cuantos metros cuadrados del universo ficticio están en dos o más parcelas a la vez.

Lo primero que hay que hacer es leer la entrada, que esta vez ya no es tan trivial. Un simple RegEx nos permite obtener toda la información de forma sencilla.

from dataclasses import dataclass
import re

@dataclass
class Claim():
    id: int
    x: int
    y: int
    width: int
    height: int

def read_file():
    claims = []
    with open("input.txt") as f:
        lines = f.readlines()
    prog = re.compile(r"#([0-9]+) @ ([0-9]+),([0-9]+): ([0-9]+)x([0-9]+)");
    for line in lines:
        result = prog.match(line.strip())
        claim = Claim(
            id=int(result.group(1)),
            x=int(result.group(2)),
            y=int(result.group(3)),
            width=int(result.group(4)),
            height=int(result.group(5))
        )
        claims.append(claim)
    return claims

Para el algoritmo en sí, vamos a usar defaultdict. La idea es tener una tabla hash, cuya clave sean las coordenadas del mundo y su valor, el número de parcelas que estan sobre ella. Usamos defaultdict para que por defecto cualquier coordenada que no existiese con anterioridad tenga valor 0.

Así pues vamos a recorrer todas las parcelas y para cada parcela vamos a visitar todos los metros cuadrados que contiene en la tabla hash, sumando 1 para indicar que ese metro cuadrado pertenece a una parcela (más).

if __name__ == "__main__":
    claims = read_file()
    area = defaultdict(lambda: 0)
    for claim in claims:
        for i in range(claim.x,claim.x+claim.width):
            for j in range(claim.y,claim.y+claim.height):
                area[(i,j)] += 1
    overlaps = count_overlaps(area)
    print("Overlaps: %d" % overlaps)

La segunda parte nos indica que solo existe una parcela que no esté invadida por otra parcela. Hay que buscar cuál es e informar del ID que tiene. Para esto he simplemente vuelto a recorrer cada parcela comprobando si tienen algún metro cuadrado con uso por más de 1 parcela. Si para una parcela no se encuentran metros compartidos, automáticamente se devuelve su ID (ya que solo hay una parcela de estas características).

El código completo es el siguiente:

from dataclasses import dataclass
from collections import defaultdict
import re

@dataclass
class Claim():
    id: int
    x: int
    y: int
    width: int
    height: int

def read_file():
    claims = []
    with open("input.txt") as f:
        lines = f.readlines()
    prog = re.compile(r"#([0-9]+) @ ([0-9]+),([0-9]+): ([0-9]+)x([0-9]+)");
    for line in lines:
        result = prog.match(line.strip())
        claim = Claim(
            id=int(result.group(1)),
            x=int(result.group(2)),
            y=int(result.group(3)),
            width=int(result.group(4)),
            height=int(result.group(5))
        )
        claims.append(claim)
    return claims

def count_overlaps(area):
    overlaps = 0
    for overlap in area.values():
        if overlap > 1:
            overlaps += 1
    return overlaps

def find_nonoverlaping(claims,area):
    for claim in claims:
        overlaps = False
        for i in range(claim.x,claim.x+claim.width):
            for j in range(claim.y,claim.y+claim.height):
                if area[(i,j)] > 1:
                    overlaps = True
        if not overlaps:
            return claim.id 

if __name__ == "__main__":
    claims = read_file()
    area = defaultdict(lambda: 0)
    for claim in claims:
        for i in range(claim.x,claim.x+claim.width):
            for j in range(claim.y,claim.y+claim.height):
                area[(i,j)] += 1
    overlaps = count_overlaps(area)
    print("Overlaps: %d" % overlaps)
    non_overlaping = find_nonoverlaping(claims,area)
    print("ID: %d" % non_overlaping)

En esta segunda parte, tengo la intuición de que existe una manera más eficiente de hacerlo, pero todavía no he encontrado esa solución más eficiente.

Día 4

El problema del día 4, aunque aparentemente distinto al anterior, tiene muchas cosas en común y mi forma de resolverlo fue bastante parecida.

Se nos pide que sobre un registro de todas las noches identifiquemos el guardia que duerme más horas, y posteriormente su minuto preferido para quedarse dormido.

En primer lugar, la entrada de texto ya es bastante más compleja, pero con unos simples regex se puede analizar rápidamente, al menos para extraer la información que necesitamos. Aquí conviene prestar atención a que los registros de dormirse y despertarse ya que todos ocurren a la misma hora, luego lo único importante son los minutos. Otro detalle respecto a la entrada es que nos indican que está desordenada, sin embargo el formato de representación de la fecha que nos dan (parecido al ISO), se ordena cronológicamente simplemente con una ordenación alfabética.

La idea es muy similar a la del algoritmo anterior, primero tenemos una tabla hash con todos los guardias. Allí almacenamos otra tabla hash con los minutos de la hora y ponemos un cero si nunca se han dormido en ese minuto. Si usamos defaultdict, como en el código anterior, el código se simplifica bastante. En definitiva estamos usando dos tablas hash en vez de una y aplicar la misma idea de sumar 1, salvo que esta vez con el tiempo en vez del espacio (aunque Einstein vino a decir que eran cosas muy parecidas).

import re
from collections import defaultdict

def read_file():
    with open("input.txt") as f:
        lines = f.readlines()
    lines.sort()
    return lines


if __name__ == "__main__":
    lines = read_file()
    guard_prog = re.compile(r"[* ]+Guard #([0-9]+)")
    time_prog = re.compile(r"\[([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+)")
    current_guard = 0
    start_time = 0
    end_time = 0
    timetable = defaultdict(lambda: defaultdict(lambda: 0))
    for line in lines:
        # Hay tres tipos de líneas
        # Guardia, Sleep, Wake
        a = guard_prog.match(line.split("]")[1])
        if a != None:
            current_guard = a.group(1)
        elif "falls" in line:
            t = time_prog.match(line.split("]")[0])
            start_time = int(t.group(5))
        elif "wakes" in line:
            t = time_prog.match(line.split("]")[0])
            end_time = int(t.group(5))
            for i in range(start_time,end_time):
                timetable[current_guard][i] += 1

    # Calcular horas dormido
    max_guard = ""
    max_guard_sleeptime = 0
    for guard in timetable:
        s = sum(timetable[guard].values())
        if s > max_guard_sleeptime:
            max_guard_sleeptime = s
            max_guard = guard

    print("El guardia que más duerme es el %s con %d minutos" % (max_guard,max_guard_sleeptime))

    #Calcular minuto ideal
    max_minute = 0
    max_minute_times = 0
    for minute in timetable[max_guard]:
        if timetable[max_guard][minute] > max_minute_times:
            max_minute = minute
            max_minute_times = timetable[max_guard][minute]

    print("El guardia duerme más en el minuto %d (%d veces)" % (max_minute,max_minute_times))

    print("CHECKSUM %d" % (max_minute*int(max_guard)))

Posteriormente se recorren estas tablas hash para calcular lo pedido.

La segunda parte nos pide algo similar pero no idéntico, el guardia que se ha quedado dormido más veces en el mismo minuto (e indicar que minuto es).

La estructura de datos es exactamente la misma y solamente añadimos otro bucle para que busque este otro dato:

import re
from collections import defaultdict

def read_file():
    with open("input.txt") as f:
        lines = f.readlines()
    lines.sort()
    return lines


if __name__ == "__main__":
    lines = read_file()
    guard_prog = re.compile(r"[* ]+Guard #([0-9]+)")
    time_prog = re.compile(r"\[([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+)")
    current_guard = 0
    start_time = 0
    end_time = 0
    timetable = defaultdict(lambda: defaultdict(lambda: 0))
    for line in lines:
        # Hay tres tipos de líneas
        # Guardia, Sleep, Wake
        a = guard_prog.match(line.split("]")[1])
        if a != None:
            current_guard = a.group(1)
        elif "falls" in line:
            t = time_prog.match(line.split("]")[0])
            start_time = int(t.group(5))
        elif "wakes" in line:
            t = time_prog.match(line.split("]")[0])
            end_time = int(t.group(5))
            for i in range(start_time,end_time):
                timetable[current_guard][i] += 1

    # Calcular horas dormido
    max_guard = ""
    max_guard_sleeptime = 0
    for guard in timetable:
        s = sum(timetable[guard].values())
        if s > max_guard_sleeptime:
            max_guard_sleeptime = s
            max_guard = guard

    print("El guardia que más duerme es el %s con %d minutos" % (max_guard,max_guard_sleeptime))

    #Calcular minuto ideal
    max_minute = 0
    max_minute_times = 0
    for minute in timetable[max_guard]:
        if timetable[max_guard][minute] > max_minute_times:
            max_minute = minute
            max_minute_times = timetable[max_guard][minute]

    print("El guardia duerme más en el minuto %d (%d veces)" % (max_minute,max_minute_times))

    print("CHECKSUM %d" % (max_minute*int(max_guard)))

    # El guardia que ha estado un minuto concreto mas veces dormido
    max_guard = ""
    guard_minute = 0
    guard_minutes = 0
    for guard in timetable:
        for minute in timetable[guard]:
            if timetable[guard][minute] > guard_minutes:
                max_guard = guard
                guard_minute = minute
                guard_minutes = timetable[guard][minute]
    print("El guardia %s se ha dormido en el minuto %d (%d veces)" % (max_guard,guard_minute,guard_minutes))
    print("CHECKSUM %d" % (guard_minute*int(max_guard)))

En este caso, la segunda parte apenas ha implicado modificaciones, siendo la estructura de datos subyacente intacta.

Día 5

Este día se nos propone un reto aparentemente sencillo, pero cuya resolución puede ser muy lenta o rápida dependiendo de como lo hagamos. He de decir, que mi solución era muy lenta, extremadamente y tuve que mirar como lo habían hecho otras personas para entender como se podía optimizar.

La primera tarea consiste en reducir unas cadenas de reactivos. La norma es que si hay una letra minúscula y una mayúscula al lado, se pueden quitar. Se nos pide la longitud de la cadena de reactivos después de reducirlo al máximo.

if __name__ == "__main__":

    with open("input.txt") as f:
        line = f.readline()
    
    line = list(line.strip())
    end = False
    while not end:
        end = True
        for i in range(1,len(line)):
            if line[i-1] != line[i] and line[i-1].lower() == line[i].lower():
                end = False
                del line[i-1]
                del line[i-1]
                break
    print("Units: %d" % (len(line)))

La versión original consistía en ir realizando elimaciones sobre la propia lista. Todo ello en un bucle que para cuando en una iteración no se modifica la cadena. Esto es extremadamente ineficiente. Tomando el código de Peter Tseng, existe una opción mejor. Se puede ir haciendo un string nuevo poco a poco comprobando si la nueva letra reacciona con la última del string nuevo. Esto tiene la ventaja de que solo hace falta una iteración para cubrir todas las reacciones. La versión mejorada es la siguiente:

def react(line):
    new = list()
    for c in line.strip():
        if len(new) > 0 and c != new[-1] and c.lower() == new[-1].lower():
            del new[-1]
        else:
            new += c
    return new

if __name__ == "__main__":

    with open("input.txt") as f:
        line = f.readline()
    line = react(line)
    print("Units: %d" % (len(line)))

Para la segunda parte se nos pida que encontremos la letra, que si eliminamos del compuesto antes de empezar la reacción, genera la cadena más pequeña. Hay que probar con todas las letras, mención especial a string.ascii_lowercase que tiene un iterador con todas las letras minúsculas del alfabeto inglés. Y posteriormente, encontrar la que de resultado inferior. Como no nos pide la letra, solamente la longitud que tendría esa cadena, no tenemos que pasar información extra.

import string

def react(line):
    new = list()
    for c in line:
        if len(new) > 0 and c != new[-1] and c.lower() == new[-1].lower():
            del new[-1]
        else:
            new += c
    return new

def min_react(line,letter):
    line = [c for c in line if c.lower() != letter]
    return len(react(line))

if __name__ == "__main__":

    with open("input.txt") as f:
        line = f.readline()
    l = react(line)

    print("Units: %d" % (len(l)))
    
    m = min([min_react(line,char) for char in string.ascii_lowercase])
    print("Minimum length: %d" % (m))

Día 6

Esto se empieza a complicar. El día 6 nos pide que sobre una cuadrícula encontremos qué punto de control está más cercano a esa posición. De los puntos de control que un número de cuadrículas cercanas finitas, encontrar cuántas cuadrículas tiene el punto de control con más cuadrículas asociadas. Para saber la distancia de una cuadrícula al punto de control se usa la distancia de Manhattan.

Lo primero es reconocer que puntos de control tienen áreas infinitas, para no tenerlos en cuenta.

Para ello, voy a calcular dos puntos extremos (esquina superior izquierda y esquina inferior derecha), dentro del rectángulo que forman estos puntos están contenidos todos los puntos de control. El objetivo es calcular las distancias de las cuadrículas justo por fuera de este rectángulo. Las cuadrículas que estén más lejos de eso no van a cambiar de punto de control, ya que el más cercano en el borde seguirá siendo el más cercano en el borde + N, ya que no hay más puntos de control fuera.

Posteriormente, empezamos a calcular las distancias de todos los puntos de la cuadrícula. Para almacenar los datos vuelvo a usar una tabla hash (defaultdict de Python), donde la clave es la coordenada X,Y y el valor es el punto de control más cercano a esa cuadrícula. Si dos puntos de control están a la misma distancia o no se ha calculado, se usa -1.

Cuando se ha calculado el punto de control más cercano, se revisa si ese punto estaba fuera del rectángulo que contiene a los puntos de control. Si está fuera, el punto de control pasa a un conjunto de puntos con infinitas cuadrículas cercanas.

Para el conteo de cuántas cuadrículas tiene un punto de control que sabemos que es finito, uso otra tabla hash, inicializada por defecto a 0, cuya clave es el identificador de punto de control y su valor, el número de cuadrículas. Después, de los valores almacenados se calcula el máximo.

from dataclasses import dataclass
from collections import defaultdict
import math

@dataclass
class Punto:
    x: int
    y: int
    owner: int

def distancia(p1,p2):
    return abs(p1.x-p2.x)+abs(p1.y-p2.y)

if __name__ == "__main__":
    with open("input.txt") as f:
        lines = f.readlines()
    puntosControl = list()
    xlist = list()
    ylist = list()
    for i,line in enumerate(lines):
        l = line.split(",")
        xlist.append(int(l[0]))
        ylist.append(int(l[1]))
        puntosControl.append(Punto(x=int(l[0]),y=int(l[1]),owner=i))
    esquinaSuperiorIzquierda = Punto(x=min(xlist),y=min(ylist),owner=-1)
    esquinaInferiorDerecha = Punto(x=max(xlist),y=max(ylist),owner=-1)

    # Los que están fuera del rango esquinaSuperiorIzquierdaxesquinaInferiorDerecha se excluyen automáticamente
    excluidos = set()
    world = defaultdict(lambda: -1)
    for i in range(esquinaSuperiorIzquierda.x-1,esquinaInferiorDerecha.x+2):
        for j in range(esquinaSuperiorIzquierda.y-1,esquinaInferiorDerecha.y+2):
            punto = Punto(x=i,y=j,owner=-1)
            distanciaMin = math.inf
            total = 0
            for p in puntosControl:
                if distancia(punto,p) == distanciaMin:
                    punto.owner = -1
                if distancia(punto,p) < distanciaMin:
                    distanciaMin = distancia(punto,p)
                    punto.owner = p.owner
                
            if i == esquinaSuperiorIzquierda.x-1 or i == esquinaInferiorDerecha.x+1 or j == esquinaSuperiorIzquierda.y-1 or j == esquinaInferiorDerecha.y+1:
                excluidos.add(punto.owner)
            if punto.owner > -1:
                world[(i,j)] = punto.owner
    conteo = defaultdict(lambda: 0)
    for p in world:
        if not world[p] in excluidos:
            conteo[world[p]] += 1
    print("Maximum finite area: %d" % max(conteo.values()))

En la segunda parte nos dicen que hay una región de puntos cuya suma de distancias a todos los puntos de control es menor a 10000. ¿Cuántos puntos forman esta región? Aquí creo que el enunciado no fue demasiado claro, ya que en un principio pensé que podría haber varias áreas, o que podría haber puntos sueltos, no conectados a la región. Sin embargo eso no pasa. Yo diseñé un algoritmo que iba visitando las celdas adyacentes, pero en realidad no hacía falta, simplemente se puede contar cuantos puntos cumplen la condición. Y se puede hacer en el mismo bucle que la primera parte.

from dataclasses import dataclass
from collections import defaultdict
import math

@dataclass
class Punto:
    x: int
    y: int
    owner: int

def distancia(p1,p2):
    return abs(p1.x-p2.x)+abs(p1.y-p2.y)

if __name__ == "__main__":
    with open("input.txt") as f:
        lines = f.readlines()
    puntosControl = list()
    xlist = list()
    ylist = list()
    for i,line in enumerate(lines):
        l = line.split(",")
        xlist.append(int(l[0]))
        ylist.append(int(l[1]))
        puntosControl.append(Punto(x=int(l[0]),y=int(l[1]),owner=i))
    esquinaSuperiorIzquierda = Punto(x=min(xlist),y=min(ylist),owner=-1)
    esquinaInferiorDerecha = Punto(x=max(xlist),y=max(ylist),owner=-1)

    # Los que están fuera del rango esquinaSuperiorIzquierdaxesquinaInferiorDerecha se excluyen automáticamente
    excluidos = set()
    world = defaultdict(lambda: -1)
    world_total = 0
    for i in range(esquinaSuperiorIzquierda.x-1,esquinaInferiorDerecha.x+2):
        for j in range(esquinaSuperiorIzquierda.y-1,esquinaInferiorDerecha.y+2):
            punto = Punto(x=i,y=j,owner=-1)
            distanciaMin = math.inf
            total = 0
            for p in puntosControl:
                if distancia(punto,p) == distanciaMin:
                    punto.owner = -1
                if distancia(punto,p) < distanciaMin:
                    distanciaMin = distancia(punto,p)
                    punto.owner = p.owner
                total += distancia(punto,p)
            if total < 10000:
                world_total += 1
                
            if i == esquinaSuperiorIzquierda.x-1 or i == esquinaInferiorDerecha.x+1 or j == esquinaSuperiorIzquierda.y-1 or j == esquinaInferiorDerecha.y+1:
                excluidos.add(punto.owner)
            if punto.owner > -1:
                world[(i,j)] = punto.owner
    conteo = defaultdict(lambda: 0)
    for p in world:
        if not world[p] in excluidos:
            conteo[world[p]] += 1
    print("Maximum finite area: %d" % max(conteo.values()))
    print("Region size: %d" % world_total)

Día 7

El día 7 se nos propone un reto muy interesante. En primer lugar, tenemos una lista de tareas que hacer en orden. Cada tarea depende de que otras hayan finalizado. Se nos pide el orden en el que se deberán hacer. Para resolver esto vamos a usar una estructura de datos nueva, el grafo dirigido. No voy a implementarlo yo, sino que voy a usar la magnífica librería networkx.

La idea es construir un grafo dirigido, con las tareas. Un nodo dirigido de C a A significa que antes de hacer la tarea A, C tiene que estar completado. Por supuesto puede darse el caso de que antes de hacer A haya que hacer C y otras tareas.

Vamos a realizar una búsqueda primero en anchura (DFS en inglés). Para ello mantenemos una lista con las tareas completadas y otra lista con las tareas que podríamos empezar a hacer. Cuando completamos una tarea vemos si las tareas a las que llevan necesitan todavía más tareas por realizar o en cambio ya pueden ser añadidas a la lista de “listas para empezar”. El enunciado nos indica que ante la situación de dos tareas listas para empezar, se usa el orden alfabético para determinar cuál va antes.

Hace falta además una lista de tareas iniciales, que pueden empezar sin esperar. Esto se hace con dos conjuntos según leemos el archivo. Se hace la diferencia entre ellos y esas tareas no tienen prerrequisitos.

import networkx as nx
import re

def read_file():
    first = set()
    second = set()
    G = nx.DiGraph()
    prog = re.compile("Step ([A-Z]) must be finished before step ([A-Z]) can begin.")
    with open("input.txt") as f:
        lines = f.readlines()
    for line in lines:
        r = prog.match(line.strip())
        if not r.group(1) in G:
            G.add_node(r.group(1))
        if not r.group(2) in G:
            G.add_node(r.group(2))
        if not G.has_edge(r.group(1),r.group(2)):
            G.add_edge(r.group(1),r.group(2))
        first.add(r.group(1))
        second.add(r.group(2))
    return (G,first- second)

if __name__ == "__main__":
    G,starter = read_file()
    path = list()
    to_visit = sorted(starter,reverse=True)

    while len(to_visit) > 0:
        node = to_visit.pop()
        path.append(node)
        neighbours = G[node]
        for n in neighbours:
            if not n in to_visit and not n in path:
                allCompleted = True
                for u,v in G.in_edges(nbunch=n):
                    if not u in path:
                        allCompleted = False
                if allCompleted:
                    to_visit.append(n)
        to_visit = sorted(to_visit,reverse=True)
    print("".join(path))

La segunda parte también es muy interesante. Se nos indica que las tareas tienen una duración de N segundos, dependiendo del valor alfabético de la letra. Además, ahora existen 5 trabajadores que pueden ir haciendo tareas en paralelo. ¿En cuánto tiempo podemos acabar todas las tareas?

import networkx as nx
import re

def read_file():
    first = set()
    second = set()
    G = nx.DiGraph()
    prog = re.compile("Step ([A-Z]) must be finished before step ([A-Z]) can begin.")
    with open("input.txt") as f:
        lines = f.readlines()
    for line in lines:
        r = prog.match(line.strip())
        if not r.group(1) in G:
            G.add_node(r.group(1))
        if not r.group(2) in G:
            G.add_node(r.group(2))
        if not G.has_edge(r.group(1),r.group(2)):
            G.add_edge(r.group(1),r.group(2))
        first.add(r.group(1))
        second.add(r.group(2))
    return (G,first- second)

def duration(step):
    return 60+ord(step)-64

if __name__ == "__main__":
    G,starter = read_file()
    path = list()
    to_visit = sorted(starter,reverse=True)

    while len(to_visit) > 0:
        node = to_visit.pop()
        path.append(node)
        neighbours = G[node]
        for n in neighbours:
            if not n in to_visit and not n in path:
                allCompleted = True
                for u,v in G.in_edges(nbunch=n):
                    if not u in path:
                        allCompleted = False
                if allCompleted:
                    to_visit.append(n)
        to_visit = sorted(to_visit,reverse=True)
    print("".join(path))

    end_letter = path[-1]
    path = list()
    to_visit = sorted(starter,reverse=True)
    
    second = 0
    workers = list()
    # Trabajo Actual, segundo que termina
    workers.append(['.',0])
    workers.append(['.',0])
    workers.append(['.',0])
    workers.append(['.',0])
    workers.append(['.',0])
    def full_workers(workers):
        full = True
        for w in workers:
            if w[0] == ".":
                full = False
        return full
    end = False
    while not end:
        if len(to_visit) == 0 or full_workers(workers):
            second += 1
        for i in range(0,len(workers)):
            if workers[i][1] <= second:
                if workers[i][0] != ".":
                    path.append(workers[i][0])
                    neighbours = G[workers[i][0]]
                    for n in neighbours:
                        if not n in to_visit and not n in path:
                            allCompleted = True
                            for u,v in G.in_edges(nbunch=n):
                                if not u in path:
                                    allCompleted = False
                            if allCompleted:
                                to_visit.append(n)
                    to_visit = sorted(to_visit,reverse=True)
                if workers[i][0] == end_letter:
                    print("Finish point")
                    print("Seconds: %d" % second)
                    end = True
                if len(to_visit) > 0:
                    node = to_visit.pop()
                    workers[i][1] = second+duration(node)
                    workers[i][0] = node
                else:
                    workers[i][0] = "."

Bien, partiendo del mismo grafo dirigido ahora vamos a hacer otro tipo de recorrido, también DFS, pero no vamos a añadir nuevos elementos a la lista de forma inmediata, sino cuando hayan sido acabados de procesar. Almacenamos los trabajadores como listas dentro de una lista de trabajadores. Cada trabajador guarda la tarea que estaba haciendo y el segundo en el que acabará. Defino una función para saber si los trabajadores están todos ocupados.

Lo primero a tener en cuenta es que el tiempo no avanza hasta que la lista de tareas que se puede realizar está vacía o los trabajadores están llenos. Luego en cada iteración del bucle, analizamos a los trabajadores. Si no han acabado, no se hace nada. Si ya han acabado y estaban con una tarea, se añade la tarea a la lista de tareas finalizadas, y se analiza si se han desbloqueado nuevas tareas disponibles para realizar. Si la tarea que ha realizado es la última tarea, se acaba el programa.

Por último si hay una tareas disponible para hacer, se la añadimos al trabajador y si no, pues le indicamos que no haga nada (así no añadimos por duplicado la tarea en el futuro).

Salida de debug que saqué en el día 7

Conclusión

Esta ha sido la primera semana del Advent of Code 2018. Como vemos, el nivel de los problemas ha ido aumentado de forma progresiva. La próxima semana comentaré las soluciones correspondientes. Tenéis todo el código hasta ahora aquí.

 

La entrada Advent of Code 2018: primera semana se publicó primero en Adrianistán.

» Leer más, comentarios, etc...

Blog Bitix

Artículo #6 de Yo apoyo al software libre

diciembre 07, 2018 11:00

Wine

La dedicación que me exige el blog para tratar de escribir, editar y publicar al menos un artículo a la semana de forma constante sin faltar ninguna semana, últimamente estoy publicando dos, se me está recompensando en forma de superar unas pocas visitas más cada mes. Y esto también se ve reflejado en los ingresos de AdSense, ya es raro el día que no me genera al menos dos euros al día salvo los fin de semanas que puede quedarse cerca. Un buen día según el número de clics y la fortuna de que en estos el coste por clic (CPC) sea generoso los ingresos pueden llegar a entre tres y cinco euros.

Uno, dos o tres euros al día no parece significativo pero multiplicado por los treinta días del mes el conjunto suma entre 30 y 50 euros que creo no está nada mal. Aunque esto por supuesto no compensa la dedicación, sin embargo, con un mucho mayor número de visitas y complementandolo con otro tipo de ingresos como afiliación de Amazon, Gearbest, PcComponentes, etc… formación, libros, y artículos patrocinados entiendo que alguien o haya medios como Xataka, Noticias 3D, Profesional Review o El chapuzas informático que puedan tener unos ingresos mucho más significativos, incluídos artículos patrocinados, y pueda ser el medio de vida de los mismos a nivel individual o como organización. Según mis cálculos con 100K visitas diarias se generan 100€ diarios solo de publicidad AdSense uno podría plantearse que fuese el medio de vida de una persona, ahora bien no es fácil conseguir ese número de visitas diarias.

Todo esto es como introducción en que debido a estos ingresos que me genera el blog en ocasiones ya desde hace un tiempo hago una pequeña donación económica a los proyectos que me resultan de interés o he usado. En esta ocasión la donación que haré será a un proyecto dedicado a hacer posible que juegos triple A y software escrito para Windows sea posible utilizarlos en GNU/Linux cuando no ofrecen soporte nativo, este proyecto es Wine.

Wine

Después de haber comprado un Intel NUC8i5BEK le he instalado Arch Linux estoy jugando a Diablo 3 en él sin ningún tipo de problema, sin cierres inesperados y con buenos fps. La gráfica Intel Iris 655 del modelo Intel Core i5-8259U no es tan potente comparada con cualquier dedicada NVIDIA o AMD pero suficiente para este juego que ya tiene unos años, si es necesario bajando la resolución o detalles gráficos.

Diablo 2 es uno de los últimos juegos que jugué y lo finalice por completo aunque solo con el bárbaro y solo en el primer nivel de dificultad. Lo jugué en mi antiguo AMD Athlon 1800+ y una NVIDIA GeForce 2 MX 440. Me gustan este tipo de juegos tipo RPG en que se va evolucionando y consiguiendo nuevas armas para personalizar el personaje, además posee buen argumento e historia.

Para jugar a Diablo 3 he necesitado instalar Wine y con este la aplicación de Battle.net para Windows e instalar Diablo 3. Blizzard ofrece el primer acto de Diablo 3 antes de comprar el juego. He podido probarlo y ver que funciona correctamente sin tener que comprarlo primero. Una vez probado que funciona es muy posible que lo compre y lo juegue completo (o esa es mi intención).

Wine es una implementación de la API de Windows con la que los programas de Windows pueden ser ejecutados en GNU/Linux y macOS, incluidos los juegos y su API DirectX. Y hace posible que tener Windows no sea imprescindible para determinados programas como Microsoft Office o los juegos que son algunos de los motivos por los que algunas personas siguen usando Windows en vez de pasarse definitivamente y de forma completa a GNU/Linux.

Cliente de Battle.net
Instalación de Diablo 3
Juego de Diablo 3

Donación

La donación no es muy grande pero espero que ayude para cubrir los costes que tengan las personas que contribuyen en el desarrollo de este proyecto. La donación se puede realizar mediante el medio de pago PayPal. Como tenía algo de dinero acumulado de un par de artículos patrocinados que escribí he utilizado $20 de ese saldo para donar a Wine.

Donación Wine

Este es el sexto artículo de esta serie de donaciones con Yo apoyo el software libre ya son unas cuantas donaciones las que he hecho, gracias también a los visitantes que llegan principalmente como resultado de una búsqueda en un buscador, en menor medida también de alguno de los que están suscritos a la fuente de artículos que en el momento que publico uno nuevo si les interesa lo visitan.

Donaciones que he realizado hasta el momento:

Fecha Proyecto Donación
2015/12 FSFE 40€
2016/09 Wikipedia, Firefox 10€, 10€
2017/01 elementaryOS, Libre Office, Arch Linux ARM 10€, 10€, 10€
2017/05 GNOME, VideoLAN (VLC), Arch Linux 15,31€, 10€, 0,31€
2018/01 LineageOS, Replicant 15€, 15€
2018/12 Wine $20
Total 145,62€, $20

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

Resaltado de sintaxis en Take Command

diciembre 07, 2018 09:30

He hablado a menudo de Take Command de JP Soft (Rex Conn). De hecho lo uso desde 1991 en los tiempos de 4DOS 4. Obviamente luego me pasé a 4OS2 y luego 4NT; y finalmente a Take Command. Curiosamente, llevaba desde 2012 en que escribí de Take Command 14 sin hablar de este reemplazo de […]

La entrada Resaltado de sintaxis en Take Command aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

» Leer más, comentarios, etc...

Picando Código

Nuevo evento de la comunidad de Datos Abiertos uruguaya: Datos y cogollos

diciembre 05, 2018 01:15

El martes 11 de diciembre a partir de las 19:00, sumate a la comunidad de Datos Abiertos uruguaya en DATOS Y COGOLLOS. Se trata de un evento organizado por la comunidad de Datos Abiertos local, así que no le pertenece a ninguna organización en particular, y está más que abierto a su sigan sumando nuevas personas y organizaciones.

Datos y Cogollos

¿Qué es?

Es un evento para juntar a toda la comunidad trabajando alrededor (o cerca, o con interés) del tema Datos Abiertos, conocer los proyectos que hay en la vuelta, pero sobretodo conocer a las personas que estamos en eso y compartir una charla acompañada por alguna bebida para hacerlo más informal.

Es un formato que empezó en México en 2013, impulsado por SocialTIC bajo el nombre de “Datos y Mezcales” y que hace bastante rato que teníamos ganas de traer a Uruguay.

Está organizado por la comunidad de Datos Abiertos local, así que no le pertenece a ninguna organización en particular y está más que abierto a que se sigan sumando nuevas personas y organizaciones.

¿Qué va a pasar?

Ésta es la agenda que vamos a intentar seguir:

  • Bienvenida y presentación de la iniciativa
  • Presentaciones relámpago ⚡ (máx. 5 min.) de proyectos y organizaciones
  • Brindis y convivencia (asado, chelas, cogollos, helado de dulce de leche, etc.)

¿Cómo me anoto para presentar mi proyecto?

Comentá en este Meetup y decinos el nombre de persona que presentaría, un correo de contacto y el nombre del proyecto/organización/presentación que quieren compartir. Vamos a ir anotando la lista de oradores/as acá, donde también podés anotarte directamente si así lo preferís: http://bit.ly/DYCpresentaciones

Las presentaciones pueden ser con diapositivas/video/apoyo audiovisual y solicitamos que cada persona que presente la suba a la web (ej. Google Slides) y pegue el enlace a su presentación en el documento anterior.

Buscamos presentaciones y presentadores/as lo más diversas posibles, empezando por apuntar a la paridad de género, así que por favor no se ofendan si preguntamos si es posible que presenten otras personas llegado el caso. ☺

¿Porqué “cogollos”?

Básicamente porque podemos… El evento adapta su nombre a alguna bebida típica del país y así pasó por México (Datos y Mezcales), El Salvador (Datos y Cervezas), Bolivia (Datos y Singanis), Ecuador (Datos y Bielas), Colombia (Datos y Guaros), Costa Rica (Datos y Chiliguaros), Guatemala (Datos y Tragos) y España (Datos y Cañas).
Cuando le toca a Uruguay, optamos por algo que ninguna de las organizaciones amigas de todos esos países podría poner legalmente (o sea, es un poco un chiste interno).

¿Es obligatorio presentar/saber de datos abiertos/tomar/fumar porro?

No, para nada. Cada uno viene y hace lo que quiere 🤗

Visitá el evento en Meetup

» Leer más, comentarios, etc...

Adrianistán

Conclusiones de la visita de Richard Stallman a Valladolid

diciembre 04, 2018 11:21

Richard Stallman, el padre del software libre, vino a visitarnos a la ciudad de Valladolid. La oportunidad de conocer a tal personaje en primera persona era única, así que no dudé en asistir, con la suerte que tuve de poder estar en primera fila durante la conferencia.

La conferencia no contaba nada nuevo, nada que no supiese cualquier persona que haya leído un poco sobre la idea del software libre, pero se hizo amena. Stallman explica muy bien y las diapositivas están muy bien hechas. Tiene bastantes chistes precocinados pero que causan buen impacto en la audiencia.

Pero Stallman es un personaje. Hablando con la gente que cenó con él el día anterior, algunos me contaban que había sido bastante irrespetuoso, sacando el portátil durante la cena para hacer sus cosas y gritar de vez en cuando que no escuchaba bien.

Durante la charla ha estado bebiendo de su té, descalzo y hasta el momento de empezar ha seguido mandando correos. Le noté bastante envejecido, caminaba medio cojo y se le notaba la marca de la operación en el brazo. Para empezar a puesto su famosa versión de Guantanamera.

La presentación ha seguido explicando las cuatro libertades del software libre, de forma bastante extensa, explicando para todos los públicos por qué son importantes.

Durante la charla también ha hablado del software malévolo, no confundir con privativo. Según él, son dos cosas distintas, pero relacionado. Puede haber software honesto privativo y software malévolo libre, pero son minorías en la práctica. También ha hablado del software privado, que es perfectamente ético, y que hasta él programa software privado. La diferencia es que el software privado nunca se distribuye fuera del propio autor u organización. Bastante parte de la charla se ha centrado en esta parte, tocando el tema de la privacidad a fondo. Para Stallman la recolección de datos personales debería estar prohibida.

Para ilustrar este punto, ha puesto como ejemplo algo que le ha horrorizado, el sistema de parquímetros de Valladolid. Según él son horribles, no porque haya que pagar, que es algo a lo que está dispuesto, sino porque hay que poner la matrícula del vehículo. Poner la matrícula en el parquímetro lo que sirve es para rastrear a la gente. Este tipo de acciones nos acercan cada vez más a la dictadura y a la destrucción de los derechos humanos.

Personalmente el ejemplo me parece bastante exagerado y aunque veo su punto, creo que la recolección de datos personales puede ser necesario en ciertas situaciones, siempre que se traten de forma adecuada.

También ha habido tiempo para hablar de historia. Habló de la historia de GNU, como Linux al principio tenía una licencia no libre (se impedía su redistribución comercial), pero que al poco cambió a GPL 2, siendo el candidato perfecto para el proyecto GNU. Ha comentado que Hurd tiene un diseño muy elegante, moderno pero quizá fue demasiado complicado y que en perspectiva fue un error diseñarlo de esa forma. Ha dicho que Hurd no es usable para nada práctico ahora mismo.

Ha insistido mucho en que el sistema operativo se llama GNU con Linux.

También ha hablado del open source. Y cuando le proclaman padre del open source afirma: “si soy el padre es porque han hecho la reproducción invitro con semen mío robado”. Afirma que la gente del open source tiene otra ideología, mucho más pragmática, pero incompleta, ya que no preserva las libertades.

Ha hablado de licencias libres: débiles y la GPL. De entre las débiles recomienda la Apache, aunque por supuesto la mejor es la GPL, en sus distintas versiones. Ha confirmado que nadie está trabajando en la GPL4. Aquí ha aprovechado para criticar a GitHub que ha seguido una muy mala costumbre de no prestar atención a las licencias del software que aleja. Además indica que es necesario poner un comentario en cada archivo con la licencia que sigue. Eso de poner un archivo con la licencia en la carpeta raíz no es suficiente según Stallman.

Esto lo ha enlazado con LibreJS, el complemento que detecta si el código JavaScript de una página es libre o no y lo bloquea o no según esto. Stallman no ha criticado en ningún momento JavaScript, simplemente que tiene que respetar los mismos criterios de los programas nativos para ser software libre.

También ha hablado de distros libres, metiéndose con Ubuntu bastante. Reconoce que estos usuarios están más cerca de la libertad que si usasen Windows o macOS pero que todavía les falta un poco. Y lo peor para Stallman es que mucha gente cree que sistemas como Ubuntu son libres y la gente se queda en ellos.

Por último ha hablado del software libre en la educación, también ha recomendado a la universidad tener una asignatura de ingeniería inversa (o retroingeniería como él lo llama).

Después de esto ha proseguido con el momento más cómico de la charla, se puso su túnica y su aureola y empezó a hablar de la religión de Emacs.

Nos bendijo nuestros ordenadores, y habló de como formar parte de la Iglesia del tan importante Emacs. No es necesario guardar celibato para ser santo pero hay varias cosas, como el peregrinaje de Emacs (saberse todas las combinaciones de teclado de memoria) o los cismas actuales (¿cuál es la tecla más importante en Emacs?). También ha dedicado palabras a los Vimeros como yo. Usar Vi no es pecado, es una penitencia. Y eso a pesar de que VIVIVI es el número de la bestia. También contó como en China le intentaron atacar unos seguidores de Vi, pero tenía sentido porque la violencia empieza por vi.

Después ha subastado un ñu, poniendo caras para que no dejásemos al ñu solo. Además de incidir en que no puede haber pingüinos solos, tiene que haber ñus acompañándolos.

Por último la ronda de preguntas. Mi pregunta ha sido ¿Las redes neuronales pueden ser software libre? Su respuesta ha sido que existen herramientas para alterar los valores numéricos de estas redes, mucho más fácil que la ingeniería inversa. Por tanto no sería técnicamente lo mismo. Creo que lo ha puesto al nivel de una fotografía o un vídeo, donde la edición es más sencilla y no tiene sentido hablar de fotografía libre.

También se ha preguntado por Microsoft y su deriva open source. Stallman celebra que publique cosas con licencias de software libre, pero eso no quita que siga teniendo software privativo que espía como Windows.

Le han preguntado por un teléfono que respete la privacidad. Según él es imposible, aunque se está intentando con un interruptor que permita desconectar la conexión móvil de forma física. El problema es de la tecnología móvil (no importa que sean smartphones o no), que para enrutar los paquetes guarda la ubicación de los dispositivos. El propósito era muy inocente pero se puede usar para espiar. Eso sí, él ha usado teléfonos móviles, siempre cuando necesita llamar pregunata a alguien que haya alrededor si le pueden dejar el teléfono.

Por último, sobre el hardware libre ha dicho que el concepto es erróneo. No puede existir hardware libre. El software libre existe, porque se puede copiar de forma exacta, en el mundo físico eso no pasa. Habría que hablar de hardware con diseños libres, pero ningún objeto podrá ser hardware libre o hardware privativo.

La entrada Conclusiones de la visita de Richard Stallman a Valladolid se publicó primero en Adrianistán.

» Leer más, comentarios, etc...

Metodologías ágiles. De lo racional a la inspiración.

Open Space, para un roto o para un descosido

diciembre 04, 2018 09:54

Hemos hablado varias veces aquí ya de los Open Space como un formato increible para la organización y facilitación de conferencias. En Agile-Spain hemos organizado ya tres a nivel nacional, y han surgido multitud de pequeños "opens" para tratar muchos temas alrededor del agilismo. Si no sabes qué es un Open Space, echa un ojo a esta explicación, o no entenderás demasiado el siguiente post :) Pero

» Leer más, comentarios, etc...

Fixed Buffer

Depuración remota sobre SSH

diciembre 04, 2018 09:00

Depuración remota sobre SSHDespués de la entrada sobre como instalar NetCore en Linux (o sobre cualquier otro entorno), el siguiente paso lógico es poder depurar nuestro código mientras corre en Linux, como lo haríamos sobre Windows.

Para esto, tenemos 2 opciones, utilizar Visual Studio Code o Visual Studio para hacer depuración remota sobre ssh,. En este caso, vamos a utilizar la segunda opción, ya que en la mayoría de los casos en los que usemos NetCore, correrá en un servidor sin interfaz gráfica.

Para ello, lo primero que necesitamos es que nuestro equipo Linux tenga un servidor SSH habilitado, esto es lo que ocurre habitualmente, pero en caso de no haberlo puesto durante la instalación del OS, se puede añadir sin problema.

Una vez dicho esto, vamos con ello.

Proyecto

Para poder seguir la entrada, lo primero que vamos a hacer es crear un proyecto de consola de NetCore:

consola .net core

Al que le vamos a poner este código:

using System;
using System.Threading;

namespace PostDepuracionRemota
{
    class Program
    {
        static void Main(string[] args)
        {
            int nIteraciones = 100;
            Console.WriteLine($"El programa añadirá {nIteraciones} lineas de consola, con una espera de 1 segundo entre lineas");
            for(int i = 0; i < nIteraciones; i++)
            {
                Console.WriteLine(DateTime.Now);
                Thread.Sleep(1000);
            }
        }
    }
}

Es un código muy simple, ya que la finalidad es probar la  depuración remota sobre SSH, simplemente, ejecutara 100 veces el Console.WriteLine, mostrando por pantalla la hora en la que estamos. Con esto, vamos a ganar el tiempo que necesitamos para asociarnos al proceso remoto. Si lo ejecutamos tal cual, tendremos una salida parecida a esta:

Ejemplo depuración remota

Una vez que tenemos esto, lo colocamos en nuestro servidor, y vamos al lío!!!

Depuración remota sobre SSH

Visual Studio 2017 añade varias opciones para asociarnos a un proceso en ejecución, para ello, desde el menú “Depurar”, vamos a la opción “Asociar al proceso…”, o pulsamos el atajo “Ctrl+Alt+P

Asociar al proceso

Eso, nos va a mostrar una nueva ventana:

VentanaAsociar

En ella, seleccionamos el tipo de conexión SSH y ponemos la dirección del servidor. Con eso, al pulsar actualizar, se nos mostrará una nueva ventana para pedirnos los datos de conexión:

ventanaDatosSSH

Basta con que rellenemos los campos con los valores de conexión a nuestro servidor, y acto seguido,  (igual hay que pulsar sobre actualizar) se nos mostraran los procesos activos en el servido:

ProcesosActivos

Vamos a utilizar la opción de filtro, para buscar los procesos “dotnet”:

filtro

Seleccionamos el proceso que queremos depurar, y esto nos lanza una ultimo ventana, en la que nos pide que le indiquemos como queremos depurar:

Modo

Tenemos que seleccionar “Managed”. Al seleccionar el proceso, vemos que se asocia a la ejecución, y nos permite depurar el ejecutable de manera remota sobre ssh:

Breakpoint

Aclaraciones

En caso de necesitar instalar componentes, Visual Studio se encarga de descargarlos e instalarlos sin necesitar nada por nuestra parte. Esto es así siempre, salvo que nos falten paquetes que se consideran básicos, como pueden ser el servidor SSH, curl o wget, o unzip. En caso de que nos falte alguno o todos ellos,  debemos instalarlos nosotros, o cuando intentemos asociarnos se mostrara un error parecido a este:

Error Paquetes

/home/username/.vs-debugger/GetVsDbg.sh -v vs2017u5 -l “/home/username/.vs-debugger/vs2017u5”

Esto se soluciona fácilmente instalando todo lo que nos falte con el comando:

sudo apt-get install openssh-server unzip curl

Espero que haya sido de utilidad, aunque le daremos uso en siguientes entradas cuando creemos DLL de C++ y las consumamos en NetCore, para poder comprobar en caso de que tengamos algún problema.

**La entrada Depuración remota sobre SSH se publicó primero en Fixed Buffer.**

» Leer más, comentarios, etc...

Picando Código

Montevideo: Conferencia Internet y Derechos Humanos

diciembre 03, 2018 07:00

DATYSOC nos invita al siguiente evento:

Conferencia Internet y Derechos Humanos

El miércoles 13 de diciembre les invitamos a participar de una conferencia sobre Internet y Derechos Humanos.
A partir de las 17:30 horas y hasta las 19:30 les esperamos en el Espacio Interdisciplinario de la Universidad de la República en José Enrique Rodó 1843.

Se presentan Veridiana Alimonti, Analista Senior de Políticas en América Latina de la EFF. y Beatriz Busaniche, Presidenta de Fundación Vía Libre, Argentina.

Entrada libre y gratuita, y los esperamos con un brindis luego de la conferencia.

 

» Leer más, comentarios, etc...

Poesía Binaria

Crear redirecciones en Apache en función de una cookie

diciembre 03, 2018 08:56

Servidor de pruebas

En estos días que asusta leer la palabra cookie en cualquier sitio. Si nos remontamos al siglo pasado, los navegadores solían preguntarnos cada vez que iban a a definir una cookie para ver si aceptábamos o no. Cosa que se terminó volviendo insostenible ya que los sitios web definían varias cookies y empotraban contenido de otros sitios que querían definir cookies y terminábamos con unas diez ventanas emergentes cada vez que entrábamos en una web. Así que, bloqueabas todas, pero las webs no funcionaban bien, y terminabas resignándote y aceptando todo.

Aunque hoy vamos a tratar un tema diferente, vamos a darles a las cookies una utilidad extra. Aunque bien podemos conseguir el mismo efecto con un lenguaje de aplicación (Java, PHP, Python, etc), vamos a hacer que sea el mismo servidor web, en este caso Apache el que aplique una redirección en función del valor que tenga una cookie que nos manda el usuario y, un paso más, que sea transparente para él.

¿Para qué queremos esto?

Podemos utilizar esta técnica para tests A/B, para probar dos versiones de la web. Aunque vamonos a un caso extremo, pero real. Imaginemos que tenemos una versión antigua de la web, programada con versiones antiguas de un lenguaje de programación y también una versión nueva programada con versiones nuevas, bibliotecas nuevas, etc, de modo que esas dos versiones no pueden estar en la misma máquina (física o virtual), de modo que tendríamos que acceder a un punto de entrada nuevo y diferente.

También podemos hacer un acceso a un entorno de pruebas de la web, de modo que los desarrolladores puedan entrar a él, o incluso el cliente final, mientras no impedimos el acceso a los usuarios a la plataforma, haciendo que, el código sea diferente, las bases de datos sean diferentes, incluso las posibilidades de cargarnos algo también. Muchos desarrollos pueden estar pensadas para realizar pruebas, pero pensemos en plataformas como WordPress, que por unas cosas o por otras, en las que no voy a entrar, si cambiamos el host, con muchos plugins o temas podemos tener problemas.

Y, ¿qué tal el dar una seguridad extra a nuestras aplicaciones? De forma que solo alguien que tenga la cookie pueda entrar en un área privada en donde podrá trastear con muchas cosas peligrosas de su servidor adicionalmente a tener usuario y contraseña.

En muchos casos seguro que nos puede ser útil hacer una redirección en función de la dirección IP desde la que viene la petición, aunque en otros casos, tanto por el número de equipos que tienen que tener acceso como por el hecho de tener IPs dinámicas, no es posible.

Vamos a ver Apache

Para lograr esto, entramos en el VirtualHost de Apache y escribimos lo siguiente:

1
2
3
4
5
6
        ProxyPreserveHost On
        RewriteEngine On

        RewriteCond %{HTTP_COOKIE}     nombre_de_cookie=([^;]+)
        RewriteCond %1                 ^valor_de_cookie$
        RewriteRule         ^/(.*) http://otro_host/$1 [P,L]

En este caso, con la directiva P para conectar vía proxy y L para que no se procesen más reglas de reescritura tras esta. Es necesario también tener los módulos rewrite y mod_proxy_http instalados por lo que antes de nada no está de más ejecutar:

sudo a2enmod rewrite
sudo a2enmod proxy_http
sudo service apache2 restart

Veamos un ejemplo completo, podemos ver este archivo de VirtualHost (lo he puesto todo en HTTP, en el mundo real deberíamos utilizar todos HTTPs):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<VirtualHost *:80>
        ServerName miweb.com
        ServerAlias www.miweb.com

        ServerAdmin security@miweb.com
        DocumentRoot /var/www/miweb.com/www
        ProxyPreserveHost On
        RewriteEngine On

        RewriteCond %{HTTP_COOKIE}     magicCookie=([^;]+)
        RewriteCond %1                 ^123456$
        RewriteRule         ^/(.*) http://10.0.1.198/$1 [P,L]

       <Directory /var/www/miweb.com/www/ >
                Options -Indexes +FollowSymLinks +MultiViews
                AllowOverride All
                Require all granted
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error.log

        LogLevel warn

        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

En este caso, cuando entra un visitante normal, entrará directamente a la web alojada en este servidor. Web que está en /var/www/miweb.com/www, pero si viene un visitante que tenga la cookie magicCookie establecida y cuyo valor sea 123456, internamente la petición se redirigirá al 10.0.1.198, que es una IP privada que está en la misma red que el servidor y a la que normalmente los usuarios no tendrían acceso. Esta nueva dirección puede pertenecer a una máquina de la misma red, una máquina conectada a Internet, máquinas virtuales o incluso contenedores docker.

Vamos a probarlo

Para realizar pruebas, podemos crear un pequeño programa que defina la cookie en cuestión, por ejemplo, en PHP podríamos hacer algo así:

1
2
3
<?php

setcookie('magicCookie', '123456');

Solo tenemos que llamar a este archivo php desde el navegador y cuando volvamos a nuestro dominio se realizará la redirección interna.

También podemos utilizar una extensión de Chrome o Chromium llamada Cookie Inspector. Solo tenemos que activar las herramientas para desarrolladores y nos dejará establecer y modificar cookies.

Foto principal: unsplash-logoLouis Reed

The post Crear redirecciones en Apache en función de una cookie appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 340

diciembre 03, 2018 07:55

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Meta-Info

¿Que es?

Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

rss subscripción

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

Idea: Juanjo Navarro

Diseño: Albin