11.       Devuelva un Map que asocie a cada TipoCuadrante un SortedSet con los puntos de ese cuadrante ordenados por distancia al origen.

// Esquema de construcción de un Map donde el tipo value es mutable
public Map<TipoCuadrante, SortedSet<Punto>> getPuntosPorCuadrante(){
  Map<TipoCuadrante, SortedSet<Punto>> res = new
                 HashMap<TipoCuadrante, SortedSet<Punto>>();
                
  for (Punto p: nube){
          TipoCuadrante clave = p.getCuadrante();
         if(res.containsKey(clave)){
                 res.get(clave).add(p);
         }
         else{
              SortedSet<Punto> sp = new TreeSet<Punto>(
                    new ComparadorDistanciaOrigen());
             sp.add(p);
             res.put(clave, sp);
         }
  }
  return res;
}

// En Java 8 un Map se crea mediante el método groupingBy de Collectors al
// que le proporcionamos la propiedad por la que se forman las claves
// (getCuadrante) y que el conjunto de valores está formado por una
// Collection mediante un conjunto ordenado por un Comparator  
public Map<TipoCuadrante, SortedSet<Punto>> getPuntosPorCuadrante(){
         return nube.stream().
         collect(Collectors.groupingBy(Punto::getCuadrante,
                         Collectors.toCollection(()->new TreeSet<Punto>(
                                 new ComparadorDistanciaOrigen()))));
  }

 

12.       Devuelva un Map que asocie a cada TipoCuadrante el número de puntos que hay de ese tipo.

// esquema de construcción de un Map donde los values son inmutables.
// El tipo contador es Long para hacerlo compatible con Java 8
public SortedMap<TipoCuadrante,Long> getNumPuntosPorCuadrante(){
  SortedMap<TipoCuadrante, Long> res =
                          new TreeMap<TipoCuadrante, Long>();    
  for (Punto p: nube){
          TipoCuadrante clave = p.getCuadrante();
         if(res.containsKey(clave)){
                 Long val=res.get(clave);
                 val++;
                 res.put(clave, val);
         }
         else{
                 res.put(clave, 1L);
         }
  }
  return res;
}

// En Java 8 de nuevo invocamos a groupingBy, el supplier del TreeMap nos
// hace devolver un SortedMap y en el conjunto de valores se calcula como
// un contador mediante el método counting
public SortedMap<TipoCuadrante,Long> getNumPuntosPorCuadrante(){
  return nube.stream().
         collect(Collectors.groupingBy(
          Punto::getCuadrante,
                 TreeMap::new,
                 Collectors.counting()));
}

13.       Devuelva un SortedMap que asocie a cada TipoDiana un Set con los puntos que le corresponden.

// Este es un esquema similar a ejercicio 11, cambia que el Map es
// ordenado y los elementos del conjunto Values es un Set en vez
// de un SortedSet, pero eso no cambia nada del método salvo
// la invocación a los constructores
public SortedMap<TipoDiana, Set<Punto>> getPuntosPorDiana(){
  SortedMap<TipoDiana, Set<Punto>> res =
            new TreeMap<TipoDiana, Set<Punto>>();
  for (Punto p: nube){
      TipoDiana clave = p.getPosicionDiana();
      if(res.containsKey(clave)){
          res.get(clave).add(p);
      }
      else{
          Set<Punto> sp = new HashSet<Punto>();
          sp.add(p);
         res.put(clave, sp);
     }
  }
  return res;
}

// En Java 8 cambia respecto de la solución del 11 que hay que proporcionar
// el supplier del TreeMap (Como ya hicimos en el 12) y que al ser un Set
// es más fácil, pues basta con invocar al método toSet de Collectors
// (y no al método general toCollection de la solución del ejercicio 11.
public SortedMap<TipoDiana, Set<Punto>> getPuntosPorDiana(){
  return nube.stream().
         collect(Collectors.groupingBy(
                 Punto::getPosicionDiana,
                 TreeMap::new,
                 Collectors.toSet()));
}

14.       Dados dos valores vx e vy desplace toda la nube en la dirección y sentido indicados por el vector (vx,vy)

