0% found this document useful (0 votes)
9 views

Slides Cours7 Interfaces Adt

The document discusses using abstract classes, interfaces, and abstract data types to design a geometric drawing program that allows for different shapes. It shows how to create an abstract Shape class and concrete Circle and Rectangle classes that extend from it. It also covers calculating total area by iterating through a shape array. The document emphasizes open-closed design principles and how interfaces allow classes to take on multiple roles.

Uploaded by

jimes30928
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views

Slides Cours7 Interfaces Adt

The document discusses using abstract classes, interfaces, and abstract data types to design a geometric drawing program that allows for different shapes. It shows how to create an abstract Shape class and concrete Circle and Rectangle classes that extend from it. It also covers calculating total area by iterating through a shape array. The document emphasizes open-closed design principles and how interfaces allow classes to take on multiple roles.

Uploaded by

jimes30928
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 67

LEPL1402

INFORMATIQUE 2
Abstract Classes, Interfaces, Abstract Data-Types and
Intro to Data Structure
Design Challenge ?
• Designing a geometric drawing program shapes with functionalities:

• Scienti c computations, such as calculating the area (shapes dependant).

• Shapes should have the capability to print information about itself (quite general for all
shapes).

• Additionally, the program is designed to allow users to de ne their own shapes.


fi
fi
Solution: use an abstract class
public abstract class Shape {
protected String shapeName;
public Shape(String name) {
this.shapeName = name;
}

// Abstract method to calculate the area of the shape


public abstract double calculateArea();

// A concrete method implemented in the abstract class


public void displayShapeInfo() {
System.out.println("The " + shapeName + " has an area of: " + calculateArea());
}
}
Let’s make two concrete classes

class Rectangle extends Shape {


class Circle extends Shape { private double length;
private double radius; private double width;

public Circle(double radius) { public Rectangle(double length, double width) {


super("Circle"); super("Rectangle");
this.radius = radius; this.length = length;
} this.width = width;
}
@Override
public double calculateArea() { @Override
return Math.PI * radius * radius; public double calculateArea() {
} return length * width;
} }
}
Add a functionality
• I want to be able to compute the total area of several shapes

class ShapeUtils {

// Static method to compute the total area of an array of shapes


public static double calculateTotalArea(Shape[] shapes) {
double totalArea = 0.0;

for (Shape shape : shapes) {


totalArea += shape.calculateArea();
}

return totalArea;
}

public static void main(String[] args) {


Shape[] shapes = {new Circle(5), new Rectangle(4, 5), new Rectangle(2, 8)};
double totalArea = calculateTotalArea(shapes);
System.out.println("Total Area: " + totalArea);
}
}
Open-Close Principle
• Software entities (such as classes, modules, and functions) should be open for extension
but closed for modi cation.

• This approach ensures that our program can grow and adapt over time without
necessitating alterations to the existing, stable parts of the code.
fi
Exercise
• If I want to introduce triangles, What do I need to modify in those two classes ?

public abstract class Shape {


protected String shapeName;
public Shape(String name) {
this.shapeName = name;
}
public abstract double calculateArea();
public void displayShapeInfo() {
System.out.println("The " + shapeName + " has an area of: " + calculateArea());
}
}

class ShapeUtils {
public static double calculateTotalArea(Shape[] shapes) {
double totalArea = 0.0;
for (Shape shape : shapes) {
totalArea += shape.calculateArea();
}
return totalArea;
}
}
Answer: Nothing!
• We just extend the shape class:
class Triangle extends Shape {
private double base;
private double height;
public Triangle(double base, double height) {
super("Triangle");
this.base = base;
this.height = height;
}
@Override
public double calculateArea() {
return 0.5 * base * height;
}
}

public static void main(String[] args) {


Shape[] shapes = {new Circle(5), new Rectangle(4, 5), new Triangle(3, 4)};
double totalArea = calculateTotalArea(shapes);
System.out.println("Total Area: " + totalArea);
}
More or less abstract
Abstract classes:
Some methods
implemented

