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();
}
}
Muy buenas.
ResponderEliminarMe 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))
));
}