public void moverDireccion(Double vx, Double vy){
  for(Punto p:nube){
          p.moverDireccion(vx, vy);
  }
}

public void moverDireccion(Double vx, Double vy){
  nube.stream().forEach(x->x.moverDireccion(vx,vy));
}

 

15.       Dado un valor de TipoCuadrante devuelva un Punto con la suma de todas las coordenadas de los puntos de ese cuadrante.

// este problema es prácticamente igual al ya se ha resuelto en el
// ejercicio 4 y en Java 7 no aporta ninguna novedad
public Punto getPuntoSuma(TipoCuadrante t){
  Double sx=0.,sy=0.;
  for(Punto p:nube){
          if (p.getCuadrante().equals(t)){
                 sx=sx+p.getX();
                 sy=sy+p.getY();
         }
  }
  return new PuntoImpl(sx,sy);
}

// Sin embargo en Java 8 es un interesante ejercicio de aplicación
// del método reduce con un operador binario (caso particular de
// BiFunction). Si definimos el BinaryOperator que devuelve el
// Punto suma de dos, entonces proporcionamos al método reduce el
// elemento neutro y el operador binario para que devuelva la
// operación acumulada sobre todos los elementos del stream que
// pasan el filtro.
BinaryOperator<Punto> sumaCoord = (x,y)-> {
      Double nx = x.getX() + y.getX();
      Double ny = x.getY() + y.getY();
      return new PuntoImpl(nx,ny);
};
public Punto getPuntoSuma(TipoCuadrante t){
     return nube.stream().
          filter(x->x.getCuadrante().equals(t)).
          reduce(new PuntoImpl(0.,0.), sumaCoord);
}

// De hecho de haber definido anteriormente el operador binario sumaCoord
// el ejercicio 4 podría haberse resuelto de esta manera:
public Punto getCentroGravedad() {
  Punto m = nube.stream().reduce(new PuntoImpl(0.,0.), sumaCoord);
  m.setX(m.getX()/nube.size());
  m.setY(m.getY()/nube.size());
  return m;
}

 

16.       Devuelva un SortedMap que asocie a cada TipoCuadrante el Punto cuyas coordenadas sean la suma de todas las coordenadas de los puntos de ese cuadrante.

// Este problema es una generalización del anterior para conseguir
// relacionar cada Cuadrante con su centro de masa resultado de sumar
// todos los puntos en uno solo.
// Nótese que la media (centro de gravedad) no sería posible de
// calcular hasta el final y que deberíamos invocar al método que
// nos devuelve el número de puntos en cada cuadrante para hallar
// la media. Obsérvese que el tipo de los objetos de values es
// mutable, por lo que es necesario crear uno nuevo cada vez,
// ya que modificar el Punto de values conllevaría indeseados
// efectos laterales
public SortedMap<TipoCuadrante, Punto> getPuntoSumaPorCuadrante(){
    SortedMap<TipoCuadrante, Punto> res =
                 new TreeMap<TipoCuadrante, Punto>();
               
    for (Punto p: nube){
        TipoCuadrante clave = p.getCuadrante();
        if(res.containsKey(clave)){
            Punto val = res.get(clave);
            val = new PuntoImpl(val.getX()+p.getX(),val.getY()+p.getY());
            res.put(clave, val);
        }
        else{
            res.put(clave, p);
        }
}
return res;
}

// En Java 8 es una solución similar a los problemas 12 y 13, pero usando
// el método reducing de Collectors para aprovecha el operador binario
// sumaCoord definido en la solución del ejercicio 15
public SortedMap<TipoCuadrante, Punto> getPuntoSumaPorCuadrante(){
    return nube.stream().
        collect(Collectors.groupingBy(
                Punto::getCuadrante,
                TreeMap::new,
                Collectors.reducing(new PuntoImpl(0.,0.),sumaCoord)));
}


17.       Devuelva un SortedMap que asocie a cada TipoCuadrante el Punto centro de gravedad de los puntos de ese cuadrante.