Standard classes:
All methods Interfaces:
implemented No method implemented
Advantage of interfaces
• A class can implement more than one interface, not possible with class extension. You can
only extend one class. Remember:

class ExpensiveWeapon extends Weapon {


@Override
int getPrice() {
return 1000;
}
}
class MightySword extends Weapon {
@Override
int getPrice() {
return 500 * level;
}
}
// Not allowed in Java!
// You cannot extend TWO classes.
class ExpensiveMightySword extends ExpensiveWeapon, MightySword {
}
Design Problem
• Represent those products in a modular way

• Observation: Ipods and Iphones have common functionalities

Ipods iPhones

Coolpix
Solution: Use Interfaces

interface Camera { interface MediaPlayer {


void takePhoto(); void playAudio();
void recordVideo(); void playVideo();
} }

Camera Products Media Player Products


SmartPhone
abstract class Smartphone implements Camera, MediaPlayer {

@Override
public void takePhoto() {
System.out.println("Taking a photo");
}

@Override
public void recordVideo() {
System.out.println("Recording video");
}

@Override
public void playAudio() {
System.out.println("Playing audio");
}

@Override
public void playVideo() {
System.out.println("Playing video");
}
}
iPod

abstract class iPod implements MediaPlayer {


@Override
public void playAudio() {
System.out.println("Playing your favorite playlist");
}

@Override
public void playVideo() {
System.out.println("Playing your favorite video");

}
}
<<Interface>>
Camera <<Interface>>
MediaPlayer
+ takePhoto(): void
+ recordVideo(): void + playAudio(): void
+ playVideo(): void

abstract NikonCoolPix abstract iPhone abstract iPod

+ zoom() + call() + recordAudio(): void

class CoolPixV1 class CoolPixV2 class IPhoneFourteen class IPhoneFifteen class IPodShuffle class IPodTouch class IPodNano
Abstract Data Types
• An interface (set of methods) that allows you to store and retrieve some data.

• When more than one data can be added/retrieved/removed we generally speak about a
collection.

• It doesn’t tell you how those those methods are implemented and also not how e cient
(complexity) those methods are. This is why it is abstract.

ffi
A Data-Structure
• Is one possible implementation of an ADT

• Several possible implementations are generally possible but it complies with the
speci cation of the methods of the ADT

• It is thus concrete and we can analyse the time-complexity of each method.


fi
One of the must used ADT is the List
Such an interface exists in java.util.List

Severa classes implement this interface: AbstractList, AbstractSequentialList, ArrayList,


AttributeList, CopyOnWriteArrayList, LinkedList, RoleList, RoleUnresolvedList, Stack, Vector
Very rich set of methods
package java.util;

public interface List<E> extends Collection<E> {

void add(E e);


E remove(int index);
void add(int index, E element);
boolean remove(Object o);
void clear();
E set(int index, E element);
boolean contains(Object o);
int size();
boolean equals(Object o);

E get(int index);
List<E> subList(int fromIndex, int toIndex);
int hashCode();
Object[] toArray();
int indexOf(Object o);
<T> T[] toArray(T[] a);
}
boolean isEmpty();

Iterator<E> iterator();
List Example Usage
public static void main(String[] args) {

⚠ ADT
List<String> fruits; // declaring a List typereference
of the interface
fruits = new LinkedList<>(); // Initializing it using LinkedList
// fruits = new ArrayList<>(); This would also work

// Adding elements
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");

// Removing an element
fruits.remove("Banana");
}
Other ADT
• Bag

• Queue

