Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
pp:intro [2019/02/14 16:09] pdmatei |
pp:intro [2019/02/14 18:19] (current) pdmatei |
||
---|---|---|---|
Line 8: | Line 8: | ||
<code java> | <code java> | ||
public class Rev { | public class Rev { | ||
- | public static void show (Integer[] v){ | ||
- | for (Integer i:v) | ||
- | System.out.println(i); | ||
- | } | ||
public static void main (String[] args) { | public static void main (String[] args) { | ||
Integer[] v = new Integer[] {1,2,3,4,5,6,7,8,9}; | Integer[] v = new Integer[] {1,2,3,4,5,6,7,8,9}; | ||
Line 25: | Line 21: | ||
} | } | ||
</code> | </code> | ||
+ | //Features:// | ||
+ | * the reversal is implemented over an array, and performed **in-place** by swapping the first half of the array with the second. | ||
+ | * the code is **compact** and **efficient** ($math[n/2] swaps over an array of size $math[n]). | ||
+ | * the solution modifies the sequence to a reversed one. | ||
- | The first approach stores the sequence as an array, and performs reversal in-place, by swapping the first half of the array with the second. The code is **compact** and fairly **efficient** in terms of computational complexity. | + | //Possible usages:// |
+ | * when development speed is critical | ||
+ | * when minimising the number of computation steps is critical | ||
- | The //undergrad-math-teacher-style// style: | + | The //Undergrad-math-teacher-style// style (short //Undergrad// style): |
<code java> | <code java> | ||
- | class List { | + | interface List { |
+ | public Integer head (); | ||
+ | public List tail (); | ||
+ | } | ||
+ | |||
+ | class Cons implements List { | ||
Integer val; | Integer val; | ||
List next; | List next; | ||
- | public List(Integer val, List next){ | + | public Cons (Integer val, List next){ |
this.val = val; | this.val = val; | ||
this.next = next; | this.next = next; | ||
} | } | ||
@Override | @Override | ||
- | public String toString(){ | + | public Integer head() {return val;} |
- | if (next == null) | + | @Override |
- | return val.toString(); | + | public List tail() {return next;} |
- | return val.toString()+" "+next.toString(); | + | |
- | } | + | |
} | } | ||
- | public class FuncRev { | ||
- | private static List rev(List x, List y){ | + | class Empty implements List { |
- | if (x == null) | + | @Override |
+ | public Integer head() {return -1;} | ||
+ | @Override | ||
+ | public List tail() {return null;} | ||
+ | } | ||
+ | |||
+ | public class V2 { | ||
+ | |||
+ | private static List rev (List x, List y){ | ||
+ | if (x instanceof Empty) | ||
return y; | return y; | ||
- | return rev(x.next,new List(x.val,y)); | + | return rev(x.tail(), new Cons(x.head(),y)); |
} | } | ||
+ | |||
public static List reverse (List l){ | public static List reverse (List l){ | ||
- | return rev(l,null); | + | return rev(l,new Empty()); |
} | } | ||
- | public static void main (String[] args) { | + | |
- | List v = new List(1, new List(2, new List(3, new List(4, new List (5,null))))); | + | public static void main (String[] args){ |
- | System.out.println(reverse(v)); | + | List v = new Cons(1, new Cons(2, new Cons(3, new Empty()))); |
+ | List r = reverse(v); | ||
} | } | ||
+ | |||
} | } | ||
</code> | </code> | ||
+ | //Features:// | ||
+ | * This solution first attempts to **separate** the sequence representation from the **reversal algorithm**. | ||
+ | * The sequence is **represented** as a **list** in the ADT-style. | ||
+ | * The in the reversal, the internal list representation is abstracted by using constructors (''Empty'' and ''Cons'') together with observers (''head'' and ''tail''). | ||
+ | * The code focuses more on **input-output**: reversal takes a list (instead of an array), and produces another list. The strategy relies on an auxiliary function ''rev'' which uses an accumulator to reverse the list. Both ''rev'' and the display function are recursive. However, ''rev'' is **tail-recursive** hence efficient as long as the programming language supports tail-end optimisation (which Java 8 does not support). | ||
- | Unlike the former approach, here the code focuses more on **input-output**: reversal takes a list (instead of an array), and produces another list. The strategy relies on an auxiliary function ''rev'' which uses an accumulator to reverse the list. Both ''rev'' and the display function are recursive. However, ''rev'' is **tail-recursive** hence quite efficient. | + | //Possible usages:// |
+ | * When it is necessary to separate the (reversal) algorithm from the sequence representation (e.g. when we want a single reversal algorithm for several types of lists) | ||
+ | * After calling ''reverse'' we have two list objects, the original and reversed list. This could be useful to test if the sequence is a palindrome. This programming style is called //purely-functional//: objects are never modified. Instead, new objects are created from existing ones. | ||
+ | |||
The //Industry// style: | The //Industry// style: | ||
<code java> | <code java> | ||
import java.util.Iterator; | import java.util.Iterator; | ||
- | class RVList<T> implements Iterable<T> { | + | class RevView<T> implements Iterable<T> { |
private T[] array; | private T[] array; | ||
- | public RVList(T[] array){ | + | public RevView(T[] array){ |
this.array = array; | this.array = array; | ||
} | } | ||
Line 100: | Line 123: | ||
String[] s = new String[]{"1", "2", "3", "4", "5", "6"}; | String[] s = new String[]{"1", "2", "3", "4", "5", "6"}; | ||
- | Iterator<String> r = (new RVList<String>(s)).iterator(); | + | Iterator<String> r = (new RevView<String>(s)).iterator(); |
while (r.hasNext()){ | while (r.hasNext()){ | ||
System.out.println(r.next()); | System.out.println(r.next()); | ||
Line 107: | Line 130: | ||
} | } | ||
</code> | </code> | ||
+ | //Features:// | ||
+ | * The solution here focuses on **views**. The sequence is represented as an **array of strings**, but it could be basically any kind of array. The object ''RevView'' is a **view** over the array: | ||
+ | * Note that ''RevView'' only holds a reference to the array, not the array itself. This means that the array could be modified from //outside// the ''RevView'' class; | ||
+ | * ''RevView'' returns an iterator over the array, which allows **traversing** the array in reverse order; | ||
+ | * The solution also separates the sequence representation (and the separation could be improved), and focuses on **viewing** or **traversing** the list rather than constructing another list from the original one; | ||
+ | //Possible usages:// | ||
+ | * Such a solution could be used when different **threads** may want to **read** the same sequence - **views** allow inspecting the sequence without modifying it (in our example). | ||
+ | * Also, it could be used to check properties of the sequence (e.g. palindrome) without duplicating data, using different views over the same sequence. | ||
- | The //Uni-math-teacher style//: | + | |
+ | The //Uni-math-teacher style// (short. //Uni// style): | ||
<code java> | <code java> | ||
Line 151: | Line 183: | ||
</code> | </code> | ||
- | The final alternative relies on the Java concept of **iterator** of collections. Informally, a collection of elements has the property that its elements can be //enumerated// or //iterated//. Any such collection is an ''Iterable'' (extends the Java interface Iterable). Here, ''RVList'' is such an iterable collection, which takes its elements at construction, from an array. ''RVList'' is **generic**, hence its elements can be of any type ''T''. | + | //Features:// |
+ | * The solution relies on the observation that **reversing a list** is a particular type of **folding operation**. Consider the sequence ''1,2,3'', the ''+'' operation and the initial value ''0''. Folding the sequence with ''+'' and ''0'', amounts to summing up the numbers from the sequence: | ||
+ | <code> | ||
+ | fold({1,2,3},+,0) = | ||
+ | fold({2,3},+,1+0) = | ||
+ | fold({3},+,2+1+0) = | ||
+ | fold({},+,3+2+1+0) = | ||
+ | 3+2+1+0 | ||
+ | </code> | ||
+ | |||
+ | * However, the folding operation need not be arithmetic (nor the initial value - a number). Suppose we replace ''+'' by ''cons'' ('':'') and ''0'' by the empty list ''[]''. The result of the folding operation is ''3:2:1:[]'', which is precisely the reversed list. In this solution, the ''fold'' operation is implemented in an abstract fashion, with respect to a generic binary operation ''op'' and a generic initial value ''init''. | ||
+ | * List reversal is a **particular case of a fold operation** | ||
- | The fundamental trait of an ''Iterable'' object is that it implements the method ''iterator'', which returns just that. An **iterator** is an object which allows enumerating all elements of the collection at hand, via the methods ''hasNext'' and ''next''. Sometimes (as in the case of a ''Set'') the order is unimportant. Here, the order matters - elements are explored in their reverse order. Technically, the iterator object is an **anonymous class**. | + | //Possible usages:// |
+ | * This solution is also characteristic to the functional programming style, more specifically, programming with **higher-order functions**. A higher-order function (like ''fold'') takes other functions (like ''op'') and implements some functionality in terms of it. In a functional programming language, implementing (and using) a ''fold'' is much simpler and elegant than in an imperative language (like Java). | ||
+ | * Using higher-order functions is very useful when writing programs over large data (for processing like that done in Machine Learning). Such processing is done using a combination of //map-reduce// functions: //map// transforms data uniformly, and //reduce// computes a new (reduced) value from it. Map-reduce programs are easily to parallelise (however, reversal is not a demonstration for that). | ||
- | This alternative focuses on **list traversal**, and on the **generality** of the approach. Basically, any Java collection (for which order makes sense) can be transformed to an array (via the ''toArray'' method) and subsequently - an ''RVList'' which can be traversed in reverse order via its iterator. | ||
==== Why so many reversals? ==== | ==== Why so many reversals? ==== | ||
- | The reason for choosing reversal as a running example is that the task is algorithmically trivial: take the sequence of elements of the collection at hand, be it array of list (or anything else), in their **reverse** order. | + | Note that **reversal** is an algorithmically trivial task: take the sequence of elements of the collection at hand, be it array of list (or anything else), in their **reverse** order. |
- | However, there are **many different ways** for implementing reversal, and each makes perfect sense in some setting. Moreover, some of these ways are: **subtle**, **conceptually challenging**, and require **higher skill/knowledge** in operating the programming language at hand. | + | Our point is that, apart from mastering algorithms, a skilled programmer needs **solid knowledge** on programming concepts and on the way programming languages (and the hardware they employ) are designed. In some cases, these concepts may be subtle (e.g. programming with Monads in Haskell) and may supersede the algorithm at hand in complexity. However, they are crucial in developing a **efficient**, **secure** and **correct** applications. |
- | Our point, and one of the major objectives of this lecture, is to emphasise that, apart from developing fast and correct algorithms, a task of equal challenge and importance is **writing down the code**, in the **suitable programming language**. | + | This lecture will focus on different ways of **writing code** for specific algorithms, in different programming languages, with an emphasis on Functional Languages (Haskell) and Logic-based Languages (Prolog). |
We have clear metrics for choosing algorithms. Do we also have metrics for **code writing**? For instance: | We have clear metrics for choosing algorithms. Do we also have metrics for **code writing**? For instance: | ||
Line 176: | Line 220: | ||
==== How many other ways? ==== | ==== How many other ways? ==== | ||
- | There are many possible variations to our three examples, but a few elements do stand out: | + | There are many possible variations (and combinations) to our examples, but a few elements do stand out: |
- | * the functional style for **representing a list** in the second example - very akin to Abstract Datatypes | + | * the functional style for **representing a list** - very akin to Abstract Datatypes |
- | * the functional style for reversal - relying on recursion, in the same example | + | * the functional style for reversal - relying on recursion, relying on folding |
* the Object Oriented style for **traversing a list** in the third example - which although is tightly linked to Java Collections, can be migrated to any other object-oriented language. | * the Object Oriented style for **traversing a list** in the third example - which although is tightly linked to Java Collections, can be migrated to any other object-oriented language. | ||
- | These **elements of style** are frequently called **design patterns** - generic ways of writing code, which can be deployed for different implementations. | + | Such **elements of style** are may be called **design patterns**, i.e. generic ways of writing code, which can be deployed for different implementations. |
- | Some elements of style may have some common ground, or rely on certain traits of the programming language. These latter are called programming **paradigms**. For instance, writing programs as recursive functions as well as representing data as ADTs (recall that constructors //are// functions) are both styles of the **functional** paradigm. | + | Some elements of style may have some common ground, or require certain traits from the programming language. These latter are called programming **paradigms**. For instance, writing programs as recursive functions, using higher-order functions and representing data as ADTs (recall that constructors //are// functions) are both styles of the **functional** paradigm. |
In this lecture, we shall go over the following paradigms: | In this lecture, we shall go over the following paradigms: |