import numpy as np
import graphviz as gv
Modul 5 Struktur Data: Stack
Stack
Kembali ke Struktur Data
Di praktikum kali ini tentang stack, kita akan membahas implementasi stack (baik dengan array maupun dengan linked list) serta contoh penggunaannya.
Implementasi dan contoh penggunaan stack
Implementasi stack dengan array
class ArrayStack:
def __init__(self, dtype, max):
self.dtype = dtype
self.max = max
self.array = np.empty(max, dtype=dtype)
self.top = -1
def get_size(self):
return self.top + 1
def get_capacity(self):
return self.max
def get_dtype(self):
return self.dtype
def is_empty(self):
if self.get_size() > 0:
return False
else:
return True
def is_full(self):
if self.get_size() >= self.get_capacity():
# if top+1 >= max
# atau sama saja, if top >= max-1
return True
else:
return False
def push(self, newdata):
if self.is_full():
print("Error push: stack sudah penuh.")
else:
self.top += 1
self.array[self.top] = newdata
def peek(self):
if self.is_empty():
print("Error peek: stack sedang kosong.")
return None
else:
return self.array[self.top]
def pop(self):
if self.is_empty():
print("Error pop: stack sudah kosong sebelumnya.")
return None
else:
= self.array[self.top]
output self.top -= 1
return output
def print_stack(self):
= self.top
i while i >= 0:
print(self.array[i])
-= 1
i
# print array
def print_storage(self):
print(self.array)
def get_digraph_stack(self):
= gv.Digraph()
new_digraph # gambar akan terdiri dari satu tabel saja, satu kolom,
# dan tiap baris adalah tiap elemen di stack
= "<"
tabel_besar # pembuka tabel
+= "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">"
tabel_besar # menambahkan tiap elemen sebagai baris tersendiri
= self.top
i if i < 0:
+= "<TR><TD>"
tabel_besar += "(Stack sedang kosong; tidak ada data sama sekali.)"
tabel_besar += "</TD></TR>"
tabel_besar while i >= 0:
+= "<TR><TD>"
tabel_besar += str(self.array[i])
tabel_besar += "</TD></TR>"
tabel_besar -= 1
i # penutup tabel
+= "</TABLE>"
tabel_besar += ">"
tabel_besar "ArrayStack", shape="none", label=tabel_besar)
new_digraph.node(return new_digraph
def get_digraph_storage(self):
# menggambar array
= gv.Digraph()
new_digraph
# pembuka tabel
= "<"
tabel_besar += "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">"
tabel_besar # tabel hanya terdiri dari satu baris
+= "<TR>"
tabel_besar # satu elemen per kolom
for i in range(self.get_capacity()):
+= "<TD>"
tabel_besar += str(self.array[i])
tabel_besar += "</TD>"
tabel_besar # penutup baris
+= "</TR>"
tabel_besar # penutup tabel
+= "</TABLE>"
tabel_besar += ">"
tabel_besar "array", shape="none", label=tabel_besar)
new_digraph.node(return new_digraph
= ArrayStack(int, 5)
arraystack 5)
arraystack.push(80)
arraystack.push(100) arraystack.push(
arraystack.print_stack()
100
80
5
print(arraystack.get_capacity())
5
arraystack.print_storage()
[ 5 80 100
4622241330054037504 4625478292286210048]
print(arraystack.peek())
100
arraystack.print_stack()
100
80
5
= arraystack.pop()
nilai print(nilai)
100
arraystack.print_stack()
80
5
arraystack.print_storage()
[ 5 80 100
4622241330054037504 4625478292286210048]
-10)
arraystack.push(57) arraystack.push(
arraystack.print_stack()
57
-10
80
5
arraystack.print_storage()
[ 5 80 -10
57 4625478292286210048]
= arraystack.get_digraph_stack() graf1
display(graf1)
= arraystack.get_digraph_storage() graf2
display(graf2)
90) arraystack.push(
46) arraystack.push(
Error push: stack sudah penuh.
arraystack.print_storage()
[ 5 80 -10 57 90]
print(arraystack.pop())
print(arraystack.pop())
print(arraystack.pop())
print(arraystack.pop())
print(arraystack.pop())
90
57
-10
80
5
print(arraystack.pop())
Error pop: stack sudah kosong sebelumnya.
None
print(arraystack.get_size())
0
arraystack.print_stack()
arraystack.print_storage()
[ 5 80 -10 57 90]
display(arraystack.get_digraph_stack())
Implementasi stack dengan singly-inked list
class SLNode:
def __init__(self, data, next=None):
self.data = data
self.next = next
class SLStack:
def __init__(self):
# "head" ganti nama jadi top
self.top = None
def is_empty(self):
if self.top == None:
return True
else:
return False
def push(self, newdata):
= SLNode(newdata)
newnode next = self.top
newnode.self.top = newnode
def peek(self):
if self.is_empty():
print("Error peek: stack sedang kosong.")
else:
return self.top.data
def pop(self):
if self.is_empty():
print("Error pop: stack sudah kosong sebelumnya.")
else:
= self.top.data
output = self.top
temp self.top = self.top.next
del temp
return output
def get_size(self):
= self.top
temp = 0
size while temp != None:
+= 1
size = temp.next
temp return size
def print_stack(self):
= self.top
temp while temp != None:
print(temp.data)
= temp.next
temp
# print linked list
def print_storage(self):
print("top -> ", end="")
= self.top
temp while temp != None:
print(temp.data, end=" -> ")
= temp.next
temp print("None")
def get_digraph_stack(self):
= gv.Digraph()
new_digraph # gambar akan terdiri dari satu tabel saja, satu kolom,
# dan tiap baris adalah tiap elemen di stack
= ""
tabel_besar += "<"
tabel_besar += "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">"
tabel_besar = self.top
temp if temp == None:
+= "<TR><TD>"
tabel_besar += "(Stack sedang kosong; tidak ada data sama sekali.)"
tabel_besar += "</TD></TR>"
tabel_besar while temp != None:
+= "<TR><TD>"
tabel_besar += str(temp.data)
tabel_besar += "</TD></TR>"
tabel_besar = temp.next
temp # penutup tabel
+= "</TABLE>"
tabel_besar += ">"
tabel_besar "SLStack", shape="none", label=tabel_besar)
new_digraph.node(return new_digraph
# copas dari modul linked list, tapi head ganti jadi top
def get_digraph_storage(self):
# Buat digraph baru yang sifatnya dari kiri ke kanan
= gv.Digraph(graph_attr={"rankdir": "LR"})
new_digraph
# Pointer untuk menunjuk ke tiap node, mulai dari node pertama
# (akan dilakukan traversal)
= self.top
current
# Untuk menghitung node ke-sekian untuk nama node di Graphviz,
# sehingga top menunjuk ke node0, lalu node0 menunjuk ke node1, dst
= 0
counter
# Memperoleh alamat yang sedang disimpan di top
# - asumsi awal: tidak ada alamat (None)
= None
next_id = "node0" # ini nanti untuk nama node berikutnya di Graphviz
next_name # - kalau ternyata ada alamat...
if current != None:
# maka simpan alamat tersebut
= hex(id(current))
next_id # kita buat lebih spesifik untuk node berikutnya, tunjuk ke port id
= "node0:id"
next_name
# Label (tabel) untuk pointer top
# - pembuka tabel
= "<"
str_label += "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">"
str_label # - baris top
+= "<TR><TD>top</TD></TR>"
str_label # - baris alamat (sekalian membuat port namanya "contents")
+= "<TR><TD PORT=\"contents\">" + str(next_id) + "</TD></TR>"
str_label # - penutup tabel
+= "</TABLE>"
str_label += ">"
str_label
# Membuat node top, membuat edge dari top ke node berikutnya
"top", shape="none", label=str_label)
new_digraph.node("top:contents", next_name)
new_digraph.edge(# dari port "contents" ke node berikutnya, yang namanya next_name
# Selama node yang ditunjuk bukan None, buatlah node nya di Graphviz,
# lalu lanjut ke node selanjutnya (ini traversal)
while current != None:
# Alamat yang tersimpan pada current.next
# - asumsi awal: tidak ada alamat; current adalah node terakhir
= None
next_id # - kalau ternyata ada alamat...
if current.next != None:
# maka simpan alamat tersebut
= hex(id(current.next))
next_id
# Persiapan label (tabel) untuk node
# - pembuka tabel
= "<"
str_label += "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">"
str_label # - baris tulisan "data", "next"
+= "<TR><TD>data</TD><TD>next</TD></TR>"
str_label # - baris untuk isi data dan isi next
+= "<TR>"
str_label += "<TD>" + str(current.data) + "</TD>"
str_label += "<TD PORT=\"next\">" + str(next_id) + "</TD>"
str_label += "</TR>"
str_label # - baris tulisan "alamat node", merentang dua kolom
+= "<TR><TD COLSPAN=\"2\">alamat node</TD></TR>"
str_label # - baris untuk isi alamat node, merentang dua kolom
+= "<TR>"
str_label += "<TD PORT=\"id\" COLSPAN=\"2\">"
str_label += str(hex(id(current)))
str_label += "</TD>"
str_label += "</TR>"
str_label # - penutup tabel
+= "</TABLE>"
str_label += ">"
str_label
# Membuat node baru di Graphviz dengan label (tabel) tersebut
"node" + str(counter), shape="none", label = str_label)
new_digraph.node(
# Menentukan nama dua port yang bakal disambung dengan edge,
# yaitu (node saat ini):next disambung ke node(berikutnya):id
# yaitu bagian "next" disambung ke bagian alamat di node berikutnya
= "node" + str(counter) + ":next"
nama_node_next if current.next != None:
= "node" + str(counter+1) + ":id"
nama_alamat_node_berikutnya # atau ke node(berikutnya) saja tanpa id kalau itu ternyata None,
# karena None tidak akan memiliki port id
else:
= "node" + str(counter+1)
nama_alamat_node_berikutnya
# Menyambung keduanya
new_digraph.edge(nama_node_next, nama_alamat_node_berikutnya)
# Lanjut ke node selanjutnya
= current.next
current += 1
counter # Kalau sudah keluar loop, artinya current menunjuk ke None
# Berarti tinggal membuat "node" terakhir berisi tulisan None
# (karena sambungannya sudah dibuat di dalam loop, tinggal node nya)
"node" + str(counter), shape="none", label="None")
new_digraph.node(
# Digraph sudah jadi
return new_digraph
= SLStack()
slstack slstack.print_storage()
top -> None
"abc")
slstack.push("fg")
slstack.push("ijk")
slstack.push("pqrs")
slstack.push("xyz") slstack.push(
slstack.print_stack()
xyz
pqrs
ijk
fg
abc
slstack.print_storage()
top -> xyz -> pqrs -> ijk -> fg -> abc -> None
display(slstack.get_digraph_stack())
print(slstack.pop())
print(slstack.pop())
print(slstack.pop())
xyz
pqrs
ijk
slstack.print_stack()
fg
abc
slstack.print_storage()
top -> fg -> abc -> None
display(slstack.get_digraph_stack())
Contoh sederhana: reverse suatu list/array
def reverse_array_arraystack(array_old):
= array_old.copy()
array
# memeriksa tipe data dari elemen pertama
= type(array[0])
tipe_data # khusus array, bisa juga menggunakan array.dtype
= ArrayStack(tipe_data, len(array))
arraystack for i in range(len(array)):
arraystack.push(array[i])for i in range(len(array)):
= arraystack.pop()
array[i] return array
= ["m", "a", "t", "e", "k"]
list1 = reverse_array_arraystack(list1)
list2 print(list2)
['k', 'e', 't', 'a', 'm']
def reverse_array_slstack(array_old):
= array_old.copy()
array = SLStack()
slstack for i in range(len(array)):
slstack.push(array[i])for i in range(len(array)):
= slstack.pop()
array[i] return array
= np.array(["m", "a", "t", "e", "k"])
array1 = reverse_array_slstack(array1)
array2 print(array2)
['k' 'e' 't' 'a' 'm']
(TODO) Notasi prefix, infix, dan postfix
Notasi prefix, infix, dan postfix adalah tiga jenis notasi (cara penulisan) untuk menuliskan operasi aritmetika seperti penjumlahan, perkalian, dan sebagainya.
Misalnya, kita bisa menuliskan penjumlahan 3 + 5
, di mana dua angka, 3 dan 5, dioperasikan oleh suatu “operator” yaitu + (plus). Perhatikan bahwa operator berada di tengah, di antara kedua angka. Penulisan seperti ini disebut notasi infix, dan inilah penulisan yang biasa kita kenal.
Ada juga cara penulisan di mana operator ditempatkan sebelum kedua angka, disebut notasi prefix, seperti berikut: + 3 5
Walaupun terlihat agak aneh, kita bisa saja mendefinisikan fungsi seperti pseuducode berikut:
function add(x, y)
return x+y endfunction
Kemudian penggunaannya adalah add(3, 5)
, secara tidak langsung menggunakan notasi prefix :)
Selain prefix untuk di awal dan infix untuk di tengah, kita juga bisa menempatkan operator setelah kedua angka, disebut notasi postfix. Contohnya: 3 5 +
Notasi postfix sebenarnya tidak terlalu asing, karena misalnya untuk menuliskan faktorial itu biasanya menggunakan tanda seru setelah angkanya, lagi-lagi secara tidak langsung menggunakan notasi postfix, seperti: 4!
Salah satu keuntungan menggunakan notasi prefix maupun postfix adalah bisa menghilangkan kurung tanpa menyebabkan ambigu. Contohnya, dalam notasi infix kita bisa menuliskan 5 * (6 + 7)
agar penjumlahan dilakukan terlebih dahulu. Sedangkan, notasi prefix maupun postfix dijamin tidak membutuhkan kurung:
- Prefix:
* 5 + 6 7
- Postfix:
6 7 + 5 *
Stack bisa sangat membantu untuk mengubah antara notasi prefix, infix, dan postfix.
Tokenisasi
Sebelum membahas konversi antara notasi prefix, infix, dan postfix, kita perlu membahas sebentar mengenai “tokenisasi” (tokenization), yaitu proses “memecah” suatu string yang utuh menjadi “bagian-bagiannya”.
Misalnya, kalau kita punya notasi infix dalam string "3 + 5"
, kita bisa melakukan tokenization untuk memecahnya menjadi ["3", "+", "5"]
.
Cara mudah untuk melakukan tokenisasi, bisa dengan sekedar menganggap tiap “bagian” atau tiap “token” terpisahkan oleh spasi, sehingga bisa di-split begitu saja:
def tokenize(string_utuh):
= string_utuh.split(" ") # string berisi satu spasi
hasil return hasil
print(tokenize("3 + 5"))
['3', '+', '5']
Agar cara mudah ini berhasil (terutama untuk notasi infix), bahkan antara kurung buka/tutup juga harus diberi spasi, ya!
print(tokenize("5 * ( 6 + 7 )"))
['5', '*', '(', '6', '+', '7', ')']
Precedence dan associativity
Sebelumnya, telah disebutkan bahwa salah satu keuntungan notasi prefix maupun postfix dibandingkan notasi infix adalah penulisan yang tidak ambigu tanpa diperlukannya kurung. Agar bisa mengubah notasi infix menjadi notasi prefix ataupun notasi postfix, tentunya kita harus bisa membaca notasi infix secara tidak ambigu. Artinya, kita harus kenal dengan aturan urutan pengoperasian.
Urusan urutan pengoperasian terbagi menjadi dua:
- Precedence, semacam tingkatan prioritas antara operasi yang berbeda, yang mana yang dilakukan duluan (apalagi kalau tidak ada tanda kurung)
- Associativity, urutan pengoperasian antara dua operasi yang precedence nya sama, apakah dari kiri ke kanan atau kanan ke kiri
Misalkan ada penulisan notasi infix: 9 + 8 * 7
Tentunya perkalian dilakukan terlebih dahulu, barulah penjumlahan. Artinya, perkalian memiliki higher precedence (atau precedence yang lebih tinggi) daripada penjumlahan; bisa juga dikatakan, penjumlahan memiliki lower precedence (atau precedence yang lebih rendah) daripada perkalian.
Sedangkan, misal ada penulisan notasi infix: 8 / 4 * 2
dan 8 * 4 / 2
Keduanya dilakukan dari kiri ke kanan. Artinya:
- Tidak ada prioritas yang lebih utama antara pembagian maupun perkalian, sehingga keduanya memiliki equal precedence (atau precedence yang sama).
- Associativity dari pembagian maupun perkalian bersifat left-to-right.
Precedence dan associativity dari beberapa operator bisa didata:
Precedence | Operator | Associativity |
---|---|---|
3 | ^ |
right-to-left |
2 | * / |
left-to-right |
1 | + - |
left-to-right |
Perhatikan:
Perpangkatan bersifat right-to-left karena
.Pembagian maupun pengurangan bersifat left-to-right karena
dan .Kebetulan, perkalian maupun penjumlahan memiliki sifat asosiatif, yaitu
sehingga perkalian maupun penjumlahan sebenarnya bersifat left-to-right maupun right-to-left sekaligus, yaitu
Namun, untuk mempermudah klasifikasi, kita bisa mengkategorikan perkalian dan penjumlahan bersifat left-to-right.
(TODO) Urusan notasi prefix, infix, dan postfix dengan stack
Notasi infix menjadi postfix
Setelah tokenisasi, berikut langkah mengubah notasi infix menjadi postfix.
Siapkan suatu stack kosong, serta tempat (misal string kosong) untuk menyimpan hasil infix. Lalu, scanning (melihat satu-per-satu) tiap token dari kiri ke kanan, dan ikuti ketentuan berikut:
Apabila token adalah operand/angka, langsung tambahkan ke hasil infix
Apabila stack kosong, atau apabila elemen teratas pada stack adalah kurung kiri, maka push token tersebut ke dalam stack
Apabila token adalah kurung kiri yaitu “(”, push ke dalam stack
Apabila token adalah kurung kanan yaitu “)”, lakukan while loop: lakukan pop pada stack, masukkan hasil pop tersebut ke hasil infix, hentikan while loop apabila hasil pop tersebut adalah kurung kiri.
Apabila token memiliki precedence yang lebih tinggi daripada elemen teratas pada stack, maka push token tersebut ke dalam stack.
Apabila token memiliki precedence yang lebih rendah daripada elemen teratas pada stack, lakukan langkah berikut: lakukan pop pada stack, lalu masukkan hasil pop tersebut ke hasil infix.
Apabila token memiliki precedence yang setara dengan elemen teratas pada stack, perhatikan associativity dari operator tersebut, lalu:
Apabila untuk operator tersebut bersifat left-to-right: lakukan pop pada stack, masukkan hasil pop ke hasil infix, lalu push token
Sedangkan apabila bersifat right-to-left: push token tersebut ke dalam stack
Setelah suatu token teratasi, tentunya langsung lanjut melihat token berikutnya. Apabila semua token sudah teratasi sedangkan stack belum kosong, maka ulangi sampai stack kosong: lakukan pop, masukkan hasil pop ke hasil infix.
Juga bisa ditulis,
Ini buat infix ke postfix
1 Buat Empty Stack (buat operator) dan empty list (buat operand).
Convert ekspresi string jadi per karakter pake .spit()
Scan dari kiri ke kanan
Jika token operand, masukin ke akhir list
Jika token kurung kiri, masukin ke stack
jika token kurung kanan, pop stack sampe si kurung kiri ilang. Masukin semua operator ke dalam akhir list.
Jika token operator *,/,+, atau -, masukin ke stack. Namun, jika operator tersebut levelnya lebih tinggi atau sama dengan yang ada di stack maka masukin “mereka” ke akhir list.
Jika token ope rator levelnya lebih rendah dari yang stack, maka pop stack lalu bandingkan lagi top yang baru dengan operator tadi.
- Ketika ekspresi sudah discan semua, cek stack. Jika ada operator lagi maka bisa dimasukin aja ke akhir list.
= {"+", "-", "*", "/", "**", "(", ")"}
Operators
def level(char):
if char == "**" or char == "^":
return 3
elif char == "/" or char == "*":
return 2
elif char =="+" or char == "-":
return 1
else:
return 0
def infix_to_postfix(expression):
= list(expression)
characters = SLStack()
stack = []
postfix
for char in characters:
if char == "(":
stack.push(char)
elif char == ")":
while stack.is_empty() != True and stack.peek() != "(":
postfix.append(stack.pop())print("2")
stack.pop()
elif char not in Operators:
postfix.append(char)
else:
while (stack.is_empty() != True and ( level(char) <= level(stack.peek()) )):
postfix.append(stack.pop())
stack.push(char)
while stack.is_empty() != True:
postfix.append(stack.peek())
stack.pop()print('1')
return "".join(postfix)
"a+b(c-d)/e") infix_to_postfix(
1
1
1
Error pop: stack sudah kosong sebelumnya.
1
'a+b(c-d/e'
= "a+b(c-d)/e"
expression print(list(expression))
['a', '+', 'b', '(', 'c', '-', 'd', ')', '/', 'e']
= {"+", "-", "*", "/", "**", "(", ")"}
Operators
= "+"
char
if char not in Operators:
print("ya, dia tidak di op")
else:
print("tidak, dia operator")
tidak, dia operator