• Stack
Bag: Non Ordered (also called Multi-Set)
public interface Bag<E> {

// Adds an element to the bag


void add(E element);

// Checks if the bag contains a specific element


boolean contains(E element);

// Returns the number of occurrences of an element in the bag


int count(E element);

// Removes one occurrence of the specified element (if present)


boolean remove(E element);

// Checks if the bag is empty


boolean isEmpty();

// Returns the number of items in this stack.


public int size();
}
Queue: First In First Out (FIFO)
public interface Queue<E> {

// Adds an element to the rear of the queue


void enqueue(E element);

// Removes and returns the element at the front of the queue


E dequeue();

// Returns the element at the front of the queue without removing it


E peek();

// Checks if the queue is empty


boolean isEmpty();

// Returns the number of items in this Queue


public int size();
}
Stack: Last In First Out (LIFO)
public interface Stack<T> {
// Pushes an item onto the top of this stack.
void push(T item);

// Removes and returns the top item from this stack.


T pop();

// Returns the top item from this stack without removing it.
T peek();

// Returns true if this stack is empty.


boolean isEmpty();

// Returns the number of items in this stack.


public int size();

}
Two different implementations
• Using a linked-list structure <<Interface>>
Stack

+ push(E):E
+ pop(): E
+ peek(): E
+ isEmpty(): boolean
+ size(): int

top

• Using an array structure


LinkedStack DynamicArrayStack

+ size: int + array: E[]


+ top: Node + top: int
5 8 2 6 2 5 6 8 5

top
The stack implementation
• It can also be implemented with a linked data-structure or with an array-based
implementation (left as an exercise).

• Linked data-structure (FIFO): keep a reference to the head and do the push/pop operations
from there

• rst
Array-based data-structure: push/pop at the end to avoid the shifts. You may also want to
reduce the size when the capacity is too large.

5 8 2 6 2 5 6 8 5
fi
The LinkedStack data-structure
• A series of nodes each containing some element and each referencing the next.

• The series start with the special node that we call “top”

private static class Node<Item> {


private Item item;
private Node<Item> next; Node.next
}
I’m rst!
An instance of Node
fi
The LinkedStack Implementation
public class LinkedStack<T> implements Stack<T> {
private Node<T> top;
private int size;
@Override
public void push(T item) { private static class Node<T> {
top = new Node<>(item, top); T item;
size++; Node<T> next;
}
@Override Node(T item, Node<T> next) {
public T pop() { this.item = item;
if (isEmpty()) this.next = next;
throw new RuntimeException("Stack is empty"); }
T item = top.item; }
top = top.next;
size--;
return item;
}
@Override
public T peek() {
if (isEmpty())
throw new RuntimeException("Stack is empty");
return top.item;
}
@Override
public boolean isEmpty() { return top == null; }
@Override
public int size() { return size; }
}
DynamicArrayStack
• Idea: Use an internal array to store the content of the stack

• The problem is that the arrays have a xed size in java. Say N, it must be large enough to
store all the elements of the stack. Otherwise we double its size and copy the old content in
it.

5 8 2 6 2 5
push(6)

5 8 2 6 2 5 6

push(8)
5 8 2 6 2 5 6 8

push(5)
5 8 2 6 2 5 6 8 5
fi
DynamicArrayStack
public class DynamicArrayStack<T> implements Stack<T> {
private T[] array;
private int top;

public DynamicArrayStack(int initialCapacity) {


array = (T[]) new Object[initialCapacity];
top = -1;
}
@Override
public void push(T item) {
if (top == array.length - 1) {
resize(2 * array.length); // double the size
}
array[++top] = item;
}
private void resize(int newCapacity) {
T[] newArray = (T[]) new Object[newCapacity];
for (int i = 0; i <= top; i++) {
newArray[i] = array[i];
}
array = newArray;
}
}
DynamicArrayStack
public class DynamicArrayStack<T> implements Stack<T> {
private T[] array;
private int top;
@Override
public T pop() {
if (isEmpty()) {
throw new RuntimeException("Stack is empty");
}
T item = array[top];
array[top--] = null; // to prevent memory leak

// shrink the size if necessary


if (top > 0 && top == array.length / 4) {
resize(array.length / 2); 5 8 2 6 2
}
return item; pop()
}
@Override
public T peek() { 5 8 2 6
if (isEmpty()) {
throw new RuntimeException("Stack is empty");
}
return array[top];
}
@Override
public boolean isEmpty() {return top == -1;}
@Override
public int size() { return top + 1;}
}
Time Complexity Analysis
• What if the time complexity of one push/pop for our two implementation ? Expressed in n,
the current size of the stack. What for n push starting with an empty ?
<<Interface>>
Stack

