add exercise point sorter

- implement sort by radius
This commit is contained in:
Raffaele Mignone 2019-03-28 21:32:16 +01:00
parent 46daa16e3c
commit aeb32837a9
Signed by: norangebit
GPG Key ID: F5255658CB220573
2 changed files with 162 additions and 0 deletions

View File

@ -0,0 +1,156 @@
---
title: Ordinamento di punti cartesiani in base al loro angolo polare
author: Raffaele Mignone
lang: it-IT
subject: Ordinamento
keywords:
- Higher Order Functions
- Kotlin
- Sorting
papersize: a4
---
# Ordinamento di punti
## Tracia
Scrivere un programma per ordinare un insieme di punti rispetto allangolo polare che formano con un punto dato P.
Valutare la complessità.
![Esempio di ordinamento](figures/pointSorter.pdf)
## Soluzione
I punti cartesiani non presentano alcun ordinamento naturale, quindi implementare di la classe `Point` usando l'interfaccia `Comparable` renderebbe la nostra astrazione non coincidente con la realtà e la soluzione specifica per questo problema e non riutilizzabile.
La libreria standard di Java ci consente di eseguire ordinamenti su oggetti che non implementano l'interfaccia `Comparable`, o in base a criteri non specificati in essa, attraverso la classe `Comparator`.
Analizzando la struttura interna della classe `Comparator` si nota immediatamente la sua natura di wrapper rispetto alla strategia di confronto racchiusa nel metodo `compare`.
Questa soluzione si rendeva necessaria in linguaggi strettamente OO, ma ciò non è più necessario nei linguaggi che supportano le *Higher Order Functions* come kotlin.
Per questo motivo all'interfaccia `Sorter` (@lst:sorter) è stata aggiunta la funzione `compareWith` che oltre ad accettare un array di oggetti (non è necessario che siano di tipo `Comparable`) accetta anche una funzione `compare` che ricevuti due oggetti in input restituisce un intero.
```{#lst:sorter .kotlin caption="Interfaccia utilizzata dagli algoritmi di ordinamento"}
interface Sorter {
fun <T : Comparable<T>> sort(array: Array<T>)
fun <T> sortWith(array: Array<T>, compare: (T, T) -> Int)
}
```
Nella classe `Point` (@lst:point) è stata implementata la funzione `theta` che tramite l'ausilio della funzione di libreria `Math.atan2`, restituisce l'angolo[^angolo] formato con l'origine degli assi cartesiani.
Tramite un companion object[^companion-object] è stata implementata la funzione `thetaComparator` che accetta come parametri tre punti e esegue il confronto tra gli angoli formati dai primi due punti rispetto all'origine indicata dal terzo punto.
[^angolo]: L'angolo viene restituito nel range $[-\pi, \pi]$, ma per poter eseguire un confronto dovremo portarlo nell'intervallo $[0, 2\pi[$.
[^companion-object]: Le funzioni dichiarate all'interno di un companion object possono essere equiparate ai metodi statici di Java.
```{#lst:point .kotlin caption="Classe Point"}
data class Point(val x: Double, val y: Double) {
fun theta(): Double = Math.atan2(y, x)
companion object {
fun thetaComparator(
p1: Point,
p2: Point,
origin: Point = Point(0, 0)
): Int {
var theta1 = (p1 - origin).theta()
var theta2 = (p2 - origin).theta()
if (theta1 < 0) theta1 += 2 * Math.PI
if (theta2 < 0) theta2 += 2 * Math.PI
return theta1.compareTo(theta2)
}
}
}
```
A questo punto è possibile eseguire il sort di un array di punti attraverso un sorter come mostrato nel @lst:main [^eccezioni]
[^eccezioni]: La funzione `main` non gestisce le eccezioni.
```{#lst:main .kotlin caption="Ordinamento e stampa di punti letti da file"}
fun main(args: Array<String>) {
if (args.size < 4 || args.size % 2 != 0) return
val origin = Point(args[0].toDouble(), args[1].toDouble())
val list = ArrayList<Point>()
for (i in 2 until args.size step 2)
list.add(Point(args[i].toDouble(), args[i + 1].toDouble()))
val points = list.toTypedArray()
val compare = { p1: Point, p2: Point ->
Point.thetaComparator(p1, p2, origin)
}
Mergesort.sortWith(points, compare)
println("origin in $origin")
points.forEach {
println(it)
}
}
```
## Caratterizzazione della complessità
A differenza della funzione `sort` applicata ad un array di `Comparable` l'implementazione attuale differisce solo nel modo in cui viene eseguito il confronto tra gli oggetti, per cui la complessità complessiva dell'algoritmo coincide con quella dell'algoritmo di sorting utilizzato.
Nel caso del @lst:main si è usato il `Mergesort` per cui si ha una complessità di best, average e worst case pari a $n\log n$.
## Sort by
Nel caso di oggetti privi di un ordinamento naturale è possibile percorrere una via alternativa all'utilizzo di un `Comparator`.
Invece di passare la funzione che esegue la comparazione possiamo passare una funzione che si occupa di *estrarre* dal nostro oggetto un oggetto di tipo `Comparable` e usare quest'ultimo come riferimento durante le operazioni di confronto.
Per fare ciò è stata aggiunta la funzione `sortBy` all'interfaccia `Sorter`.
```kotlin
fun <T, C : Comparable<C>> sortBy(
array: Array<T>,
compareBy: (T) -> C
)
```
Nel @lst:main2 viene mostrato l'utilizzo della funzione `sortBy` per ordinare i punti in base al raggio.
Inoltre essendo l'algoritmo di mergesort stabile avremo un ordinamento in base all'angolo *theta* e a parità di angolo in base al raggio.
Per gli stessi ragionamenti di cui sopra la complessità totale dell'algoritmo non cambia.
```{#lst:main2 .kotlin caption="Ordinamento in base all'angolo e a parità di angolo in base al raggio"}
fun main(args: Array<String>) {
if (args.size < 4 || args.size % 2 != 0) return
val origin = P(args[0].toDouble(), args[1].toDouble())
val list = ArrayList<Point>()
for (i in 2 until args.size step 2)
list.add(P(args[i].toDouble(), args[i + 1].toDouble()))
val points = list.toTypedArray()
Mergesort.sortBy(points) {
(it - origin).r()
}
val compare = { p1: Point, p2: Point ->
Point.thetaComparator(p1, p2, origin)
}
Mergesort.sortWith(points, compare)
println("origin in $origin")
points.forEach {
println(it)
}
}
```
## Source code
- Interfaccia [Sorter](https://github.com/norangebit-unisannio-computer-science/lm-tecniche-di-programmazione/blob/master/src/main/kotlin/it/norangeb/algorithms/sorting/Sorter.kt)
- Oggetto [Mergesort](https://github.com/norangebit-unisannio-computer-science/lm-tecniche-di-programmazione/blob/master/src/main/kotlin/it/norangeb/algorithms/sorting/Mergesort.kt)
- Classe [Point e funzione main](https://github.com/norangebit-unisannio-computer-science/lm-tecniche-di-programmazione/blob/master/src/main/kotlin/it/norangeb/algorithms/exercises/PointSorter.kt)

View File

@ -40,6 +40,8 @@ data class Point(val x: Double, val y: Double) {
fun theta(): Double = Math.atan2(y, x)
fun r(): Double = Math.sqrt(x*x + y*y)
companion object {
fun thetaComparator(
p1: Point,
@ -71,6 +73,10 @@ fun main(args: Array<String>) {
val points = list.toTypedArray()
Mergesort.sortBy(points) {
(it - origin).r()
}
val compare = { p1: Point, p2: Point ->
Point.thetaComparator(p1, p2, origin)
}