add exercise point sorter
- implement sort by radius
This commit is contained in:
parent
46daa16e3c
commit
aeb32837a9
156
doc/exercises/point_sorter.md
Normal file
156
doc/exercises/point_sorter.md
Normal 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 all’angolo 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)
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user