+ push(E):E
+ pop(): E
+ peek(): E
+ isEmpty(): boolean
+ size(): int

LinkedStack DynamicArrayStack

+ size: int + array: E[]


+ top: Node + top: int
Time Complexity

DynamicArrayStac
LinkedStack
k

Push O(1) O(n) ?

Pop O(1) O(n) ?

n push O(n) O(n^2) ?

n pop O(n) O(n^2) ?


DynamicArrayStack Time Complexity 🤔
X2 on size when size limit reached
LinkedStack DynamicArrayStack
Over n push, resize occurs at

O(1) ‣1
Push O(1) Sauf redim.
‣2
O(n)
‣4
O(1) ‣8
Pop O(1) Sauf redim. ‣ 16
O(n) ‣…
‣n
Number of resizes = log2n
n push O(n) O(n^2) ?

log2 n
n
∑ 2i
n pop O(n) O(n^2) ? < 2n ∈ (n)
𝒪
i=0
Time Complexity ArrayStack n x push

n/2

n/4

n/8

n/16

n/32

n/64

Conclusion
DynamicArrayStack has excellent amortised complexities for n operations

LinkedStack DynamicArrayStack

O(1)
Push O(1) Except redim.
O(n)
O(1)
Pop O(1) Except redim.
O(n)

n push O(n) O(n) (amortised)

n pop O(n) O(n) (amortised)


Evaluation of arithmetic Expression: Stack Based Algo

( ( 2 * ( 3 + 5 ) ) / 4 )

Stacks: 2 ɸ
evals ops
Evaluation of arithmetic Expressions

( ( 2 * ( 3 + 5 ) ) / 4 )

Stacks: 2 *

evals ops
Evaluation of arithmetic Expressions

( ( 2 * ( 3 + 5 ) ) / 4 )

Stacks: 2 *

evals ops
Evaluation of arithmetic Expressions

( ( 2 * ( 3 + 5 ) ) / 4 )

Stacks: 2 *

evals ops
Evaluation of arithmetic Expressions

( ( 2 * ( 3 + 5 ) ) / 4 )

3 +

Stacks: 2 *

evals ops
Evaluation of arithmetic Expressions

( ( 2 * ( 3 + 5 ) ) / 4 )

3 +

Stacks: 2 *

evals ops
Evaluation of arithmetic Expressions

( ( 2 * ( 3 + 5 ) ) / 4 )

8 3+5

Stacks: 2 *

evals ops
Evaluation of arithmetic Expressions

( ( 2 * ( 3 + 5 ) ) / 4 )

Stacks: 16 2*8 ɸ
evals ops
Evaluation of arithmetic Expressions

( ( 2 * ( 3 + 5 ) ) / 4 )

Stacks: 16 /

evals ops
Evaluation of arithmetic Expressions

( ( 2 * ( 3 + 5 ) ) / 4 )

Stacks: 16 /

evals ops
Evaluation of arithmetic Expressions

( ( 2 * ( 3 + 5 ) ) / 4 )

Stacks: 4 16/4 ɸ
evals ops
Arithmetic Expression Evaluation with a Stack
> java ArithmeticExpression “( ( 2 * ( 3 + 5 ) ) / 4 )"