// Reutilizandos los Map resultado de la suma de puntos y el número de
// puntos por Cuadrante basta con cambiar el punto suma por el punto medio
public SortedMap<TipoCuadrante, Punto>getCentroGravedadPorCuadrante(){
        SortedMap<TipoCuadrante, Punto> m1 = getPuntoSumaPorCuadrante();
        SortedMap<TipoCuadrante, Long> m2 = getNumPuntosPorCuadrante();
         
        for (TipoCuadrante k:m1.keySet()){
                Punto sum = m1.get(k);
                Long num = m2.get(k);
                sum.setX(sum.getX()/num);
                sum.setY(sum.getY()/num);
        }
        return m1;
}      

// En Java 8 el for para recorrer los cuadrantes de las claves del Map
// se implementa mediante un método forEach
public SortedMap<TipoCuadrante, Punto>getCentroGravedadPorCuadrante(){
        SortedMap<TipoCuadrante, Punto> m1 = getPuntoSumaPorCuadrante();
        SortedMap<TipoCuadrante, Long> m2 = getNumPuntosPorCuadrante();
         
        m1.keySet().forEach(k->m1.get(k).setX(m1.get(k).getX()/m2.get(k)));
        m1.keySet().forEach(k->m1.get(k).setY(m1.get(k).getY()/m2.get(k)));
        return m1;
}


18.       Proporcione a la clase NubePuntos un constructor público tal que reciba el nombre de un fichero conteniendo en cada línea dos valores reales separados por una coma con las coordenadas de los puntos y un constructor privado que reciba una List<Punto> para usarlo en el ejercicio 10.

// En Java 7 mantenemos la lectura tradicional mediante el método
// estático leeFicheroPorLinea en la clase Util que devuelva el
// fichero leído por líneas, siendo cada línea las coordenadas de
// un Punto
private NubePuntosImpl(List<Punto> lis){
      nube = lis;
}
public NubePuntosImpl(String nomFile){
      List<String> ls = Util.LeeFicheroPorLinea(nomFile);
      nube = new ArrayList<Punto>();
      for (String linea:ls){
              Punto p = new PuntoImpl(linea);
              nube.add(p);
      }
}

// en la clase Util
public static List<String> LeeFicheroPorLinea(String fileName) {
      List<String> listaleida = new LinkedList<String>();
      try {
              Scanner sc = new Scanner(new File(fileName));
              while (sc.hasNextLine()){
                      listaleida.add(sc.nextLine());
              }
              sc.close();
      } catch (FileNotFoundException e){
              System.out.println("Fichero no encontrado"+fileName);
      }      
      return listaleida;
 

// En Java 8 la lectura de un fichero por líneas se hace mediante
// el método lines de la clase Files. Convertir el stream de String
// a List de Punto es trivial con los métodos map (y el supplier
// del constructor de Punto a partir de un String) y el
// método collect.
public NubePuntosImpl2(String nomFile){
   try {
      nube = Files.lines(Paths.get(nomFile)).
                      map(x->new PuntoImpl(x)).
                      collect(Collectors.toList());
      } catch (IOException e) {
                      e.printStackTrace();
      }
}

1 comentario:

  1. Muy buenas.
    Me gustaría compartir mi solución al problema 17 aprovechando la funcionalidad de Collectors.collectingAndThen.

    Añado en el comentario el código pero añado un enlace al commit donde publico la solución: https://github.com/AlbertFX91/java8-exercises/commit/c902f30d991c9b7a590c6da757fcdc1d20e9e7eb

    Sería un placer recibir feedback sobre ella

    private Punto centroGravedad(Collection puntos){
    return PuntoImpl.create(
    puntos.stream().collect(Collectors.averagingDouble(Punto::getX)),
    puntos.stream().collect(Collectors.averagingDouble(Punto::getY)));
    }
    public SortedMap getCentroGravedadPorCuadrante(){
    return puntos.stream()
    .collect(Collectors.groupingBy(
    Punto::getCuadrante,
    TreeMap::new,
    Collectors.collectingAndThen(Collectors.toSet(), x->centroGravedad(x))
    ));
    }





    ResponderEliminar