From aeb32837a950a6e4a3a6620a7e19eb5a5827d48f Mon Sep 17 00:00:00 2001 From: norangebit Date: Thu, 28 Mar 2019 21:32:16 +0100 Subject: [PATCH] add exercise point sorter - implement sort by radius --- doc/exercises/point_sorter.md | 156 ++++++++++++++++++ .../algorithms/exercises/PointSorter.kt | 6 + 2 files changed, 162 insertions(+) create mode 100644 doc/exercises/point_sorter.md diff --git a/doc/exercises/point_sorter.md b/doc/exercises/point_sorter.md new file mode 100644 index 0000000..ee6b97d --- /dev/null +++ b/doc/exercises/point_sorter.md @@ -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 > sort(array: Array) + fun sortWith(array: Array, 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) { + if (args.size < 4 || args.size % 2 != 0) return + + val origin = Point(args[0].toDouble(), args[1].toDouble()) + + val list = ArrayList() + + 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 > sortBy( + array: Array, + 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) { + if (args.size < 4 || args.size % 2 != 0) return + + val origin = P(args[0].toDouble(), args[1].toDouble()) + + val list = ArrayList() + + 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) diff --git a/src/main/kotlin/it/norangeb/algorithms/exercises/PointSorter.kt b/src/main/kotlin/it/norangeb/algorithms/exercises/PointSorter.kt index befcf3f..d15bfc0 100644 --- a/src/main/kotlin/it/norangeb/algorithms/exercises/PointSorter.kt +++ b/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) { val points = list.toTypedArray() + Mergesort.sortBy(points) { + (it - origin).r() + } + val compare = { p1: Point, p2: Point -> Point.thetaComparator(p1, p2, origin) }