public class ArithmeticExpression {


public static void main(String[] args) {
Stack<String> ops = new LinkedStack<String>();
Stack<Double> vals = new LinkedStack<Double>();

for (String s: args[0].split(" ")) {


if (s.equals("(")) ;
else if (s.equals("+")) ops.push(s);
else if (s.equals("-")) ops.push(s);
else if (s.equals("*")) ops.push(s);
else if (s.equals("/")) ops.push(s);
else if (s.equals(")")) {
String op = ops.pop();
double v = vals.pop();
if (op.equals("+")) v = vals.pop() + v;
else if (op.equals("-")) v = vals.pop() - v;
else if (op.equals("*")) v = vals.pop() * v;
else if (op.equals("/")) v = vals.pop() / v;
vals.push(v);
}
else vals.push(Double.parseDouble(s));
}
System.out.println("result: "+vals.pop());
}
}
Exercise: Write a recursive algorithm not using explicit stacks
public class ArithmeticExpression {
public static void main(String[] args) {
Stack<String> ops = new LinkedStack<String>();
Stack<Double> vals = new LinkedStack<Double>();

for (String s: args[0].split(" ")) {


if (s.equals("(")) ;
else if (s.equals("+")) ops.push(s);
else if (s.equals("-")) ops.push(s);
else if (s.equals("*")) ops.push(s);
else if (s.equals("/")) ops.push(s);
else if (s.equals(")")) {
String op = ops.pop();
double v = vals.pop();
if (op.equals("+")) v = vals.pop() + v;
else if (op.equals("-")) v = vals.pop() - v;
else if (op.equals("*")) v = vals.pop() * v;
else if (op.equals("/")) v = vals.pop() / v;
vals.push(v);
}
else vals.push(Double.parseDouble(s));
}
System.out.println("result: "+vals.pop());
}
}
Linked structures can be more complex: Tree Example
• To represent arithmetic expressions

OperatorExpressionTree

3
*

2 -

- 3
ValueExpressionTree

5 7
BinaryExpressionTree
public abstract class BinaryExpressionTree {
private static class OperatorExpressionTree extends BinaryExpressionTree {
private nal BinaryExpressionTree left;
private nal BinaryExpressionTree right;
private nal char operator;

private OperatorExpressionTree(char op, BinaryExpressionTree l, BinaryExpressionTree r) { OperatorExpressionTree


this.operator = op;
this.left = l;
this.right = r; /
}
public BinaryExpressionTree plus(BinaryExpressionTree r) { // similar for minus/div/mul 3
return new OperatorExpressionTree('+',this,r); *
}
}
private static class ValueExpressionTree extends BinaryExpressionTree {
2 -
private nal int value;
- 3
private ValueExpressionTree(int v) { ValueExpressionTree
this.value = v;
}
5 7
}
public static void main(String[] args) {
BinaryExpressionTree expr = value(2).mul(value(5).plus(value(7)).minus(value(3)).div(value(3))); // (2 * ((5+7)-3)) / 3
}
}
fi
fi
fi
fi
Recursive Traversal of a Tree
• 3 Strategies

InOrder Traversal PostOrder Traversal


PreOrder Traversal
1.Visit Left (InOrder) 1.Visit Left (PostOrder)
1.Visite Node
2.Visit Node 2.Visit Right (PostOrder)
2.Visit Left (PreOrder)
3.Visit Right (InOrder) 3.Visit Node
3.Visit Right (PreOrder)

/
/ /
3
*
3 3 *
*
2 - 2 - 2 -

3 3 - 3
- -

5 7 5 7 5 7
Evaluating Arithmetic Expressions

• What kind of traversal do we need to evaluate the expression ?

3
*

2 -

- 3

5 7
Evaluating Arithmetic Expressions
• Post x:

• answerLeft = Get evaluation of left subtree


• answerRight = Get evaluation of right subtree
• return: answerLeft operator answerRight

3
*

2 -

- 3

5 7
fi
public abstract class BinaryExpressionTree {

abstract int evaluate();

private static class OperatorExpressionTree extends BinaryExpressionTree {


private nal BinaryExpressionTree left;
private nal BinaryExpressionTree right;
private nal char operator;
@Override
int evaluate() {
int leftRes = left.evaluate();
int rightRes = right.evaluate();
switch (operator) {
case '+': public static void main(String[] args) {
return leftRes + rightRes; BinaryExpressionTree expr = value(2).mul(value(5).plus(value(7))) // 2*(5+7)
case '-': System.out.println(expr.evaluate());
return leftRes - rightRes; }
case '/':
return leftRes / rightRes;
case '*':
return leftRes * rightRes;
default:
throw new IllegalArgumentException("unkown operator " + operator);
}
}
}
private static class ValueExpressionTree extends BinaryExpressionTree {
private nal int value;

@Override
int evaluate() {
return value;
}
}
}
fi
fi
fi
fi
Printing an expression

• expr.toString() // (2 * ((5+7)-3)) / 3
Also known ad the “in x” notation for
arithmetic expression

3
*

2 -

- 3

5 7
fi
public abstract class BinaryExpressionTree {

abstract int evaluate();


abstract String in xString();

private static class OperatorExpressionTree extends BinaryExpressionTree {


private nal BinaryExpressionTree left;
private nal BinaryExpressionTree right;
private nal char operator;

@Override
public String in xString() {
return "("+left.in xString()+operator+right.in xString()+")";
}

private static class ValueExpressionTree extends BinaryExpressionTree {


private nal int value;

@Override
public String in xString() {
return value+""; public static void main(String[] args) {
} BinaryExpressionTree expr = value(2).mul(value(5).plus(value(7))) // 2*(5+7)
System.out.println(expr.in xString());
}
}

}
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
Exercise

• Implement a “post order printing” (without parenthesis)

• (2 * ((5+7)-3)) / 3 => 2 5 7 + 3 - 3 / *

What kind of ADT would you use to


evaluation directly a “pos x notation”
fi
Time complexity of traversals

• n = number of nodes

PreOrder Traversal InOrder Traversal PostOrder Traversal


1.Visite Node 1.Visit Left (InOrder) 1.Visit Left (PostOrder)
2.Visit Left (PreOrder) 2.Visit Node 2.Visit Right (PostOrder)
3.Visit Right (PreOrder) 3.Visit Right (InOrder) 3.Visit Node
Binary Search Tree

• Is a binary proper binary is either

• Empty

• Non Empty: contains two subtrees left and right

• A binary search tree = is a binary tree with a “key” in each node such that

• This key is larger that all the keys in the left subtree

• This key is smaller than all the keys in the right subtree
Example
S

E X

null
A R

C B

• Is this a binary search tree ?


M
• And this one ?

E X

A R

C H

M
Interesting questions:
• How to nd the smallest key in a binary search tree ?

• What is the time complexity ?

• How to enumerate all the keys in increasing order ?

• What is the time complexity ?


S

E X

A R

C H

M
fi
Linked BinaryTrees
private class Node {

public int val;

public Node left;

• Linked Structure public Node right;

public Node(int val){


this.val = val;
}

public boolean isLeaf(){


return this.left == null && this.right == null;
}
}

private class Node {


private class Node {
public int val;
public int val;
public Node left;
public Node left;
public Node right;
public Node right;
public Node(int val){
public Node(int val){
this.val = val;
this.val = val;
}
}
public boolean isLeaf(){
public boolean isLeaf(){
return this.left == null && this.right == null;
return this.left == null && this.right == null;
}
}
}
}
Complexity (to verify is a value is present) ?

• Relation between n (number of nodes) and h (the height)

E X

A R

C H

M
Worst and Best-Case

• Example for 7 nodes


4

1
3 6
2

1 2 5 7
4

6 Best case: h = log(n)+1


Worst case: h = n 7
LEPL1402
INFORMATIQUE 2
Abstract Classes, Interfaces, Abstract Data-Types and
Intro to Data Structure

You might also like