├── .gitattributes
├── .github
└── workflows
│ └── run-tests.yml
├── .gitignore
├── .vscode
├── launch.json
├── run.sh
├── settings.json
├── setup-debugger.sh
├── tasks.json
├── visualize-c-memory.py
└── visualize-c-memory.so
├── Makefile
├── README.md
├── common.mk
├── include
├── ADTBinaryTree.h
├── ADTGraph.h
├── ADTList.h
├── ADTMap.h
├── ADTPriorityQueue.h
├── ADTQueue.h
├── ADTSet.h
├── ADTStack.h
├── ADTVector.h
├── acutest.h
├── common_types.h
└── valgrind.h
├── lib
└── Makefile
├── modules
├── UsingADTList
│ ├── ADTQueue.c
│ └── ADTStack.c
├── UsingADTSet
│ └── ADTMap.c
├── UsingAVL
│ └── ADTSet.c
├── UsingBTree
│ └── ADTSet.c
├── UsingBinarySearchTree
│ └── ADTSet.c
├── UsingDynamicArray
│ └── ADTVector.c
├── UsingHashTable
│ └── ADTMap.c
├── UsingHeap
│ └── ADTPriorityQueue.c
└── UsingLinkedList
│ └── ADTList.c
├── programs
├── cat
│ ├── Makefile
│ ├── cat.c
│ ├── input-file
│ ├── io.c
│ ├── io.h
│ └── io_test.c
├── fibonacci
│ ├── ADTIntVector.c
│ ├── ADTIntVector.h
│ ├── ADTIntVector_test.c
│ ├── Makefile
│ ├── fibonacci.c
│ ├── fibonacci.h
│ └── fibonacci_test.c
├── pair_sum
│ ├── Makefile
│ ├── pair_sum.c
│ ├── pair_sum.h
│ └── pair_sum_test.c
└── set_example
│ ├── Makefile
│ └── tests.c
└── tests
├── ADTList_test.c
├── ADTMap_test.c
├── ADTPriorityQueue_test.c
├── ADTQueue_test.c
├── ADTSet_test.c
├── ADTStack_test.c
├── ADTVector_test.c
└── Makefile
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: run-tests
2 | on: [push, pull_request]
3 |
4 | jobs:
5 | # Note: use a separate entry for each arch, instead of a matrix, cause there are several small differences between the archs
6 |
7 | build-linux:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 |
12 | - name: make run
13 | run: |
14 | make run CFLAGS=-fPIC
15 |
16 | sudo apt install valgrind
17 | make valgrind CFLAGS=-fPIC
18 |
19 | - name: make lib
20 | run: make lib CFLAGS=-fPIC
21 |
22 | # note: this can be made into anchor when they are supported. See https://github.com/actions/runner/issues/1182
23 | - name: Upload
24 | uses: actions/upload-artifact@v4
25 | with:
26 | name: ${{ github.job }}
27 | path: .
28 |
29 | build-wasm:
30 | runs-on: ubuntu-latest
31 | steps:
32 | - uses: actions/checkout@v4
33 | - uses: mymindstorm/setup-emsdk@v14
34 | with:
35 | version: 4.0.1
36 |
37 | - name: make lib
38 | run: make lib WASM=1
39 |
40 | - name: Upload
41 | uses: actions/upload-artifact@v4
42 | with:
43 | name: ${{ github.job }}
44 | path: .
45 |
46 | build-win:
47 | runs-on: windows-latest
48 | steps:
49 | - uses: actions/checkout@v4
50 |
51 | - name: make run
52 | run: make run CFLAGS=-fPIC
53 |
54 | - name: make lib
55 | run: make lib CFLAGS=-fPIC
56 |
57 | - name: Upload
58 | uses: actions/upload-artifact@v4
59 | with:
60 | name: ${{ github.job }}
61 | path: .
62 |
63 | build-macos-intel:
64 | runs-on: macos-13
65 | steps:
66 | - uses: actions/checkout@v4
67 |
68 | - name: make run
69 | run: make run CFLAGS=-fPIC
70 |
71 | - name: make lib
72 | run: make lib CFLAGS=-fPIC
73 |
74 | - name: Upload
75 | uses: actions/upload-artifact@v4
76 | with:
77 | name: ${{ github.job }}
78 | path: .
79 |
80 | build-macos-arm64:
81 | needs: build-macos-intel # to build multi-arch lib
82 | runs-on: macos-latest
83 | steps:
84 | - uses: actions/checkout@v4
85 | - uses: actions/download-artifact@v4
86 | name: build-macos-intel
87 |
88 | - name: make run
89 | run: make run CFLAGS=-fPIC
90 |
91 | - name: make lib
92 | run: |-
93 | make lib CFLAGS=-fPIC
94 |
95 | # create muilti-arch lib
96 | lipo -create ./build-macos-intel/lib/k08.a ./lib/k08.a -output lib/k08_macos.a
97 |
98 | - name: Upload
99 | uses: actions/upload-artifact@v4
100 | with:
101 | name: ${{ github.job }}
102 | path: .
103 |
104 | collect-libs:
105 | runs-on: ubuntu-latest
106 | needs: [build-linux, build-wasm, build-win, build-macos-arm64]
107 | steps:
108 | - uses: actions/download-artifact@v4
109 | - name: copy libs
110 | run: |
111 | mkdir lib
112 | cp ./build-linux/lib/k08.a lib/k08_linux.a
113 | cp ./build-wasm/lib/k08.a lib/k08_wasm.a
114 | cp ./build-win/lib/k08.a lib/k08_win.a
115 | cp ./build-macos-arm64/lib/k08_macos.a lib/k08_macos.a # multi-arch
116 |
117 | - name: Upload
118 | uses: actions/upload-artifact@v4
119 | with:
120 | name: all-libs
121 | path: lib
122 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Το git από default αποθηκεύει _όλα_ τα αρχεία, _εκτός_ όσων υπάρχουν στο .gitignore (blacklist).
2 | # Συχνά όμως είναι πιο βολικό να κάνουμε το αντίστροφο, δηλαδή να μην αποθηκεύουμε _κανένα_ αρχείο
3 | # πάρα _μόνο_ αυτά που δηλώνονται στο .gitignore (whitelist). Αυτό επιτυγχάνεται αγνοώντας τα πάντα (*)
4 | # και χρησιμοποιώντας το "!" που σημαίνει "μην αγνοήσεις το συγκεκριμένο pattern".
5 |
6 | # Αγνοούμε όλα τα αρχεία (όχι τα directories)
7 | *
8 | !*/
9 |
10 | # Εκτός από τα παρακάτω
11 | !*.c
12 | !*.h
13 | !*.mk
14 | !Makefile
15 | !.gitignore
16 | !README.md
17 | !.vscode/*.json
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Make: compile and debug",
6 | "type": "cppdbg",
7 | "request": "launch",
8 | "program": "${workspaceFolder}/.vscode/bash",
9 | "args": [
10 | "${workspaceFolder}/.vscode/run.sh",
11 | ],
12 | "stopAtEntry": false,
13 | "cwd": "${workspaceFolder}/${config:c_project.dir}",
14 | "externalConsole": false,
15 | "linux": {
16 | "MIMode": "gdb",
17 | "internalConsoleOptions": "neverOpen", // don't show the debugger console
18 | "environment": [
19 | {"name":"LD_PRELOAD", "value":"${workspaceFolder}/.vscode/visualize-c-memory.so"},
20 | {"name":"CODE_DEBUG", "value":"./${config:c_project.program} ${config:c_project.args}"},
21 | ],
22 | "setupCommands": [
23 | {
24 | "description": "Enable pretty-printing for gdb",
25 | "text": "-enable-pretty-printing",
26 | "ignoreFailures": true
27 | },
28 | {
29 | "text": "source ${workspaceFolder}/.vscode/visualize-c-memory.py",
30 | "ignoreFailures": true
31 | }
32 | ]
33 | },
34 | "osx": {
35 | "MIMode": "lldb", // on macOS gdb is hard to setup
36 | "internalConsoleOptions": "openOnSessionStart", // open the debugger console, lldb sends output only there
37 | "environment": [
38 | {"name":"CODE_DEBUG", "value":"./${config:c_project.program} ${config:c_project.args}"},
39 | ],
40 | "setupCommands": [
41 | {
42 | "description": "Don't stop on exec", // lldb stops execution on "exec", we need to continue since we run via bash
43 | "text": "settings set target.process.stop-on-exec false",
44 | "ignoreFailures": false
45 | }
46 | ],
47 | },
48 | "preLaunchTask": "debug-make",
49 | },
50 | {
51 | "name": "Single file: compile and debug",
52 | "type": "cppdbg",
53 | "request": "launch",
54 | "program": "${workspaceFolder}/.vscode/bash",
55 | "args": [
56 | "${workspaceFolder}/.vscode/run.sh",
57 | ],
58 | "stopAtEntry": false,
59 | "cwd": "${fileDirname}",
60 | "environment": [],
61 | "externalConsole": false,
62 | "linux": {
63 | "MIMode": "gdb",
64 | "internalConsoleOptions": "neverOpen", // don't show the debugger console
65 | "environment": [
66 | {"name":"LD_PRELOAD", "value":"${workspaceFolder}/.vscode/visualize-c-memory.so"},
67 | {"name":"CODE_DEBUG", "value":"./${fileBasenameNoExtension} ${config:c_project.args}"},
68 | ],
69 | "setupCommands": [
70 | {
71 | "description": "Enable pretty-printing for gdb",
72 | "text": "-enable-pretty-printing",
73 | "ignoreFailures": true
74 | },
75 | {
76 | "text": "source ${workspaceFolder}/.vscode/visualize-c-memory.py",
77 | "ignoreFailures": true
78 | }
79 | ]
80 | },
81 | "osx": {
82 | "MIMode": "lldb", // on macOS gdb is hard to setup
83 | "internalConsoleOptions": "openOnSessionStart", // open the debugger console, lldb sends output only there
84 | "environment": [
85 | {"name":"CODE_DEBUG", "value":"./${fileBasenameNoExtension} ${config:c_project.args}"},
86 | ],
87 | "setupCommands": [
88 | {
89 | "description": "Don't stop on exec", // lldb stops execution on "exec", we need to continue since we run via bash
90 | "text": "settings set target.process.stop-on-exec false",
91 | "ignoreFailures": false
92 | }
93 | ],
94 | },
95 | "preLaunchTask": "debug-single-file"
96 | },
97 | {
98 | "name": "All files in directory: compile and debug",
99 | "type": "cppdbg",
100 | "request": "launch",
101 | "program": "${workspaceFolder}/.vscode/bash",
102 | "args": [
103 | "${workspaceFolder}/.vscode/run.sh",
104 | ],
105 | "stopAtEntry": false,
106 | "cwd": "${fileDirname}",
107 | "environment": [],
108 | "externalConsole": false,
109 | "linux": {
110 | "MIMode": "gdb",
111 | "internalConsoleOptions": "neverOpen", // don't show the debugger console
112 | "environment": [
113 | {"name":"LD_PRELOAD", "value":"${workspaceFolder}/.vscode/visualize-c-memory.so"},
114 | {"name":"CODE_DEBUG", "value":"./${config:c_project.program} ${config:c_project.args}"},
115 | ],
116 | "setupCommands": [
117 | {
118 | "description": "Enable pretty-printing for gdb",
119 | "text": "-enable-pretty-printing",
120 | "ignoreFailures": true
121 | },
122 | {
123 | "text": "source ${workspaceFolder}/.vscode/visualize-c-memory.py",
124 | "ignoreFailures": true
125 | }
126 | ]
127 | },
128 | "osx": {
129 | "MIMode": "lldb", // on macOS gdb is hard to setup
130 | "internalConsoleOptions": "openOnSessionStart", // open the debugger console, lldb sends output only there
131 | "environment": [
132 | {"name":"CODE_DEBUG", "value":"./${config:c_project.program} ${config:c_project.args}"},
133 | ],
134 | "setupCommands": [
135 | {
136 | "description": "Don't stop on exec", // lldb stops execution on "exec", we need to continue since we run via bash
137 | "text": "settings set target.process.stop-on-exec false",
138 | "ignoreFailures": false
139 | }
140 | ],
141 | },
142 | "preLaunchTask": "debug-all-files"
143 | },
144 | ]
145 | }
--------------------------------------------------------------------------------
/.vscode/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Helper script for executing programs in tasks/debugger with flexibility for
4 | # passing arguments, input/output redirection, etc.
5 |
6 | if [ -z "$CODE_DEBUG" ]
7 | then
8 | echo " * Executing: $CODE_RUN"
9 | echo
10 |
11 | # If the child process launched by eval $CODE_RUN below segfaults, bash by
12 | # default prints the current script's (run.sh) full path and line number,
13 | # before the "Segmentation fault" message. We can make the error message cleaner
14 | # by using the "trap" below.
15 | #
16 | # NOTE1: the trap handler is not really executed since the segfault happens
17 | # in a child process. However the presence of the trap somehow causes bash
18 | # not to print the script name, so the message becomes cleaner. We still
19 | # echo a relevant message, just in case the trap does run on some system.
20 | #
21 | # NOTE2: SIGABRT does the same for asserts
22 | #
23 | trap 'echo "Segmentation fault"' SIGSEGV SIGABRT
24 |
25 | # we use "eval" to allow for input/output redirections
26 | eval $CODE_RUN
27 |
28 | else
29 | echo " * Executing: $CODE_DEBUG"
30 | echo
31 |
32 | # When debugging we exec in the same process being debugged
33 | eval "exec $CODE_DEBUG"
34 | fi
35 |
36 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "c_project": {
3 | // Όνομα του εκτελέσιμου προγράμματος (μόνο για μεταγλώττιση πολλών αρχείων & make)
4 | "program": "cat",
5 |
6 | // Directory στο οποίο βρίσκεται το πρόγραμμα (μόνο για μεταγλώτισση με make)
7 | "dir": "programs/cat",
8 |
9 | // Ορίσματα του προγράμματος.
10 | // Υποστηρίζονται ανακατευθύνσεις εισόδου (< input-file) και εξόδου (> output-file).
11 | //
12 | "args": "input-file",
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/.vscode/setup-debugger.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # We start debugging via bash which then execs the debugged process (see launch.json).
4 | # This is convenient for setting arguments and redirections, but has 2 issues:
5 | #
6 | # 1. macOS/arm does not allow us to debug /bin/bash
7 | # 2. We get a debugger warning that /bin/bash is older than the source files.
8 | #
9 | # To workaround these issues we run this script before debugging which creates
10 | # a .vscode/bash symlink. This way:
11 | # 1. On macOS we can use homebrew's bash instead of the system bash
12 | # (under /opt/homebrew/bin for arm, /usr/local/bin for intel).
13 | # 2. The symlink has newer timestamp so we avoid the warning.
14 |
15 | for path in /opt/homebrew/bin /usr/local/bin /bin
16 | do
17 | if [ -x $path/bash ]
18 | then
19 | ln -sf $path/bash ./bash
20 | exit 0
21 | fi
22 | done
23 |
24 | echo Bash not found
25 | exit 1
26 |
27 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | ////////////// Compile ///////////////////////////////////
5 | {
6 | // we also tried "cppbuild". POS: no 1 second delay. NEG: more verbose, no color output, less reliable matched (ctrl-click often failed)
7 | "type": "shell",
8 | "label": "Single file: compile",
9 | "command": "gcc",
10 | "args": [
11 | "-g",
12 | "-Werror",
13 | "-Wall",
14 | "${fileBasename}",
15 | "-lm",
16 | "-o",
17 | "${fileBasenameNoExtension}"
18 | ],
19 | "options": {
20 | "cwd": "${fileDirname}"
21 | },
22 | "presentation": {
23 | "clear": true,
24 | "showReuseMessage": false
25 | },
26 | "problemMatcher": {
27 | "base": "$gcc",
28 | // without explicitly setting a relative fileLocation the errors on the __problems pane__ are not clickable
29 | "fileLocation": [
30 | "relative",
31 | "${fileDirname}"
32 | ]
33 | },
34 | "group": "build"
35 | },
36 | {
37 | "type": "shell",
38 | "label": "All files in directory: compile",
39 | "command": "gcc",
40 | "args": [
41 | "-g",
42 | "-Werror",
43 | "-Wall",
44 | "*.c",
45 | "-lm",
46 | "-o",
47 | "${config:c_project.program}"
48 | ],
49 | "options": {
50 | "cwd": "${fileDirname}"
51 | },
52 | "presentation": {
53 | "clear": true,
54 | "showReuseMessage": false
55 | },
56 | "problemMatcher": {
57 | "base": "$gcc",
58 | // without explicitly setting a relative fileLocation the errors on the __problems pane__ are not clickable
59 | "fileLocation": [
60 | "relative",
61 | "${fileDirname}"
62 | ]
63 | },
64 | "group": "build"
65 | },
66 | {
67 | "type": "shell",
68 | "label": "Make: compile",
69 | "command": "make",
70 | "args": [
71 | "${config:c_project.program}"
72 | ],
73 | "options": {
74 | "cwd": "${workspaceFolder}/${config:c_project.dir}"
75 | },
76 | "presentation": {
77 | "clear": true,
78 | "showReuseMessage": false
79 | },
80 | "problemMatcher": {
81 | "base": "$gcc",
82 | // without explicitly setting a relative fileLocation the errors on the __problems pane__ are not clickable
83 | "fileLocation": [
84 | "relative",
85 | "${workspaceFolder}/${config:c_project.dir}"
86 | ]
87 | },
88 | "group": "build"
89 | },
90 |
91 | ////////////// Compile and run ///////////////////////////////////
92 | {
93 | "type": "shell", // less verbose
94 | "label": "Single file: compile and run",
95 | "command": "${workspaceFolder}/.vscode/run.sh",
96 | "args": [],
97 | "options": {
98 | "cwd": "${fileDirname}", // important for line-matching in error messages
99 | "env": {
100 | "CODE_RUN": "./${fileBasenameNoExtension} ${config:c_project.args}",
101 | },
102 | "shell": {
103 | "executable": "bash",
104 | }
105 | },
106 | "presentation": {
107 | "echo": false,
108 | },
109 | "problemMatcher": [],
110 | "dependsOn": "Single file: compile",
111 | "group": "build"
112 | },
113 | {
114 | "type": "shell",
115 | "label": "All files in directory: compile and run",
116 | "command": "${workspaceFolder}/.vscode/run.sh",
117 | "args": [],
118 | "options": {
119 | "cwd": "${fileDirname}", // important for line-matching in error messages
120 | "env": {
121 | "CODE_RUN": "./${config:c_project.program} ${config:c_project.args}",
122 | },
123 | "shell": {
124 | "executable": "bash",
125 | }
126 | },
127 | "presentation": {
128 | "echo": false,
129 | },
130 | "problemMatcher": [],
131 | "dependsOn": "All files in directory: compile",
132 | "group": "build"
133 | },
134 | {
135 | "type": "shell",
136 | "label": "Make: compile and run",
137 | "command": "${workspaceFolder}/.vscode/run.sh",
138 | "args": [],
139 | "options": {
140 | "cwd": "${workspaceFolder}/${config:c_project.dir}", // important for line-matching in error messages
141 | "env": {
142 | "CODE_RUN": "./${config:c_project.program} ${config:c_project.args}",
143 | },
144 | "shell": {
145 | "executable": "bash",
146 | }
147 | },
148 | "presentation": {
149 | "echo": false,
150 | },
151 | "problemMatcher": [],
152 | "dependsOn": "Make: compile",
153 | "group": {
154 | "kind": "build",
155 | "isDefault": true
156 | }
157 | },
158 |
159 |
160 | ////////////// Compile and run with valgrind ///////////////////////////////////
161 | {
162 | "type": "shell",
163 | "label": "Single file: compile and run with valgrind",
164 | "command": "${workspaceFolder}/.vscode/run.sh",
165 | "args": [],
166 | "options": {
167 | "cwd": "${fileDirname}", // important for line-matching in error messages
168 | "env": {
169 | "CODE_RUN": "valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./${fileBasenameNoExtension} ${config:c_project.args}",
170 | },
171 | "shell": {
172 | "executable": "bash",
173 | }
174 | },
175 | "presentation": {
176 | "echo": false,
177 | },
178 | "problemMatcher": [],
179 | "dependsOn": "Single file: compile",
180 | "group": "build"
181 | },
182 | {
183 | "type": "shell",
184 | "label": "All files in directory: compile and run with valgrind",
185 | "command": "${workspaceFolder}/.vscode/run.sh",
186 | "args": [],
187 | "options": {
188 | "cwd": "${fileDirname}", // important for line-matching in error messages
189 | "env": {
190 | "CODE_RUN": "valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./${config:c_project.program} ${config:c_project.args}",
191 | },
192 | "shell": {
193 | "executable": "bash",
194 | }
195 | },
196 | "presentation": {
197 | "echo": false,
198 | },
199 | "problemMatcher": [],
200 | "dependsOn": "All files in directory: compile",
201 | "group": "build"
202 | },
203 | {
204 | "type": "shell",
205 | "label": "Make: compile and run with valgrind",
206 | "command": "${workspaceFolder}/.vscode/run.sh",
207 | "args": [],
208 | "options": {
209 | "cwd": "${workspaceFolder}/${config:c_project.dir}", // important for line-matching in error messages
210 | "env": {
211 | "CODE_RUN": "valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./${config:c_project.program} ${config:c_project.args}",
212 | },
213 | "shell": {
214 | "executable": "bash",
215 | }
216 | },
217 | "presentation": {
218 | "echo": false,
219 | },
220 | "problemMatcher": [],
221 | "dependsOn": "Make: compile",
222 | "group": "build"
223 | },
224 |
225 | ////////////// Set args ///////////////////////////////////
226 | {
227 | "type": "cppbuild", // no 1 second delay
228 | "label": "Set program arguments",
229 | "command": "code",
230 | "args": [
231 | "${workspaceFolder}/.vscode/settings.json"
232 | ],
233 | "presentation": {
234 | "echo": false,
235 | "reveal": "never"
236 | },
237 | "problemMatcher": [],
238 | "group": "build"
239 | },
240 |
241 | ////////////// For the debugger (see launch.json) ///////////////////////////////////
242 | {
243 | "label": "debug-single-file",
244 | "hide": true,
245 | "dependsOrder": "sequence",
246 | "dependsOn": ["Single file: compile", "Setup debugger"]
247 | },
248 | {
249 | "label": "debug-all-files",
250 | "hide": true,
251 | "dependsOrder": "sequence",
252 | "dependsOn": ["All files in directory: compile", "Setup debugger"]
253 | },
254 | {
255 | "label": "debug-make",
256 | "hide": true,
257 | "dependsOrder": "sequence",
258 | "dependsOn": ["Make: compile", "Setup debugger"]
259 | },
260 | {
261 | "type": "cppbuild", // no 1 second delay
262 | "label": "Setup debugger",
263 | "hide": true,
264 | "command": "./setup-debugger.sh",
265 | "args": [],
266 | "options": {
267 | "cwd": "${workspaceFolder}/.vscode",
268 | "shell": {
269 | "executable": "bash",
270 | }
271 | },
272 | "presentation": {
273 | "echo": false,
274 | "reveal": "never"
275 | },
276 | "problemMatcher": [],
277 | "group": "build"
278 | }
279 | ],
280 | }
--------------------------------------------------------------------------------
/.vscode/visualize-c-memory.py:
--------------------------------------------------------------------------------
1 | import gdb # pyright: reportMissingImports=false
2 | import subprocess
3 | import json
4 | import html
5 | import traceback
6 |
7 |
8 | ### Register pretty printer ######################
9 |
10 | class MemoryPrinter:
11 | def __init__(self):
12 | pass
13 | def to_string(self):
14 | return visualize_memory()
15 |
16 | def lookup_printer(value):
17 | # Use MemoryPrinter if value is the string "memory"
18 | if value.type.strip_typedefs().code == gdb.TYPE_CODE_ARRAY and value.type.target().strip_typedefs().code == gdb.TYPE_CODE_INT and value.string() == "memory":
19 | return MemoryPrinter()
20 | else:
21 | return None
22 |
23 | gdb.pretty_printers.append(lookup_printer)
24 |
25 |
26 | ### The actual visualization ########################
27 |
28 | # Returns a json visualization of memory that can be consumed by vscode-debug-visualizer
29 | def visualize_memory():
30 | try:
31 | return json.dumps({
32 | 'kind': { 'svg': True },
33 | 'text': svg_of_memory(),
34 | })
35 | except BaseException as e:
36 | # display errors using the text visualizer
37 | return json.dumps({
38 | 'kind': { 'text': True },
39 | 'text': str(e) + "\n\n\n\n\n\n\n" + traceback.format_exc()
40 | })
41 |
42 | def svg_of_memory():
43 | memory = {
44 | 'stack': recs_of_stack(),
45 | 'heap': rec_of_heap(),
46 | }
47 | infer_heap_types(memory)
48 |
49 | # If the heap is too large, show only the last 100 entries
50 | if(len(memory['heap']['values']) > 100):
51 | memory['heap']['name'] = 'Heap (100 most recent entries)'
52 | memory['heap']['values'] = memory['heap']['values'][-100:]
53 | memory['heap']['fields'] = memory['heap']['fields'][-100:]
54 |
55 | dot = f"""
56 | digraph G {{
57 | layout = neato;
58 | overlap = false;
59 | node [style=none, shape=none];
60 |
61 | {dot_of_stack(memory)}
62 | dummy[pos="1,0!",style=invis,width=0.8]; // space
63 | {dot_of_heap(memory)}
64 | {dot_of_pointers(memory)}
65 | }}
66 | """
67 |
68 | # debugging
69 | # print(dot)
70 | # return json.dumps({
71 | # 'kind': { 'text': True },
72 | # 'text': dot,
73 | # })
74 |
75 | # vscode-debug-visualizer can directly display graphviz dot format. However
76 | # its implementation has issues when the visualization is updated, after the
77 | # update it's often corrupted. Maybe this has to do with the fact that
78 | # vscode-debug-visualizer runs graphviz in wasm (via viz.js).
79 | #
80 | # To avoid the isses we run graphviz ourselves, convert to svg, and use the svg visualizer.
81 | # The downside is that graphviz needs to be installed.
82 | svg = subprocess.run(
83 | ['dot', '-T', 'svg'],
84 | input=dot.encode('utf-8'),
85 | stdout=subprocess.PIPE, stderr=subprocess.PIPE)
86 |
87 | if svg.returncode != 0:
88 | raise Exception(f"dot failed:\n {svg.stderr.decode('utf-8')}\n\ndot source:\n{dot}")
89 |
90 | return svg.stdout.decode('utf-8')
91 |
92 | def dot_of_stack(memory):
93 | rows = [['
Stack | ']]
94 | for frame_rec in memory['stack']:
95 | rows += rows_of_rec(frame_rec, memory)
96 |
97 | return f"""
98 | stack[pos="0,0!",label=<
99 | {table_of_rows(rows)}
100 | >];
101 | """
102 |
103 | def dot_of_heap(memory):
104 | # pos="2,0" makes heap to be slightly on the right of stack/dummy.
105 | # overlap = false will force it further to the right, to avoid overlap.
106 | # but pos="2,0" is important to establish the relative position between the two.
107 |
108 | rows = rows_of_rec(memory['heap'], memory)
109 | return f"""
110 | heap[pos="2,0!",label=<
111 | {table_of_rows(rows)}
112 | >];
113 | """
114 |
115 | def table_of_rows(rows):
116 | res = f"""
117 |
118 | """
119 |
120 | col_n = max([len(row) for row in rows])
121 | for row in rows:
122 | # the last cell is the address, put it first
123 | row.insert(0, row.pop())
124 |
125 | # if the row has missing columns, add a colspan to the last cell
126 | if len(row) < col_n:
127 | row[-1] = row[-1].replace('{"".join(row)}\n'
130 |
131 | res += ' |
'
132 | return res
133 |
134 | def dot_of_pointers(memory):
135 | # construct stack:"XXXXXXX-right":e or heap:"XXXXXX-left":w
136 | def anchor_of_rec(rec):
137 | return rec['area'] + ':"' + rec["address"] + ('-right":e' if rec['area'] == 'stack' else '-left":w')
138 |
139 | res = ""
140 | for rec in find_pointers(memory):
141 | target_rec = lookup_address(rec['value'], memory)
142 | if target_rec is not None:
143 | res += f"""
144 | {anchor_of_rec(rec)} -> {anchor_of_rec(target_rec)};
145 | """
146 | return res
147 |
148 | def rows_of_rec(rec, memory):
149 | if rec['kind'] in ['array', 'struct', 'frame']:
150 | res = []
151 | for i in range(len(rec['values'])):
152 | name = rec['fields'][i] if rec['kind'] != 'array' else str(i)
153 | value_rec = rec['values'][i]
154 | rows = rows_of_rec(value_rec, memory)
155 |
156 | if len(rows) == 0: # it can happen in case of empty array
157 | continue
158 |
159 | # the name is only inserted in the first row, with a rowspan to include all of them
160 | # an empty cell is also added to all other rows, so that len(row) always gives the number of cols
161 | rows[0].insert(0, f"""
162 | {name} |
163 | """)
164 | for row in rows[1:]:
165 | row.insert(0, '')
166 |
167 | res += rows
168 |
169 | if rec['kind'] == 'frame':
170 | # at least 170 width, to avoid initial heap looking tiny
171 | res.insert(0, [f'{rec["name"]} | '])
172 |
173 | else:
174 | color = 'red' if rec['kind'] == 'pointer' and rec['value'] != "0" and lookup_address(rec['value'], memory) is None else 'black'
175 | res = [[
176 | f"""{rec['value']} | """,
177 | f"""{rec['address']} ({rec['size']}) | """,
178 | ]]
179 |
180 | return res
181 |
182 |
183 | def address_within_rec(address, rec):
184 | address_i = int(address, 16)
185 | rec_i = int(rec['address'], 16)
186 | return address_i >= rec_i and address_i < rec_i + rec['size']
187 |
188 | # Check if address is within any of the known records, if so return that record
189 | def lookup_address(address, memory):
190 | for rec in [memory['heap']] + memory['stack']:
191 | res = lookup_address_rec(address, rec)
192 | if res != None:
193 | return res
194 | return None
195 |
196 | def lookup_address_rec(address, rec):
197 | if rec['kind'] in ['array', 'struct', 'frame']:
198 | for value in rec['values']:
199 | res = lookup_address_rec(address, value)
200 | if res != None:
201 | return res
202 | return None
203 | else:
204 | return rec if address_within_rec(address, rec) else None
205 |
206 |
207 | # Check if any of the known (non-void) pointers points to address, if so return the rec of the pointer
208 | def lookup_pointer(address, memory):
209 | for rec in find_pointers(memory):
210 | # exclud void pointers
211 | if rec['value'] == address and rec['type'].target().code != gdb.TYPE_CODE_VOID:
212 | return rec
213 | return None
214 |
215 | # recursively finds all pointers
216 | def find_pointers(memory):
217 | return find_pointers_rec(memory['heap']) + \
218 | [pointer for frame in memory['stack'] for pointer in find_pointers_rec(frame)]
219 |
220 | def find_pointers_rec(rec):
221 | if rec['kind'] in ['frame', 'array', 'struct']:
222 | return [pointer for rec in rec['values'] for pointer in find_pointers_rec(rec)]
223 | elif rec['kind'] == 'pointer':
224 | return [rec]
225 | else:
226 | return []
227 |
228 | def format_pointer(val):
229 | return hex(int(val)).replace('0x', "") if val is not None else ""
230 |
231 | def rec_of_heap():
232 | # we return a 'frame' rec
233 | rec = {
234 | 'kind': 'frame',
235 | 'name': 'Heap',
236 | 'fields': [],
237 | 'values': [],
238 | }
239 |
240 | # get the linked list from watch_heap.c
241 | try:
242 | heap_node_ptr = gdb.parse_and_eval("heap_contents")['next']
243 | except:
244 | raise Exception(
245 | "Heap information not found.\n"
246 | "You need to load visualize-c-memory.so by setting the environment variable\n"
247 | " LD_PRELOAD=/visualize-c-memory.so\n"
248 | "_or_ link your program with visualize-c-memory.c"
249 | )
250 |
251 | while int(heap_node_ptr) != 0:
252 | # read node from the linked list
253 | heap_node = heap_node_ptr.dereference()
254 | pointer = heap_node['pointer']
255 | size = int(heap_node['size'])
256 | source = chr(heap_node['source'])
257 | heap_node_ptr = heap_node['next']
258 |
259 | # for the moment we have no type information, so we just create an 'untyped' record
260 | rec['fields'].append(f"{'malloc' if source == 'm' else 'realloc' if source == 'r' else 'calloc'}({size})")
261 | rec['values'].append({
262 | 'name': " ", # space to avoid errors
263 | 'value': "?",
264 | 'size': size,
265 | 'address': format_pointer(pointer),
266 | 'area': 'heap',
267 | 'kind': 'untyped',
268 | })
269 |
270 | # the linked list contains the heap contents in reverse order
271 | rec['fields'].reverse()
272 | rec['values'].reverse()
273 |
274 | return rec
275 |
276 | def infer_heap_types(memory):
277 | for i,rec in enumerate(memory['heap']['values']):
278 | if rec['kind'] != 'untyped':
279 | continue
280 |
281 | incoming_pointer = lookup_pointer(rec['address'], memory)
282 | if incoming_pointer is None:
283 | continue
284 |
285 | type = incoming_pointer['type']
286 | if type.target().code == gdb.TYPE_CODE_VOID:
287 | continue # void pointer, not useful
288 |
289 | if type.target().sizeof == 0:
290 | # pointer to incomplete struct, just add the type name to the "?" value
291 | code_name = 'struct ' if type.target().code == gdb.TYPE_CODE_STRUCT else \
292 | 'union ' if type.target().code == gdb.TYPE_CODE_UNION else ''
293 | rec['value'] = f'? ({code_name}{type.target().name})'
294 | continue
295 |
296 | # we use the type information to get a typed value, then
297 | # replace the heap rec with a new one obtained from the typed value
298 | n = int(rec['size'] / type.target().sizeof)
299 | if n > 1:
300 | # the malloced space is larger than the pointer's target type, most likely this is used as an array
301 | # we treat the pointer as a pointer to array
302 | type = type.target().array(n-1).pointer()
303 |
304 | value = gdb.Value(int(rec['address'], 16)).cast(type).dereference()
305 | memory['heap']['values'][i] = rec_of_value(value, 'heap')
306 |
307 | # the new value might itself contain pointers which can be used to
308 | # type records we already processed. So re-start frrom scratch
309 | return infer_heap_types(memory)
310 |
311 | def recs_of_stack():
312 | res = []
313 | frame = gdb.newest_frame()
314 | while frame is not None:
315 | res.append(rec_of_frame(frame))
316 | frame = frame.older()
317 |
318 | res.reverse()
319 | return res
320 |
321 | def rec_of_frame(frame):
322 | # we want blocks in reverse order, but symbols within the block in the correct order!
323 | blocks = [frame.block()]
324 | while blocks[0].function is None:
325 | blocks.insert(0, blocks[0].superblock)
326 |
327 | rec = {
328 | 'kind': 'frame',
329 | 'name': frame.function().name,
330 | 'fields': [],
331 | 'values': [],
332 | }
333 | for block in blocks:
334 | for symb in block:
335 | # avoid "weird" symbols, eg labels
336 | if not (symb.is_variable or symb.is_argument or symb.is_function or symb.is_constant):
337 | continue
338 |
339 | var = symb.name
340 | rec['fields'].append(var)
341 | rec['values'].append(rec_of_value(symb.value(frame), 'stack'))
342 |
343 | return rec
344 |
345 | def rec_of_value(value, area):
346 | type = value.type.strip_typedefs()
347 | rec = {
348 | 'size': type.sizeof,
349 | 'address': format_pointer(value.address),
350 | 'type': type,
351 | 'area': area,
352 | }
353 |
354 | if type.code == gdb.TYPE_CODE_ARRAY:
355 | # stack arrays of dynamic length (eg int foo[n]) might have huge size before the
356 | # initialization code runs! In this case replace type with one of size 0
357 | if int(type.sizeof) > 1000:
358 | type = type.target().array(-1)
359 |
360 | array_size = int(type.sizeof / type.target().sizeof)
361 |
362 | rec['values'] = [rec_of_value(value[i], area) for i in range(array_size)]
363 | rec['kind'] = 'array'
364 |
365 | elif type.code == gdb.TYPE_CODE_STRUCT:
366 | rec['fields'] = [field.name for field in type.fields()]
367 | rec['values'] = [rec_of_value(value[field], area) for field in type.fields()]
368 | rec['kind'] = 'struct'
369 |
370 | else:
371 | # treat function pointers as scalar values
372 | is_pointer = type.code == gdb.TYPE_CODE_PTR
373 | func_pointer = is_pointer and type.target().code == gdb.TYPE_CODE_FUNC
374 |
375 | if is_pointer and not func_pointer:
376 | rec['value'] = format_pointer(value)
377 | rec['kind'] = 'pointer'
378 | else:
379 | try:
380 | rec['value'] = html.escape(value.format_string())
381 | except:
382 | rec['value'] = '?'
383 | rec['kind'] = 'other'
384 | if func_pointer:
385 | rec['value'] = rec['value'].replace("0x", "").replace(" ", "
")
386 |
387 | return rec
--------------------------------------------------------------------------------
/.vscode/visualize-c-memory.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chatziko-k08/lecture-code/e45b093bdc91d08d0459fb90186ae0299fb00fc1/.vscode/visualize-c-memory.so
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Το Makefile αυτό βρίσκεται στο root ολόκληρου του project και χρησιμεύει για
2 | # να κάνουμε εύκολα compile πολλά τμήματα του project μαζί. Το Makefile αυτό
3 | # καλεί το make στα διάφορα directories ως
4 | # $(MAKE) -C
5 | # το οποίο είναι ισοδύναμο με το να τρέξουμε make μέσα στο directory
6 |
7 | # Τρέχουμε το make με --silent γιατί η έξοδος είναι τεράστια
8 | MAKE += --silent
9 |
10 | # Ολα τα directories μέσα στο programs directory
11 | PROGRAMS = $(subst programs/, , $(wildcard programs/*))
12 |
13 | # Compile: όλα, προγράμματα, βιβλιοθήκη και tests
14 | all: programs lib tests
15 |
16 | # Η παρακάτω γραμμή δημιουργεί ένα target programs- για οποιοδήποτε . Η μεταβλητή $* περιέχει το "foo"
17 | programs-%:
18 | $(MAKE) -C programs/$*
19 |
20 | programs: $(addprefix programs-, $(PROGRAMS)) # depend στο programs- για κάθε στοιχείο του PROGRAMS
21 |
22 | tests:
23 | $(MAKE) -C tests all
24 |
25 | lib:
26 | $(MAKE) -C lib all
27 |
28 | # Εκτέλεση: όλα, προγράμματα, tests
29 | run: run-tests run-programs
30 |
31 | run-programs-%:
32 | $(MAKE) -C programs/$* run
33 |
34 | run-programs: $(addprefix run-programs-, $(PROGRAMS))
35 |
36 | run-tests:
37 | $(MAKE) -C tests run
38 |
39 | # Εκτέλεση με valgrind: όλα, προγράμματα, tests
40 | valgrind: valgrind-tests valgrind-programs
41 |
42 | valgrind-programs-%:
43 | $(MAKE) -C programs/$* valgrind
44 |
45 | valgrind-programs: $(addprefix valgrind-programs-, $(PROGRAMS))
46 |
47 | valgrind-tests:
48 | $(MAKE) -C tests valgrind
49 |
50 | # Εκκαθάριση
51 | clean-programs-%:
52 | $(MAKE) -C programs/$* clean
53 |
54 | clean: $(addprefix clean-programs-, $(PROGRAMS))
55 | $(MAKE) -C tests clean
56 | $(MAKE) -C lib clean
57 |
58 | # Δηλώνουμε ότι οι παρακάτω κανόνες είναι εικονικοί, δεν παράγουν αρχεία. Θέλουμε δηλαδή
59 | # το "make programs" να εκτελεστεί παρόλο που υπάρχει ήδη ένα directory "programs".
60 | #
61 | .PHONY: programs tests lib run run-programs run-tests clean
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## lecture-code
4 |
5 | Ο βασικός κώδικας που χρησιμοποιείται στις διαλέξεις του μαθήματος [Δομές Δεδομένων και Τεχνικές Προγραμματισμού](https://k08.chatzi.org).
6 |
7 | ### Παράδειγμα χρήσης
8 |
9 | ```bash
10 | cd programs/cat
11 |
12 | make cat
13 | ./cat Makefile
14 |
15 | make io_test
16 | ./io_test
17 |
18 | make clean
19 | ```
20 |
21 | ### Project structure
22 |
23 | - `include`
24 |
25 | Κοινόχρηστα include files (μπορούν να χρησιμοποιηθούν από οποιοδήποτε πρόγραμμα ή module).
26 | Περιγράφουν τις λειτουργίες κάθε Abstract Data Type. Πχ ένα πρόγραμμα
27 | που θέλει να χρησιμοποιήσει ένα `Vector` κάνει `#include "ADTVector.h"`.
28 |
29 | - `lib`
30 |
31 | Κοινόχρηστες βιβλιοθήκες. Εκτελώντας `make` σε αυτό το directory παράγεται η βιβλιοθήκη
32 | `k08.a` η οποία περιέχει υλοποιήσεις από όλα τα ADTs.
33 |
34 | - `programs`
35 |
36 | Εκτελέσιμα προγράμματα. Ενα πρόγραμμα που χρησιμοποιεί κάποιο ADT, πχ το `Vector`, θα πρέπει
37 | 1. να κάνει `#include "ADTVector.h"`
38 | 2. να γίνει link με το `lib/k08.a` (ή με κάποια συγκεκριμένη υλοποίηση του ADT)
39 |
40 | - `modules`
41 |
42 | Κοινόχρηστα modules, παρέχουν υλοποιήσεις των διαφόρων ADTs. Κατηγοριοποιούνται με βάση τη
43 | δομή δεδομένων που υλοποιεί το ADT, πχ το `UsingDynamicArray/ADTVector.c` υλοποιεί ένα `Vector`
44 | μέσω Dynamic Array.
45 |
46 | - `tests`
47 |
48 | Tests για κοινόχρηστα modules (ένα για κάθε ADT). Οποιαδήποτε υλοποίηση ενός ADT οφείλει να
49 | περνάει το αντίστοιχο test. Για να φτιάξουμε ένα εκτελέσιμο κάνουμε link
50 | το test με την υλοποίηση που θέλουμε να ελέγξουμε, πχ
51 | το `ADTVector_test.o` με το `UsingDynamicArray/ADTVector.o`.
--------------------------------------------------------------------------------
/common.mk:
--------------------------------------------------------------------------------
1 | # common.mk
2 | #
3 | # Το αρχείο αυτό επιτρέπει την εύκολη δημιουργία Makefiles με πολλαπλά targets
4 | # αποφεύγοντας την επανάληψη των ίδιων εντολών. Χρησιμοποιείται φτιάχνοντας
5 | # ένα ή περισσότερα Makefiles, και μέσα στο κάθε Makefile:
6 | #
7 | # 1. προσθέτουμε για κάθε εκτελέσιμο μια μεταβλητή _OBJS με
8 | # όλα τα objects (.o) που χρειάζονται για το πρόγραμμα. Πχ
9 | # myprogram_OBJS = main.o module1.o module2.o
10 | #
11 | # 2. Κάνουμε: include common.mk
12 | #
13 | # Για κάθε εκτελέσιμο παράγονται τα εξής targets:
14 | # Κάνει compile το
15 | # run- Κάνει compile και εκτελεί το
16 | # valgrind- Κάνει compile και εκτελεί το μέσω valgrind
17 | # coverage- Κάνει compile και εκτελεί το , και παράγε coverage report
18 | #
19 | # Και επιπλέον παράγονται τα παρακάτω γενικά targets:
20 | # all Κάνει depend σε όλα τα targets
21 | # run Κάνει depend σε όλα τα targets run-
22 | # valgrind Κάνει depend σε όλα τα targets valgrind-
23 | # coverage Εκτελεί το run, και παράγει ένα εννιαίο coverage report
24 | # clean Διαγράφει όλα τα αρχεία που παράγονται από το make
25 | #
26 | # Το αρχείο αυτό χρησιμοποιεί κάποια advanced features του GNU make, ΔΕΝ απαιτείται
27 | # να κατανοήσετε όλες τις εντολές για να το χρησιμοποιήσετε (όχι ότι είναι δύσκολο
28 | # να το κάνετε αν θέλετε). Επίσης μπορείτε κάλλιστα τα χρησιμοποιήσετε τα απλούστερα
29 | # Makefiles που είδαμε στο μάθημα, αν προτιμάτε.
30 |
31 |
32 | ## Μεταβλητές ###########################################################
33 |
34 | # Paths
35 | MY_PATH := $(dir $(lastword $(MAKEFILE_LIST)))
36 | MODULES := $(MY_PATH)modules
37 | INCLUDE := $(MY_PATH)include
38 | LIB := $(MY_PATH)lib
39 |
40 | # Compiler options
41 | # -g Δημιουργεί εκτελέσιμο κατάλληλο για debugging
42 | # -I Λέει στον compiler να ψάξει στο συγκεκριμένο directory για include files
43 | # -Wall Ενεργοποιεί όλα τα warnings
44 | # -Werror Αντιμετωπίζει τα warnings σαν errors, σταματώντας το compilation
45 | # -MMD Δημιουργεί ένα .d αρχείο με τα dependencies, το οποίο μπορούμε να κάνουμε include στο Makefile
46 | #
47 | # Το override επιτρέπει την προσθήκη επιπλέον παραμέτρων από τη γραμμή εντολών: make CFLAGS=...
48 | #
49 | override CFLAGS += -g -Wall -Werror -MMD -I$(INCLUDE)
50 |
51 | # Linker options
52 | # -lm Link με τη math library
53 | #
54 | LDFLAGS += -lm
55 |
56 | # Αν στα targets με τα οποία έχει κληθεί το make (μεταβλητή MAKECMDGOALS) υπάρχει κάποιο
57 | # coverage*, τότε προσθέτουμε το --coverage στα compile & link flags
58 | #
59 | ifneq (,$(findstring coverage,$(MAKECMDGOALS)))
60 | override CFLAGS += --coverage
61 | override LDFLAGS += --coverage
62 | endif
63 |
64 | # compiler
65 | ifdef WASM
66 | # compile to webassembly
67 | CC = emcc
68 | CFLAGS += -DWASM
69 | else
70 | CC = gcc
71 | endif
72 |
73 | # Λίστα με όλα τα εκτελέσιμα & βιβλιοθήκες για τα οποία υπάρχει μια μεταβλητή _OBJS
74 | WITH_OBJS := $(subst _OBJS,,$(filter %_OBJS,$(.VARIABLES)))
75 | PROGS := $(filter-out %.a,$(WITH_OBJS))
76 | LIBS := $(filter %.a,$(WITH_OBJS))
77 |
78 | # Μαζεύουμε όλα τα objects σε μία μεταβλητή
79 | OBJS := $(foreach target, $(WITH_OBJS), $($(target)_OBJS))
80 |
81 | # Για κάθε .o ο gcc παράγει ένα .d, τα αποθηκεύουμε εδώ (το filter κρατάει μόνο τα .o, όχι τα .a)
82 | DEPS := $(patsubst %.o, %.d, $(filter %.o, $(OBJS)))
83 |
84 | # Λίστα με coverage-related αρχεία που παράγονται κατά το compile & execute με --coverage (.gcda .gcno)
85 | COV_FILES := $(patsubst %.o,%.gcda,$(OBJS)) $(patsubst %.o,%.gcno,$(OBJS))
86 |
87 | # Για κάθε target φτιάχνουμε 3 targets run-, valgrind-, coverage-
88 | # Στις παρακάτω μεταβλητές φτιάχνουμε μια λίστα με όλα αυτά τα targets
89 | # Το "?=" σημαίνει "ανάθεση αν η μεταβλητή δεν έχει ήδη τιμή". Αυτό επιτρέπει
90 | # στο Makefile να ορίσει ποια targets θέλει να φτιαχτούν, πριν το include common.mk
91 | #
92 | RUN_TARGETS ?= $(addprefix run-, $(PROGS))
93 | VAL_TARGETS ?= $(addprefix valgrind-, $(PROGS))
94 | COV_TARGETS ?= $(addprefix coverage-, $(PROGS))
95 |
96 | # Για κάθε test (*_test) θέτουμε τις παρεμέτρους του (_ARGS) από default σε --time
97 | $(foreach test, $(filter %_test, $(PROGS)), \
98 | $(eval $(test)_ARGS ?= --time) \
99 | )
100 |
101 |
102 | ## Κανόνες ###########################################################
103 |
104 | # Default target, κάνει compile όλα τα εκτελέσιμα & τις βιβλιοθήκες
105 | all: $(PROGS) $(LIBS)
106 |
107 | # Αυτό χρειάζεται για να μπορούμε να χρησιμοποιήσουμε μεταβλητές στη λίστα των dependencies.
108 | # Η χρήση αυτή απαιτεί διπλό "$$" στις μεταβλητές, πχ: $$(VAR), $$@
109 | .SECONDEXPANSION:
110 |
111 | # Για κάθε εκτελέσιμο , δημιουργούμε έναν κανόνα που δηλώνει τα περιεχόμενα του
112 | # _OBJS ως depedencies του . Το $@ περιέχει το όνομα του target,
113 | # αλλά για να το χρησιμοποιήσουμε στη λίστα των dependencies χρειάζεται $$@ και .SECONDEXPANSION
114 | #
115 | $(PROGS): $$($$@_OBJS)
116 | $(CC) $^ -o $@ $(LDFLAGS)
117 |
118 | # Για κάθε βιβλιοθήκη , δημιουργούμε έναν κανόνα που δηλώνει τα περιεχόμενα του
119 | # _OBJS ως depedencies του .
120 | #
121 | $(LIBS): $$($$@_OBJS)
122 | ar -rcs $@ $^
123 |
124 | # Κάνουμε include τα .d αρχεία που παράγει ο gcc (το "-" αγνοεί την εντολή αν αποτύχει)
125 | # Ενα αρχείο foo.d περιέχει όλα τα αρχεία (.c και .h) που χρειάστηκε o gcc για να κάνει compile
126 | # το foo.o, στη μορφή του Makefile. Οπότε κάνοντας include το foo.d δηλώνουμε ότι αν οποιοδήποτε
127 | # από τα αρχεία αυτά αλλάξει, πρέπει να ξανακάνουμε compile το foo.o.
128 | #
129 | -include $(DEPS)
130 |
131 | # Το make clean καθαρίζει οτιδήποτε φτιάχνεται από αυτό το Makefile
132 | clean:
133 | @$(RM) $(PROGS) $(LIBS) $(OBJS) $(DEPS) $(COV_FILES)
134 | @$(RM) -r coverage
135 |
136 | # Για κάθε εκτελέσιμο φτιάχνουμε ένα target run- που το εκτελεί με παραμέτρους _ARGS
137 | # Το run-% είναι "pattern rule", δημιουργεί έναν κανόνα για κάθε run-, θέτωντας το $* σε "foo".
138 | run-%: %
139 | ./$* $($*_ARGS)
140 |
141 | # Το make run εκτελεί όλα τα run targets
142 | run: $(RUN_TARGETS)
143 |
144 | # Για κάθε εκτελέσιμο φτιάχνουμε ένα target valgrind- που το εκτελεί μέσω valgrind με παραμέτρους _ARGS
145 | valgrind-%: %
146 | valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./$* $($*_ARGS)
147 |
148 | valgrind: $(VAL_TARGETS)
149 |
150 | # Βοηθητικό target που εκτελεί το lcov. Χρησιμοποιείται ως dependency στα coverage-* targets
151 | lcov:
152 | @mkdir -p coverage
153 | lcov --rc lcov_branch_coverage=1 --capture --directory=$(MY_PATH) --output-file coverage/lcov.info
154 | lcov --rc lcov_branch_coverage=1 --remove coverage/lcov.info '*.h' --output-file coverage/lcov.info
155 | cd coverage && genhtml --rc lcov_branch_coverage=1 lcov.info
156 | @echo "To see the report open the file below in your brower:"
157 | @echo "$$PWD/coverage/index.html"
158 |
159 | # Για κάθε εκτελέσιμο φτιάχνουμε ένα target coverage- που το εκτελεί και μετά φτιάχνει coverage report
160 | coverage-%: clean run-% lcov
161 | @true # dummy εντολή, γιατί ένα pattern rule αγνοείται αν δεν υπάρχουν εντολές για το target)
162 | coverage: clean run lcov
163 |
164 | # Τα targets που ορίζονται από pattern rules (eg foo-%) δεν εμφανίζονται στο bash auto-complete. Τα παρακάτω κενά rules
165 | # δεν επηρεάζουν σε τίποτα τη λειτουργία του Makefile, απλά επιτρέπουν στο auto-complete να "δει" αυτά τα targets.
166 | $(RUN_TARGETS):
167 | $(VAL_TARGETS):
168 | $(COV_TARGETS):
169 |
170 | # Δηλώνουμε ότι οι παρακάτω κανόνες είναι εικονικοί, δεν παράγουν αρχεία. Θέλουμε δηλαδή
171 | # το "make clean" να εκτελεστεί ακόμα και αν συμπτωματικά υπάρχει ένα αρχείο "clean" στο
172 | # τρέχον directory.
173 | #
174 | .PHONY: clean run valgrind coverage lcov
175 |
--------------------------------------------------------------------------------
/include/ADTBinaryTree.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////
2 | //
3 | // ADT Binary Tree
4 | //
5 | // Abstract δυαδικό δέντρο.
6 | //
7 | ///////////////////////////////////////////////////////////////////
8 |
9 | #pragma once // #include το πολύ μία φορά
10 |
11 | #include "common_types.h"
12 |
13 |
14 | // Δέντρα και κόμβοι αναπαριστώνται από τους τύπους BinaryTree και BinaryTreeNode.
15 |
16 | typedef struct binary_tree* BinaryTree;
17 | typedef struct binary_tree_node* BinaryTreeNode;
18 |
19 | typedef void (*BinaryTreeVisitFunc)(BinaryTree, BinaryTreeNode);
20 |
21 |
22 | // TODO: documentation
23 |
24 | BinaryTree binary_tree_create();
25 |
26 | int binary_tree_height(BinaryTree tree);
27 | int binary_tree_size(BinaryTree tree);
28 |
29 | BinaryTreeNode binary_tree_root(BinaryTree tree);
30 | BinaryTreeNode binary_tree_parent(BinaryTree tree, BinaryTreeNode node);
31 | BinaryTreeNode binary_tree_child_left(BinaryTree tree, BinaryTreeNode node);
32 | BinaryTreeNode binary_tree_child_right(BinaryTree tree, BinaryTreeNode node);
33 |
34 | Pointer binary_tree_get(BinaryTree tree, BinaryTreeNode node);
35 | void binary_tree_set(BinaryTree tree, BinaryTreeNode node, Pointer value);
36 |
37 | void binary_tree_insert(BinaryTree tree, BinaryTreeNode node, int position, Pointer item);
38 | void binary_tree_remove(BinaryTree tree, BinaryTreeNode node);
39 |
40 | void binary_tree_pre_order(BinaryTree tree, BinaryTreeVisitFunc visit);
41 | void binary_tree_in_order(BinaryTree tree, BinaryTreeVisitFunc visit);
42 | void binary_tree_post_order(BinaryTree tree, BinaryTreeVisitFunc visit);
43 | void binary_tree_level_order(BinaryTree tree, BinaryTreeVisitFunc visit);
44 |
45 | void binary_tree_Destroy(BinaryTree tree);
46 |
47 |
--------------------------------------------------------------------------------
/include/ADTGraph.h:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // ADT Graph
4 | //
5 | // Abstract μη κατευθυνόμενος γράφος με βάρη.
6 | //
7 | ////////////////////////////////////////////////////////////////////////
8 |
9 | #pragma once // #include το πολύ μία φορά
10 |
11 | #include "common_types.h"
12 | #include "ADTList.h" // Ορισμένες συναρτήσεις επιστρέφουν λίστες
13 |
14 |
15 | // Ενα γράφος αναπαριστάται από τον τύπο Graph
16 |
17 | typedef struct graph* Graph;
18 |
19 | // Δείκτης σε συνάρτηση που επισκέπτεται τα στoιχεία του γράφου
20 | typedef void (*GraphVisitFunc)(Graph graph, Pointer value);
21 |
22 |
23 | // Δημιουργεί και επιστρέφει ένα γράφο, στο οποίο τα στοιχεία συγκρίνονται με βάση
24 | // τη συνάρτηση compare.
25 | // Αν destroy_vertex != NULL, τότε καλείται destroy_vertex(vertex) κάθε φορά που αφαιρείται μια κορυφή.
26 |
27 | Graph graph_create(CompareFunc compare, DestroyFunc destroy_vertex);
28 |
29 | // Επιστρέφει τον αριθμό στοιχείων (κορυφών) που περιέχει ο γράφος graph.
30 |
31 | int graph_size(Graph graph);
32 |
33 | // Προσθέτει μια κορυφή στο γράφο
34 |
35 | void graph_insert_vertex(Graph graph, Pointer vertex);
36 |
37 | // Επιστρέφει λίστα με όλες τις κορυφές του γράφου
38 |
39 | List graph_get_vertices(Graph graph);
40 |
41 | // Διαγράφει μια κορυφή από τον γράφο
42 |
43 | void graph_remove_vertex(Graph graph, Pointer vertex);
44 |
45 | // Προσθέτει μια ακμή με βάρος weight στο γράφο
46 |
47 | void graph_insert_edge(Graph graph, Pointer vertex1, Pointer vertex2, int weight);
48 |
49 | // Αφαιρεί μια ακμή από το γράφο
50 |
51 | void graph_remove_edge(Graph graph, Pointer vertex1, Pointer vertex2);
52 |
53 | // Επιστρέφει το βάρος της ακμής ανάμεσα στις δύο κορυφές, ή INT_MAX αν δεν είναι γειτονικές.
54 |
55 | int graph_get_weight(Graph graph, Pointer vertex1, Pointer vertex2);
56 |
57 | // Επιστρέφει λίστα με τους γείτονες μιας κορυφής
58 |
59 | List graph_get_adjacent(Graph graph, Pointer vertex);
60 |
61 | // Επιστρέφει (σε λίστα) το συντομότερο μονοπάτι ανάμεσα στις κορυφές source
62 | // και target, ή κενή λίστα αν δεν υπάρχει κανένα μονοπάτι.
63 |
64 | List graph_shortest_path(Graph graph, Pointer source, Pointer target);
65 |
66 | // Καλεί τη visit(graph, vertex) για κάθε στοιχείο του γράφου, ξεκινώντας από
67 | // την κορυφή vertex, και διασχίζοντας τον γράφο πρώτα κατά πλάτος (BFS)
68 |
69 | void graph_bfs(Graph graph, Pointer vertex, GraphVisitFunc visit);
70 |
71 | // Καλεί τη visit(graph, vertex) για κάθε στοιχείο του γράφου, ξεκινώντας από
72 | // την κορυφή vertex, και διασχίζοντας τον γράφο πρώτα κατά βάθος (DFS)
73 |
74 | void graph_dfs(Graph graph, Pointer vertex, GraphVisitFunc visit);
75 |
76 | // Ελευθερώνει όλη τη μνήμη που δεσμεύει το γράφος.
77 | // Οποιαδήποτε λειτουργία πάνω στο γράφο μετά το destroy είναι μη ορισμένη.
78 |
79 | void graph_destroy(Graph graph);
80 |
--------------------------------------------------------------------------------
/include/ADTList.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////
2 | //
3 | // ADT List
4 | //
5 | // Abstract λίστα. Παρέχει σειριακή πρόσβαση στα στοιχεία, και
6 | // προσθήκη/αφαίρεση σε οποιοδήποτε σημείο της λίστας.
7 | //
8 | ///////////////////////////////////////////////////////////////////
9 |
10 | #pragma once // #include το πολύ μία φορά
11 |
12 | #include "common_types.h"
13 |
14 | // Οι σταθερές αυτές συμβολίζουν εικονικούς κόμβους _πριν_ τον πρώτο και _μετά_ τον τελευταίο
15 | #define LIST_BOF (ListNode)0
16 | #define LIST_EOF (ListNode)0
17 |
18 |
19 | // Λίστες και κόμβοι αναπαριστώνται από τους τύπους List και ListNode. Ο χρήστης δε χρειάζεται να γνωρίζει το περιεχόμενο
20 | // των τύπων αυτών, απλά χρησιμοποιεί τις συναρτήσεις list_ που δέχονται και επιστρέφουν List / ListNode.
21 | //
22 | // Οι τύποι αυτοί ορίζινται ως pointers στα "struct list" και "struct list_node" των οποίων το
23 | // περιεχόμενο είναι άγνωστο (incomplete structs), και εξαρτάται από την υλοποίηση του ADT List.
24 | //
25 | typedef struct list* List;
26 | typedef struct list_node* ListNode;
27 |
28 |
29 |
30 | // Δημιουργεί και επιστρέφει μια νέα λίστα.
31 | // Αν destroy_value != NULL, τότε καλείται destroy_value(value) κάθε φορά που αφαιρείται ένα στοιχείο.
32 |
33 | List list_create(DestroyFunc destroy_value);
34 |
35 | // Επιστρέφει τον αριθμό στοιχείων που περιέχει η λίστα.
36 |
37 | int list_size(List list);
38 |
39 | // Προσθέτει έναν νέο κόμβο __μετά__ τον node, ή στην αρχή αν node == LIST_BOF, με περιεχόμενο value.
40 |
41 | void list_insert_next(List list, ListNode node, Pointer value);
42 |
43 | // Αφαιρεί τον __επόμενο__ κόμβο από τον node, ή τον πρώτο κόμβο αν node == LIST_BOF.
44 | // Αν ο node δεν έχει επόμενο η συμπεριφορά είναι μη ορισμένη.
45 |
46 | void list_remove_next(List list, ListNode node);
47 |
48 | // Επιστρέφει την πρώτη τιμή που είναι ισοδύναμη με value
49 | // (με βάση τη συνάρτηση compare), ή NULL αν δεν υπάρχει
50 |
51 | Pointer list_find(List list, Pointer value, CompareFunc compare);
52 |
53 | // Αλλάζει τη συνάρτηση που καλείται σε κάθε αφαίρεση/αντικατάσταση στοιχείου σε
54 | // destroy_value. Επιστρέφει την προηγούμενη τιμή της συνάρτησης.
55 |
56 | DestroyFunc list_set_destroy_value(List list, DestroyFunc destroy_value);
57 |
58 | // Ελευθερώνει όλη τη μνήμη που δεσμεύει η λίστα list.
59 | // Οποιαδήποτε λειτουργία πάνω στη λίστα μετά το destroy είναι μη ορισμένη.
60 |
61 | void list_destroy(List list);
62 |
63 |
64 | // Διάσχιση της λίστας /////////////////////////////////////////////
65 | //
66 | // Επιστρέφουν τον πρώτο και τον τελευταίο κομβο της λίστας, ή LIST_BOF / LIST_EOF αντίστοιχα αν η λίστα είναι κενή
67 |
68 | ListNode list_first(List list);
69 | ListNode list_last(List list);
70 |
71 | // Επιστρέφει τον κόμβο μετά από τον node, ή LIST_EOF αν ο node είναι ο τελευταίος
72 |
73 | ListNode list_next(List list, ListNode node);
74 |
75 | // Επιστρέφει το περιεχόμενο του κόμβου node
76 |
77 | Pointer list_node_value(List list, ListNode node);
78 |
79 | // Βρίσκει τo πρώτo στοιχείο που είναι ισοδύναμο με value (με βάση τη συνάρτηση compare).
80 | // Επιστρέφει τον κόμβο του στοιχείου, ή LIST_EOF αν δεν βρεθεί.
81 |
82 | ListNode list_find_node(List list, Pointer value, CompareFunc compare);
83 |
--------------------------------------------------------------------------------
/include/ADTMap.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////
2 | //
3 | // ADT Map
4 | //
5 | // Abstract map. Παρέχει γρήγορη αντιστοίχιση key => value.
6 | //
7 | ///////////////////////////////////////////////////////////
8 |
9 | #pragma once // #include το πολύ μία φορά
10 |
11 | #include "common_types.h"
12 |
13 |
14 | // Ενα map αναπαριστάται από τον τύπο Map
15 |
16 | typedef struct map* Map;
17 |
18 |
19 | // Δημιουργεί και επιστρέφει ένα map, στο οποίο τα στοιχεία συγκρίνονται με βάση
20 | // τη συνάρτηση compare.
21 | // Αν destroy_key ή/και destroy_value != NULL, τότε καλείται destroy_key(key)
22 | // ή/και destroy_value(value) κάθε φορά που αφαιρείται ένα στοιχείο.
23 |
24 | Map map_create(CompareFunc compare, DestroyFunc destroy_key, DestroyFunc destroy_value);
25 |
26 | // Επιστρέφει τον αριθμό στοιχείων που περιέχει το map.
27 |
28 | int map_size(Map map);
29 |
30 | // Προσθέτει το κλειδί key με τιμή value. Αν υπάρχει κλειδί ισοδύναμο με key, τα παλιά key & value αντικαθίσταται από τα νέα.
31 | //
32 | // ΠΡΟΣΟΧΗ:
33 | // Όσο το key είναι μέλος του map, οποιαδήποτε μεταβολή στο περιεχόμενό του (στη μνήμη που δείχνει) δεν πρέπει
34 | // να αλλάζει τη σχέση διάταξης (compare) με οποιοδήποτε άλλο key, διαφορετικά έχει μη ορισμένη συμπεριφορά.
35 |
36 | void map_insert(Map map, Pointer key, Pointer value);
37 |
38 | // Αφαιρεί το κλειδί που είναι ισοδύναμο με key από το map, αν υπάρχει.
39 | // Επιστρέφει true αν βρέθηκε τέτοιο κλειδί, διαφορετικά false.
40 |
41 | bool map_remove(Map map, Pointer key);
42 |
43 | // Επιστρέφει την τιμή που έχει αντιστοιχιστεί στο συγκεκριμένο key, ή NULL αν το key δεν υπάρχει στο map.
44 | //
45 | // Προσοχή: η συνάρτηση επιστρέφει NULL είτε όταν το key δεν υπάρχει, είτε όταν υπάρχει και έχει τιμή NULL.
46 | // Αν χρειάζεται να διαχωρίσουμε τις δύο περιπτώσεις μπορούμε να χρησιμοποιήσουμε την map_find_node.
47 |
48 | Pointer map_find(Map map, Pointer key);
49 |
50 | // Αλλάζει τη συνάρτηση που καλείται σε κάθε αφαίρεση/αντικατάσταση key/value.
51 | // Επιστρέφει την προηγούμενη τιμή της συνάρτησης.
52 |
53 | DestroyFunc map_set_destroy_key (Map map, DestroyFunc destroy_key );
54 | DestroyFunc map_set_destroy_value(Map map, DestroyFunc destroy_value);
55 |
56 | // Ελευθερώνει όλη τη μνήμη που δεσμεύει το map.
57 | // Οποιαδήποτε λειτουργία πάνω στο map μετά το destroy είναι μη ορισμένη.
58 |
59 | void map_destroy(Map map);
60 |
61 |
62 |
63 | // Διάσχιση του map μέσω κόμβων ////////////////////////////////////////////////////////////
64 | //
65 | // Η σειρά διάσχισης είναι αυθαίρετη.
66 |
67 | // Η σταθερά αυτή συμβολίζει έναν εικονικό κόμβου _μετά_ τον τελευταίο κόμβο του map
68 | #define MAP_EOF (MapNode)0
69 |
70 | typedef struct map_node* MapNode;
71 |
72 | // Επιστρέφει τον πρώτο κομβο του map, ή MAP_EOF αν το map είναι κενό
73 |
74 | MapNode map_first(Map map);
75 |
76 | // Επιστρέφει τον επόμενο κόμβο του node, ή MAP_EOF αν ο node δεν έχει επόμενο
77 |
78 | MapNode map_next(Map map, MapNode node);
79 |
80 | // Επιστρέφει το κλειδί του κόμβου node
81 |
82 | Pointer map_node_key(Map map, MapNode node);
83 |
84 | // Επιστρέφει το περιεχόμενο του κόμβου node
85 |
86 | Pointer map_node_value(Map map, MapNode node);
87 |
88 | // Βρίσκει και επιστρέφεο τον κόμβο που έχει αντιστοιχιστεί στο κλειδί key,
89 | // ή MAP_EOF αν το κλειδί δεν υπάρχει στο map.
90 |
91 | MapNode map_find_node(Map map, Pointer key);
92 |
93 |
94 | //// Επιπλέον συναρτήσεις για υλοποιήσεις βασισμένες σε hashing ////////////////////////////
95 |
96 | // Τύπος συνάρτησης κατακερματισμού
97 |
98 | typedef uint (*HashFunc)(Pointer);
99 |
100 | // Υλοποιημένες συναρτήσεις κατακερματισμού για συχνούς τύπους δεδομένων
101 |
102 | uint hash_string(Pointer value); // Χρήση όταν το key είναι char*
103 | uint hash_int(Pointer value); // Χρήση όταν το key είναι int*
104 | uint hash_pointer(Pointer value); // Χρήση όταν το key είναι pointer που θεωρείται διαφορετικός από οποιονδήποτε άλλο pointer
105 |
106 | // Ορίζει τη συνάρτηση κατακερματισμού hash για το συγκεκριμένο map
107 | // Πρέπει να κληθεί μετά την map_create και πριν από οποιαδήποτε άλλη συνάρτηση.
108 | // Όπως και με την συνάρτηση compare, αλλαγές στο περιεχόμενο των keys δεν θα πρέπει να αλλάζουν την
109 | // τιμή που επιστρέφει η συνάρτηση κατακερματισμού, διαφορετικά η συμπεριφορά είναι μη ορισμένη.
110 |
111 | void map_set_hash_function(Map map, HashFunc hash_func);
112 |
--------------------------------------------------------------------------------
/include/ADTPriorityQueue.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////
2 | //
3 | // ADT Priority Queue
4 | //
5 | // Abstract ουρά προτεραιότητας. Σε κάθε remove αφαιρείται το
6 | // μεγαλύτερο στοιχείο (με βάση τη συνάρτηση compare).
7 | //
8 | ///////////////////////////////////////////////////////////////////
9 |
10 | #pragma once // #include το πολύ μία φορά
11 |
12 | #include "common_types.h"
13 | #include "ADTVector.h"
14 |
15 |
16 | // Μία ουρά προτεραιότητας αναπαριστάται από τον τύπο PriorityQueue
17 |
18 | typedef struct priority_queue* PriorityQueue;
19 |
20 |
21 | // Δημιουργεί και επιστρέφει μια νέα ουρά προτεραιότητας, της οποίας τα στοιχεία συγκρίνονται με βάση τη συνάρτηση compare.
22 | // Αν destroy_value != NULL, τότε καλείται destroy_value(value) κάθε φορά που αφαιρείται ένα στοιχείο.
23 | // Αν values != NULL, τότε η ουρά αρχικοποιείται με τα στοιχεία του Vector values.
24 |
25 | PriorityQueue pqueue_create(CompareFunc compare, DestroyFunc destroy_value, Vector values);
26 |
27 | // Επιστρέφει τον αριθμό στοιχείων που περιέχει η ουρά pqueue
28 |
29 | int pqueue_size(PriorityQueue pqueue);
30 |
31 | // Επιστρέφει το μεγαλύτερο στοιχείο της ουράς (μη ορισμένο αποτέλεσμα αν η ουρά είναι κενή)
32 |
33 | Pointer pqueue_max(PriorityQueue pqueue);
34 |
35 | // Προσθέτει την τιμή value στην ουρά pqueue.
36 |
37 | void pqueue_insert(PriorityQueue pqueue, Pointer value);
38 |
39 | // Αφαιρεί την μεγαλύτερη τιμή της ουράς (μη ορισμένο αποτέλεσμα αν η ουρά είναι κενή)
40 |
41 | void pqueue_remove_max(PriorityQueue pqueue);
42 |
43 | // Αλλάζει τη συνάρτηση που καλείται σε κάθε αφαίρεση/αντικατάσταση στοιχείου σε
44 | // destroy_value. Επιστρέφει την προηγούμενη τιμή της συνάρτησης.
45 |
46 | DestroyFunc pqueue_set_destroy_value(PriorityQueue pqueue, DestroyFunc destroy_value);
47 |
48 | // Ελευθερώνει όλη τη μνήμη που δεσμεύει η ουρά pqueue.
49 | // Οποιαδήποτε λειτουργία πάνω στη ουρά μετά το destroy είναι μη ορισμένη.
50 |
51 | void pqueue_destroy(PriorityQueue pqueue);
52 |
--------------------------------------------------------------------------------
/include/ADTQueue.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////
2 | //
3 | // ADT Queue
4 | //
5 | // Abstract ουρά. Λειτουργία "First In First Out", δηλαδή σε κάθε
6 | // remove αφαιρείται το παλιότερο στοιχείο που έγινε insert.
7 | //
8 | ///////////////////////////////////////////////////////////////////
9 |
10 | #pragma once // #include το πολύ μία φορά
11 |
12 | #include "common_types.h"
13 |
14 |
15 | // Μία ουρά αναπαριστάται από τον τύπο Queue. Ο χρήστης δε χρειάζεται να γνωρίζει το περιεχόμενο
16 | // του τύπου αυτού, απλά χρησιμοποιεί τις συναρτήσεις queue_ που δέχονται και επιστρέφουν Queue.
17 | //
18 | // Ο τύπος Queue ορίζεται ως pointer στο "struct queue" του οποίου το περιεχόμενο είναι άγνωστο
19 | // (incomplete struct), και εξαρτάται από την υλοποίηση του ADT Queue.
20 | //
21 | typedef struct queue* Queue;
22 |
23 |
24 | // Δημιουργεί και επιστρέφει μια νέα ουρά.
25 | // Αν destroy_value != NULL, τότε καλείται destroy_value(value) κάθε φορά που αφαιρείται ένα στοιχείο.
26 |
27 | Queue queue_create(DestroyFunc destroy_value);
28 |
29 | // Επιστρέφει τον αριθμό στοιχείων που περιέχει η ουρά queue
30 |
31 | int queue_size(Queue queue);
32 |
33 | // Επιστρέφει το στοιχείο στο μπροστινό μέρος της ουράς (μη ορισμένο αποτέλεσμα αν η ουρά είναι κενή)
34 |
35 | Pointer queue_front(Queue queue);
36 |
37 | // Επιστρέφει το στοιχείο στο πίσω μέρος της ουράς (μη ορισμένο αποτέλεσμα αν η ουρά είναι κενή)
38 |
39 | Pointer queue_back(Queue queue);
40 |
41 | // Προσθέτει την τιμή value στo πίσω μέρος της ουράς queue.
42 |
43 | void queue_insert_back(Queue queue, Pointer value);
44 |
45 | // Αφαιρεί την τιμή στο μπροστά μέρος της ουράς (μη ορισμένο αποτέλεσμα αν η ουρά είναι κενή)
46 |
47 | void queue_remove_front(Queue queue);
48 |
49 | // Αλλάζει τη συνάρτηση που καλείται σε κάθε αφαίρεση/αντικατάσταση στοιχείου σε
50 | // destroy_value. Επιστρέφει την προηγούμενη τιμή της συνάρτησης.
51 |
52 | DestroyFunc queue_set_destroy_value(Queue queue, DestroyFunc destroy_value);
53 |
54 | // Ελευθερώνει όλη τη μνήμη που δεσμεύει η ουρά queue.
55 | // Οποιαδήποτε λειτουργία πάνω στη ουρά μετά το destroy είναι μη ορισμένη.
56 |
57 | void queue_destroy(Queue queue);
58 |
--------------------------------------------------------------------------------
/include/ADTSet.h:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // ADT Set
4 | //
5 | // Abstract διατεταγμένο σύνολο. Τα στοιχεία είναι διατεταγμένα με βάση
6 | // τη συνάρτηση compare, και καθένα εμφανίζεται το πολύ μία φορά.
7 | // Παρέχεται γρήγορη αναζήτηση με ισότητα αλλά και με ανισότητα.
8 | //
9 | ////////////////////////////////////////////////////////////////////////
10 |
11 | #pragma once // #include το πολύ μία φορά
12 |
13 | #include "common_types.h"
14 |
15 |
16 | // Ενα σύνολο αναπαριστάται από τον τύπο Set
17 |
18 | typedef struct set* Set;
19 |
20 |
21 | // Δημιουργεί και επιστρέφει ένα σύνολο, στο οποίο τα στοιχεία συγκρίνονται με βάση
22 | // τη συνάρτηση compare.
23 | // Αν destroy_value != NULL, τότε καλείται destroy_value(value) κάθε φορά που αφαιρείται ένα στοιχείο.
24 |
25 | Set set_create(CompareFunc compare, DestroyFunc destroy_value);
26 |
27 | // Επιστρέφει τον αριθμό στοιχείων που περιέχει το σύνολο set.
28 |
29 | int set_size(Set set);
30 |
31 | // Προσθέτει την τιμή value στο σύνολο, αντικαθιστώντας τυχόν προηγούμενη τιμή ισοδύναμη της value.
32 | //
33 | // ΠΡΟΣΟΧΗ:
34 | // Όσο το value είναι μέλος του set, οποιαδήποτε μεταβολή στο περιεχόμενό του (στη μνήμη που δείχνει) δεν πρέπει
35 | // να αλλάζει τη σχέση διάταξης (compare) με οποιοδήποτε άλλο στοιχείο, διαφορετικά έχει μη ορισμένη συμπεριφορά.
36 |
37 | void set_insert(Set set, Pointer value);
38 |
39 | // Αφαιρεί τη μοναδική τιμή ισοδύναμη της value από το σύνολο, αν υπάρχει.
40 | // Επιστρέφει true αν βρέθηκε η τιμή αυτή, false διαφορετικά.
41 |
42 | bool set_remove(Set set, Pointer value);
43 |
44 | // Επιστρέφει την μοναδική τιμή του set που είναι ισοδύναμη με value, ή NULL αν δεν υπάρχει
45 |
46 | Pointer set_find(Set set, Pointer value);
47 |
48 | // Αλλάζει τη συνάρτηση που καλείται σε κάθε αφαίρεση/αντικατάσταση στοιχείου σε
49 | // destroy_value. Επιστρέφει την προηγούμενη τιμή της συνάρτησης.
50 |
51 | DestroyFunc set_set_destroy_value(Set set, DestroyFunc destroy_value);
52 |
53 | // Ελευθερώνει όλη τη μνήμη που δεσμεύει το σύνολο.
54 | // Οποιαδήποτε λειτουργία πάνω στο set μετά το destroy είναι μη ορισμένη.
55 |
56 | void set_destroy(Set set);
57 |
58 |
59 | // Διάσχιση του set ////////////////////////////////////////////////////////////
60 | //
61 | // Η διάσχιση γίνεται με τη σειρά διάταξης.
62 |
63 | // Οι σταθερές αυτές συμβολίζουν εικονικούς κόμβους _πριν_ τον πρώτο και _μετά_ τον τελευταίο κόμβο του set
64 | #define SET_BOF (SetNode)0
65 | #define SET_EOF (SetNode)0
66 |
67 | typedef struct set_node* SetNode;
68 |
69 | // Επιστρέφουν τον πρώτο και τον τελευταίο κομβο του set, ή SET_BOF / SET_EOF αντίστοιχα αν το set είναι κενό
70 |
71 | SetNode set_first(Set set);
72 | SetNode set_last(Set set);
73 |
74 | // Επιστρέφουν τον επόμενο και τον προηγούμενο κομβο του node, ή SET_EOF / SET_BOF
75 | // αντίστοιχα αν ο node δεν έχει επόμενο / προηγούμενο.
76 |
77 | SetNode set_next(Set set, SetNode node);
78 | SetNode set_previous(Set set, SetNode node);
79 |
80 | // Επιστρέφει το περιεχόμενο του κόμβου node
81 |
82 | Pointer set_node_value(Set set, SetNode node);
83 |
84 | // Βρίσκει το μοναδικό στοιχείο στο set που να είναι ίσο με value.
85 | // Επιστρέφει τον κόμβο του στοιχείου, ή SET_EOF αν δεν βρεθεί.
86 |
87 | SetNode set_find_node(Set set, Pointer value);
88 |
--------------------------------------------------------------------------------
/include/ADTStack.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////
2 | //
3 | // ADT Stack
4 | //
5 | // Abstract στοίβα. Λειτουργία "First In Last Out", δηλαδή σε κάθε
6 | // remove αφαιρείται το τελευταίο στοιχείο που έγινε insert.
7 | //
8 | ///////////////////////////////////////////////////////////////////
9 |
10 | #pragma once // #include το πολύ μία φορά
11 |
12 | #include "common_types.h"
13 |
14 |
15 | // Μία στοίβα αναπαριστάται από τον τύπο Stack. Ο χρήστης δε χρειάζεται να γνωρίζει το περιεχόμενο
16 | // του τύπου αυτού, απλά χρησιμοποιεί τις συναρτήσεις stack_ που δέχονται και επιστρέφουν Stack.
17 | //
18 | // Ο τύπος Stack ορίζεται ως pointer στο "struct stack" του οποίου το περιεχόμενο είναι άγνωστο
19 | // (incomplete struct), και εξαρτάται από την υλοποίηση του ADT Stack.
20 | //
21 | typedef struct stack* Stack;
22 |
23 |
24 | // Δημιουργεί και επιστρέφει μια νέα στοίβα.
25 | // Αν destroy_value != NULL, τότε καλείται destroy_value(value) κάθε φορά που αφαιρείται ένα στοιχείο.
26 |
27 | Stack stack_create(DestroyFunc destroy_value);
28 |
29 | // Επιστρέφει τον αριθμό στοιχείων που περιέχει η στοίβα stack
30 |
31 | int stack_size(Stack stack);
32 |
33 | // Επιστρέφει το στοιχείο στην κορυφή της στοίβας (μη ορισμένο αποτέλεσμα αν η στοίβα είναι κενή)
34 |
35 | Pointer stack_top(Stack stack);
36 |
37 | // Προσθέτει την τιμή value στην κορυφή της στοίβας stack.
38 |
39 | void stack_insert_top(Stack stack, Pointer value);
40 |
41 | // Αφαιρεί την τιμή στην κορυφή της στοίβας (μη ορισμένο αποτέλεσμα αν η στοίβα είναι κενή)
42 |
43 | void stack_remove_top(Stack stack);
44 |
45 | // Αλλάζει τη συνάρτηση που καλείται σε κάθε αφαίρεση/αντικατάσταση στοιχείου σε
46 | // destroy_value. Επιστρέφει την προηγούμενη τιμή της συνάρτησης.
47 |
48 | DestroyFunc stack_set_destroy_value(Stack stack, DestroyFunc destroy_value);
49 |
50 | // Ελευθερώνει όλη τη μνήμη που δεσμεύει η στοιβα stack.
51 | // Οποιαδήποτε λειτουργία πάνω στη στοίβα μετά το destroy είναι μη ορισμένη.
52 |
53 | void stack_destroy(Stack stack);
54 |
--------------------------------------------------------------------------------
/include/ADTVector.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////
2 | //
3 | // ADT Vector
4 | //
5 | // Abstract "array" μεταβλητού μεγέθους. Παρέχει πρόσβαση σε
6 | // οποιοδήποτε στοιχείο (μέσω της θέσης του), και προσθήκη/αφαίρεση
7 | // στοιχείων στο τέλος του vector.
8 | //
9 | ///////////////////////////////////////////////////////////////////
10 |
11 | #pragma once // #include το πολύ μία φορά
12 |
13 | #include "common_types.h"
14 |
15 |
16 | // Ένα vector αναπαριστάται από τον τύπο Vector. Ο χρήστης δε χρειάζεται να γνωρίζει το περιεχόμενο
17 | // του τύπου αυτού, απλά χρησιμοποιεί τις συναρτήσεις vector_ που δέχονται και επιστρέφουν Vector.
18 | //
19 | // Ο τύπος Vector ορίζεται ως pointer στο "struct vector" του οποίου το περιεχόμενο είναι άγνωστο
20 | // (incomplete struct), και εξαρτάται από την υλοποίηση του ADT Vector.
21 | //
22 | typedef struct vector* Vector;
23 |
24 |
25 | // Δημιουργεί και επιστρέφει ένα νεό vector μεγέθους size, με στοιχεία αρχικοποιημένα σε NULL.
26 | // Αν destroy_value != NULL, τότε καλείται destroy_value(value) κάθε φορά που αφαιρείται (ή αντικαθίσταται) ένα στοιχείο.
27 |
28 | Vector vector_create(int size, DestroyFunc destroy_value);
29 |
30 | // Επιστρέφει τον αριθμό στοιχείων που περιέχει το vector vec.
31 |
32 | int vector_size(Vector vec);
33 |
34 | // Προσθέτει την τιμή value στο _τέλος_ του vector vec. Το μέγεθος του vector μεγαλώνει κατά 1.
35 |
36 | void vector_insert_last(Vector vec, Pointer value);
37 |
38 | // Αφαιρεί το τελευταίο στοιχείο του vector. Το μέγεθος του vector μικραίνει κατά 1.
39 | // Αν το vector είναι κενό η συμπεριφορά είναι μη ορισμένη.
40 |
41 | void vector_remove_last(Vector vec);
42 |
43 | // Επιστρέφει την τιμή στη θέση pos του vector vec (μη ορισμένο αποτέλεσμα αν pos < 0 ή pos >= size)
44 |
45 | Pointer vector_get_at(Vector vec, int pos);
46 |
47 | // Αλλάζει την τιμή στη θέση pos του Vector vec σε value.
48 | // ΔΕΝ μεταβάλλει το μέγεθος του vector, αν pos >= size το αποτέλεσμα δεν είναι ορισμένο.
49 |
50 | void vector_set_at(Vector vec, int pos, Pointer value);
51 |
52 | // Βρίσκει και επιστρέφει το πρώτο στοιχείο στο vector που να είναι ίσο με value
53 | // (με βάση τη συνάρτηση compare), ή NULL αν δεν βρεθεί κανένα στοιχείο.
54 |
55 | Pointer vector_find(Vector vec, Pointer value, CompareFunc compare);
56 |
57 | // Αλλάζει τη συνάρτηση που καλείται σε κάθε αφαίρεση/αντικατάσταση στοιχείου σε
58 | // destroy_value. Επιστρέφει την προηγούμενη τιμή της συνάρτησης.
59 |
60 | DestroyFunc vector_set_destroy_value(Vector vec, DestroyFunc destroy_value);
61 |
62 | // Ελευθερώνει όλη τη μνήμη που δεσμεύει το vector vec.
63 | // Οποιαδήποτε λειτουργία πάνω στο vector μετά το destroy είναι μη ορισμένη.
64 |
65 | void vector_destroy(Vector vec);
66 |
67 |
68 | // Διάσχιση του vector ////////////////////////////////////////////////////////////
69 | //
70 | // Οι παρακάτω συναρτήσεις επιτρέπουν τη διάσχιση του vector μέσω κόμβων.
71 | // Δεν είναι τόσο συχνά χρησιμοποιούμενες όσο για άλλα ADTs, γιατί μπορούμε
72 | // εύκολα να διασχίσουμε το vector και μέσω indexes. Παραμένουν πάντως χρήσιμες,
73 | // τόσο για ομοιομορφία με τους άλλους ADTs, αλλά και γιατί για κάποιες υλοποιήσεις
74 | // η διάσχιση μέσω κόμβων μπορεί να είναι πιο γρήγορη.
75 |
76 | // Οι σταθερές αυτές συμβολίζουν εικονικούς κόμβους _πριν_ τον πρώτο και _μετά_ τον τελευταίο
77 | #define VECTOR_BOF (VectorNode)0
78 | #define VECTOR_EOF (VectorNode)0
79 |
80 | typedef struct vector_node* VectorNode;
81 |
82 | // Επιστρέφουν τον πρώτο και τον τελευταίο κομβο του vector, ή VECTOR_BOF / VECTOR_EOF αντίστοιχα αν το vector είναι κενό
83 |
84 | VectorNode vector_first(Vector vec);
85 | VectorNode vector_last(Vector vec);
86 |
87 | // Επιστρέφουν τον επόμενο και τον προηγούμενο κομβο του node, ή VECTOR_EOF / VECTOR_BOF
88 | // αντίστοιχα αν ο node δεν έχει επόμενο / προηγούμενο.
89 |
90 | VectorNode vector_next(Vector vec, VectorNode node);
91 | VectorNode vector_previous(Vector vec, VectorNode node);
92 |
93 | // Επιστρέφει το περιεχόμενο του κόμβου node
94 |
95 | Pointer vector_node_value(Vector vec, VectorNode node);
96 |
97 | // Βρίσκει το πρώτο στοιχείο στο vector που να είναι ίσο με value (με βάση τη συνάρτηση compare).
98 | // Επιστρέφει τον κόμβο του στοιχείου, ή VECTOR_EOF αν δεν βρεθεί.
99 |
100 | VectorNode vector_find_node(Vector vec, Pointer value, CompareFunc compare);
101 |
--------------------------------------------------------------------------------
/include/common_types.h:
--------------------------------------------------------------------------------
1 | #pragma once // #include το πολύ μία φορά
2 |
3 | // Τύποι που χρησιμοποιούνται σε πολλά modules
4 |
5 | // Χρήση του τύπου "bool" για μεταβλητές που παίρνουν μόνο τιμές true / false
6 | #include
7 |
8 | // Pointer προς ένα αντικείμενο οποιουδήποτε τύπου. Απλά είναι πιο ευανάγνωστο από το "void*" που μοιάζει με το "void"
9 | typedef void* Pointer;
10 |
11 | // unsigned int, για συντομία
12 | typedef unsigned int uint;
13 |
14 | // Δείκτης σε συνάρτηση που συγκρίνει 2 στοιχεία a και b και επιστρέφει:
15 | // < 0 αν a < b
16 | // 0 αν a και b είναι ισοδύναμα (_όχι_ αναγναστικά ίσα)
17 | // > 0 αν a > b
18 | typedef int (*CompareFunc)(Pointer a, Pointer b);
19 |
20 | // Δείκτης σε συνάρτηση που καταστρέφει ένα στοιχείο value
21 | typedef void (*DestroyFunc)(Pointer value);
--------------------------------------------------------------------------------
/lib/Makefile:
--------------------------------------------------------------------------------
1 | # Λίστα με objects που περιέχει το k08.a library
2 | k08.a_OBJS = \
3 | $(MODULES)/UsingDynamicArray/ADTVector.o \
4 | $(MODULES)/UsingLinkedList/ADTList.o \
5 | $(MODULES)/UsingAVL/ADTSet.o \
6 | $(MODULES)/UsingADTList/ADTStack.o \
7 | $(MODULES)/UsingADTList/ADTQueue.o \
8 | $(MODULES)/UsingHeap/ADTPriorityQueue.o \
9 | $(MODULES)/UsingADTSet/ADTMap.o
10 |
11 | # Ο βασικός κορμός του Makefile
12 | include ../common.mk
13 |
--------------------------------------------------------------------------------
/modules/UsingADTList/ADTQueue.c:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////
2 | //
3 | // Υλοποίηση του ADT Queue μέσω του ADT List (dependent data structure)
4 | //
5 | ///////////////////////////////////////////////////////////////////////
6 |
7 | #include
8 | #include
9 |
10 | #include "ADTList.h"
11 | #include "ADTQueue.h"
12 |
13 |
14 | // Μια ουρά είναι απλά μια λίστα, αλλά το Queue (pointer σε struct queue) είναι διαφορετικός pointer
15 | // από το List (pointer σε struct list). Εχουμε 2 επιλογές:
16 | //
17 | // 1. (Η πιο "καθαρή") Να φτιάξουμε το struct queue ως εξής
18 | // struct queue {
19 | // List list;
20 | // };
21 | // και στο πεδίο "list" να αποθηκεύσουμε τη λίστα.
22 | //
23 | // 2. (Η πιο "άμεση"). Να χρησιμοποιούμε απευθείας ένα List ως Queue, κάνοντας cast τον pointer.
24 | // Το cast δεν αλλάζει την τιμή του pointer, απλά επιτρέχει την ανάθεσή του σε μεταβλητή διαφορετικού τύπου.
25 | //
26 | // Στον παρακάτω κώδικα επιλέγουμε την πρώτη λύση (δες το ADTStack.c για τη δεύτερη).
27 |
28 | struct queue {
29 | List list;
30 | };
31 |
32 |
33 | Queue queue_create(DestroyFunc destroy_value) {
34 | // Φτιάχνουμε ένα struct και αποθηκεύουμε μέσα μια νέα λίστα
35 | //
36 | Queue queue = malloc(sizeof(*queue));
37 | queue->list = list_create(destroy_value);
38 | return queue;
39 | }
40 |
41 | int queue_size(Queue queue) {
42 | return list_size(queue->list);
43 | }
44 |
45 | Pointer queue_front(Queue queue) {
46 | return list_node_value(queue->list, list_first(queue->list));
47 | }
48 |
49 | Pointer queue_back(Queue queue) {
50 | return list_node_value(queue->list, list_last(queue->list));
51 | }
52 |
53 | void queue_insert_back(Queue queue, Pointer value) {
54 | list_insert_next(queue->list, list_last(queue->list), value); // Προσθήκη στο _τέλος_
55 | }
56 |
57 | void queue_remove_front(Queue queue) {
58 | list_remove_next(queue->list, NULL);
59 | }
60 |
61 | DestroyFunc queue_set_destroy_value(Queue queue, DestroyFunc destroy_value) {
62 | return list_set_destroy_value(queue->list, destroy_value);
63 | }
64 |
65 | void queue_destroy(Queue queue) {
66 | // Τη μνήμη της λίστας την κάνει free η list_destroy, αλλά το struct που περιέχει
67 | // τη λίστα το δημιουργήσαμε εμείς, οπότε πρέπει εμείς να το κάνουμε free.
68 | //
69 | list_destroy(queue->list);
70 | free(queue);
71 | }
72 |
--------------------------------------------------------------------------------
/modules/UsingADTList/ADTStack.c:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////
2 | //
3 | // Υλοποίηση του ADT Stack μέσω του ADT List (dependent data structure)
4 | //
5 | ///////////////////////////////////////////////////////////////////////
6 |
7 | #include
8 | #include
9 |
10 | #include "ADTList.h"
11 | #include "ADTStack.h"
12 |
13 |
14 | // Μια στοίβα είναι απλά μια λίστα, αλλά το Stack (pointer σε struct stack) είναι διαφορετικός pointer
15 | // από το List (pointer σε struct list). Εχουμε 2 επιλογές:
16 | //
17 | // 1. (Η πιο "καθαρή") Να φτιάξουμε το struct stack ως εξής
18 | // struct stack {
19 | // List list;
20 | // };
21 | // και στο πεδίο "list" να αποθηκεύσουμε τη λίστα.
22 | //
23 | // 2. (Η πιο "άμεση"). Να χρησιμοποιούμε απευθείας ένα List ως Stack, κάνοντας cast τον pointer.
24 | // Το cast δεν αλλάζει την τιμή του pointer, απλά επιτρέχει την ανάθεσή του σε μεταβλητή διαφορετικού τύπου.
25 | //
26 | // Στον παρακάτω κώδικα επιλέγουμε τη δεύτερη λύση (δες το ADTQueue.c για την πρώτη).
27 |
28 |
29 |
30 | Stack stack_create(DestroyFunc destroy_value) {
31 | // Η list_create επιστρέφει ένα List. Εμείς λέμε στον compiler να θεωρήσει τον pointer αυτόν ως Stack
32 | List list = list_create(destroy_value);
33 | return (Stack)list;
34 | }
35 |
36 | int stack_size(Stack stack) {
37 | List list = (List)stack; // το stack είναι στην πραγματικότητα ένα List, αφού ο μόνος τρόπος δημιουργίας είναι με την start_create()
38 | return list_size(list);
39 | }
40 |
41 | Pointer stack_top(Stack stack) {
42 | List list = (List)stack;
43 | return list_node_value(list, list_first(list));
44 | }
45 |
46 | void stack_insert_top(Stack stack, Pointer value) {
47 | List list = (List)stack;
48 | list_insert_next(list, NULL, value);
49 | }
50 |
51 | void stack_remove_top(Stack stack) {
52 | List list = (List)stack;
53 | list_remove_next(list, NULL);
54 | }
55 |
56 | DestroyFunc stack_set_destroy_value(Stack stack, DestroyFunc destroy_value) {
57 | List list = (List)stack;
58 | return list_set_destroy_value(list, destroy_value);
59 | }
60 |
61 | void stack_destroy(Stack stack) {
62 | List list = (List)stack;
63 | list_destroy(list);
64 | }
65 |
--------------------------------------------------------------------------------
/modules/UsingADTSet/ADTMap.c:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////
2 | //
3 | // Υλοποίηση του ADT Map μέσω του ADT Set (dependent data structure)
4 | //
5 | ///////////////////////////////////////////////////////////////////////
6 |
7 | #include
8 | #include
9 |
10 | #include "ADTSet.h"
11 | #include "ADTMap.h"
12 |
13 |
14 | struct map {
15 | Set set;
16 | CompareFunc compare;
17 | DestroyFunc destroy_key, destroy_value;
18 | };
19 |
20 | typedef struct map_node* MapNode;
21 | struct map_node {
22 | Pointer key;
23 | Pointer value;
24 | Map owner;
25 | };
26 |
27 | static int compare_map_nodes(MapNode a, MapNode b) {
28 | return a->owner->compare(a->key, b->key);
29 | }
30 |
31 | // Συνάρτηση που καταστρέφει ένα map node
32 | static void destroy_map_node(MapNode node) {
33 | if (node->owner->destroy_key != NULL)
34 | node->owner->destroy_key(node->key);
35 |
36 | if (node->owner->destroy_value != NULL)
37 | node->owner->destroy_value(node->value);
38 |
39 | free(node);
40 | }
41 |
42 | Map map_create(CompareFunc compare, DestroyFunc destroy_key, DestroyFunc destroy_value) {
43 | assert(compare != NULL); // LCOV_EXCL_LINE
44 |
45 | Map map = malloc(sizeof(*map));
46 | map->set = set_create((CompareFunc)compare_map_nodes, (DestroyFunc)destroy_map_node);
47 | map->compare = compare;
48 | map->destroy_key = destroy_key;
49 | map->destroy_value = destroy_value;
50 | return map;
51 | }
52 |
53 | int map_size(Map map) {
54 | return set_size(map->set);
55 | }
56 |
57 | Pointer map_find(Map map, Pointer key) {
58 | struct map_node search_node = { .key = key, .owner = map };
59 |
60 | MapNode node = set_find(map->set, &search_node);
61 | return node == NULL ? NULL : node->value;
62 | }
63 |
64 | void map_insert(Map map, Pointer key, Pointer value) {
65 | struct map_node search_node = { .key = key, .owner = map };
66 |
67 | MapNode node = set_find(map->set, &search_node);
68 | if (node != NULL) {
69 | if (key != node->key && map->destroy_key != NULL)
70 | map->destroy_key(node->key);
71 |
72 | if (value != node->value && map->destroy_value != NULL)
73 | map->destroy_value(node->value);
74 |
75 | node->key = key;
76 | node->value = value;
77 | return;
78 | }
79 |
80 | node = malloc(sizeof(*node));
81 | node->key = key;
82 | node->value = value;
83 | node->owner = map;
84 |
85 | set_insert(map->set, node);
86 | }
87 |
88 | bool map_remove(Map map, Pointer key) {
89 | struct map_node search_node = { .key = key, .owner = map };
90 |
91 | MapNode node = set_find(map->set, &search_node);
92 | if (node == NULL)
93 | return false;
94 |
95 | set_remove(map->set, node);
96 |
97 | return true;
98 | }
99 |
100 | DestroyFunc map_set_destroy_key(Map map, DestroyFunc destroy_key) {
101 | DestroyFunc old = map->destroy_key;
102 | map->destroy_key = destroy_key;
103 | return old;
104 | }
105 |
106 | DestroyFunc map_set_destroy_value(Map map, DestroyFunc destroy_value) {
107 | DestroyFunc old = map->destroy_value;
108 | map->destroy_value = destroy_value;
109 | return old;
110 | }
111 |
112 | void map_destroy(Map map) {
113 | // destroy το set, τα περιεχόμενα θα τα κάνει free η destroy_map_node
114 | set_destroy(map->set);
115 |
116 | // free το ίδιο το map
117 | free(map);
118 | }
119 |
120 | MapNode map_find_node(Map map, Pointer key) {
121 | struct map_node search_node = { .key = key, .owner = map };
122 |
123 | return set_find(map->set, &search_node);
124 | }
125 |
126 | MapNode map_first(Map map) {
127 | SetNode node = set_first(map->set);
128 | return node == SET_EOF ? MAP_EOF : set_node_value(map->set, node);
129 | }
130 |
131 | MapNode map_next(Map map, MapNode node) {
132 | SetNode set_node = set_find_node(map->set, node);
133 | SetNode next_node = set_next(map->set, set_node);
134 | return next_node == SET_EOF ? MAP_EOF : set_node_value(map->set, next_node);
135 | }
136 |
137 | Pointer map_node_key(Map map, MapNode node) {
138 | return node->key;
139 | }
140 |
141 | Pointer map_node_value(Map map, MapNode node) {
142 | return node->value;
143 | }
144 |
145 |
146 |
147 | //// Dummy υλοποίηση των συναρτήσεων που αφορούν hashing, αφού η συγκεκριμένη υλοποίηση του ADTMap δε χρησιμοποιεί hashing
148 |
149 | // LCOV_EXCL_START (δεν πρόκειται να κληθούν)
150 | uint hash_string(Pointer value) {
151 | return 0;
152 | }
153 | uint hash_int(Pointer value) {
154 | return 0;
155 | }
156 | uint hash_pointer(Pointer value) {
157 | return 0;
158 | }
159 | // LCOV_EXCL_STOP
160 |
161 | void map_set_hash_function(Map map, HashFunc hash_func) {
162 | }
--------------------------------------------------------------------------------
/modules/UsingAVL/ADTSet.c:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////
2 | //
3 | // Υλοποίηση του ADT Set μέσω AVL Tree
4 | //
5 | ///////////////////////////////////////////////////////////
6 |
7 | #include
8 | #include
9 |
10 | #include "ADTSet.h"
11 |
12 |
13 | // Υλοποιούμε τον ADT Set μέσω AVL, οπότε το struct set είναι ένα AVL Δέντρο.
14 | struct set {
15 | SetNode root; // η ρίζα, NULL αν είναι κενό δέντρο
16 | int size; // μέγεθος, ώστε η set_size να είναι Ο(1)
17 | CompareFunc compare; // η διάταξη
18 | DestroyFunc destroy_value; // Συνάρτηση που καταστρέφει ένα στοιχείο του set
19 | };
20 |
21 | // Ενώ το struct set_node είναι κόμβος ενός AVL Δέντρου Αναζήτησης
22 | struct set_node {
23 | SetNode left, right; // Παιδιά
24 | Pointer value; // Τιμή κόμβου
25 | int height; // Ύψος που βρίσκεται ο κόμβος στο δέντρο
26 | };
27 |
28 |
29 | //// Συναρτήσεις που υλοποιούν επιπλέον λειτουργίες του AVL σε σχέση με ένα απλό BST /////////////////////////////////////
30 |
31 | // Επιστρέφει τη max τιμή μεταξύ 2 ακεραίων
32 |
33 | static int int_max(int a, int b) {
34 | return (a > b) ? a : b ;
35 | }
36 |
37 | // Επιστρέφει το ύψος που βρίσκεται ο κόμβος στο δέντρο
38 |
39 | static int node_height(SetNode node) {
40 | if (!node) return 0;
41 | return node->height;
42 | }
43 |
44 | // Ενημερώνει το ύψος ενός κόμβου
45 |
46 | static void node_update_height(SetNode node) {
47 | node->height = 1 + int_max(node_height(node->left), node_height(node->right));
48 | }
49 |
50 | // Επιστρέφει τη διαφορά ύψους μεταξύ αριστερού και δεξιού υπόδεντρου
51 |
52 | static int node_balance(SetNode node) {
53 | return node_height(node->left) - node_height(node->right);
54 | }
55 |
56 | // Rotations : Όταν η διαφορά ύψους μεταξύ αριστερού και δεξιού υπόδεντρου είναι
57 | // μεγαλύτερη του 1 το δέντρο δεν είναι πια AVL. Υπάρχουν 4 διαφορετικά
58 | // rotations που εφαρμόζονται ανάλογα με την περίπτωση για να αποκατασταθεί η
59 | // ισορροπία. Η κάθε συνάρτηση παίρνει ως όρισμα τον κόμβο που πρέπει να γίνει
60 | // rotate, και επιστρέφει τη ρίζα του νέου υποδέντρου.
61 |
62 | // Single left rotation
63 |
64 | static SetNode node_rotate_left(SetNode node) {
65 | SetNode right_node = node->right;
66 | SetNode left_subtree = right_node->left;
67 |
68 | right_node->left = node;
69 | node->right = left_subtree;
70 |
71 | node_update_height(node);
72 | node_update_height(right_node);
73 |
74 | return right_node;
75 | }
76 |
77 | // Single right rotation
78 |
79 | static SetNode node_rotate_right(SetNode node) {
80 | SetNode left_node = node->left;
81 | SetNode left_right = left_node->right;
82 |
83 | left_node->right = node;
84 | node->left = left_right;
85 |
86 | node_update_height(node);
87 | node_update_height(left_node);
88 |
89 | return left_node;
90 | }
91 |
92 | // Double left-right rotation
93 |
94 | static SetNode node_rotate_left_right(SetNode node) {
95 | node->left = node_rotate_left(node->left);
96 | return node_rotate_right(node);
97 | }
98 |
99 | // Double right-left rotation
100 |
101 | static SetNode node_rotate_right_left(SetNode node) {
102 | node->right = node_rotate_right(node->right);
103 | return node_rotate_left(node);
104 | }
105 |
106 | // Επισκευή του AVL property αν δεν ισχύει
107 |
108 | static SetNode node_repair_balance(SetNode node) {
109 | node_update_height(node);
110 |
111 | int balance = node_balance(node);
112 | if (balance > 1) {
113 | // το αριστερό υπόδεντρο είναι unbalanced
114 | if (node_balance(node->left) >= 0)
115 | return node_rotate_right(node);
116 | else
117 | return node_rotate_left_right(node);
118 |
119 | } else if (balance < -1) {
120 | // το δεξί υπόδεντρο είναι unbalanced
121 | if (node_balance(node->right) <= 0)
122 | return node_rotate_left(node);
123 | else
124 | return node_rotate_right_left(node);
125 | }
126 |
127 | // δεν χρειάστηκε να πραγματοποιηθεί rotation
128 | return node;
129 | }
130 |
131 |
132 | //// Συναρτήσεις που είναι (σχεδόν) _ολόιδιες_ με τις αντίστοιχες της BST υλοποίησης ////////////////
133 | //
134 | // Είναι σημαντικό να κατανοήσουμε πρώτα τον κώδικα του BST πριν από αυτόν του AVL.
135 | // Θα μπορούσαμε οργανώνοντας τον κώδικα διαφορετικά να επαναχρησιμοποιήσουμε τις συναρτήσεις αυτές.
136 | //
137 | // Οι διαφορές είναι σημειωμένες με "AVL" σε σχόλιο
138 |
139 | // Δημιουργεί και επιστρέφει έναν κόμβο με τιμή value (χωρίς παιδιά)
140 | //
141 | static SetNode node_create(Pointer value) {
142 | SetNode node = malloc(sizeof(*node));
143 | node->left = NULL;
144 | node->right = NULL;
145 | node->value = value;
146 | node->height = 1; // AVL
147 | return node;
148 | }
149 |
150 | // Επιστρέφει τον κόμβο με τιμή ίση με value στο υποδέντρο με ρίζα node, διαφορετικά NULL
151 |
152 | static SetNode node_find_equal(SetNode node, CompareFunc compare, Pointer value) {
153 | // κενό υποδέντρο, δεν υπάρχει η τιμή
154 | if (node == NULL)
155 | return NULL;
156 |
157 | // Το πού βρίσκεται ο κόμβος που ψάχνουμε εξαρτάται από τη διάταξη της τιμής
158 | // value σε σχέση με την τιμή του τρέχοντος κόμβο (node->value)
159 | //
160 | int compare_res = compare(value, node->value); // αποθήκευση για να μην καλέσουμε την compare 2 φορές
161 | if (compare_res == 0) // value ισοδύναμη της node->value, βρήκαμε τον κόμβο
162 | return node;
163 | else if (compare_res < 0) // value < node->value, ο κόμβος που ψάχνουμε είναι στο αριστερό υποδέντρο
164 | return node_find_equal(node->left, compare, value);
165 | else // value > node->value, ο κόμβος που ψάχνουμε είνια στο δεξιό υποδέντρο
166 | return node_find_equal(node->right, compare, value);
167 | }
168 |
169 | // Επιστρέφει τον μικρότερο κόμβο του υποδέντρου με ρίζα node
170 |
171 | static SetNode node_find_min(SetNode node) {
172 | return node != NULL && node->left != NULL
173 | ? node_find_min(node->left) // Υπάρχει αριστερό υποδέντρο, η μικρότερη τιμή βρίσκεται εκεί
174 | : node; // Αλλιώς η μικρότερη τιμή είναι στο ίδιο το node
175 | }
176 |
177 | // Επιστρέφει τον μεγαλύτερο κόμβο του υποδέντρου με ρίζα node
178 |
179 | static SetNode node_find_max(SetNode node) {
180 | return node != NULL && node->right != NULL
181 | ? node_find_max(node->right) // Υπάρχει δεξί υποδέντρο, η μεγαλύτερη τιμή βρίσκεται εκεί
182 | : node; // Αλλιώς η μεγαλύτερη τιμή είναι στο ίδιο το node
183 | }
184 |
185 | // Επιστρέφει τον προηγούμενο (στη σειρά διάταξης) του κόμβου target στο υποδέντρο με ρίζα node,
186 | // ή NULL αν ο target είναι ο μικρότερος του υποδέντρου. Το υποδέντρο πρέπει να περιέχει τον κόμβο
187 | // target, οπότε δεν μπορεί να είναι κενό.
188 |
189 | static SetNode node_find_previous(SetNode node, CompareFunc compare, SetNode target) {
190 | if (node == target) {
191 | // Ο target είναι η ρίζα του υποδέντρου, o προηγούμενός του είναι ο μεγαλύτερος του αριστερού υποδέντρου.
192 | // (Aν δεν υπάρχει αριστερό παιδί, τότε ο κόμβος με τιμή value είναι ο μικρότερος του υποδέντρου, οπότε
193 | // η node_find_max θα επιστρέψει NULL όπως θέλουμε.)
194 | return node_find_max(node->left);
195 |
196 | } else if (compare(target->value, node->value) < 0) {
197 | // Ο target είναι στο αριστερό υποδέντρο, οπότε και ο προηγούμενός του είναι εκεί.
198 | return node_find_previous(node->left, compare, target);
199 |
200 | } else {
201 | // Ο target είναι στο δεξί υποδέντρο, ο προηγούμενός του μπορεί να είναι επίσης εκεί, διαφορετικά
202 | // ο προηγούμενός του είναι ο ίδιος ο node.
203 | SetNode res = node_find_previous(node->right, compare, target);
204 | return res != NULL ? res : node;
205 | }
206 | }
207 |
208 | // Επιστρέφει τον επόμενο (στη σειρά διάταξης) του κόμβου target στο υποδέντρο με ρίζα node,
209 | // ή NULL αν ο target είναι ο μεγαλύτερος του υποδέντρου. Το υποδέντρο πρέπει να περιέχει τον κόμβο
210 | // target, οπότε δεν μπορεί να είναι κενό.
211 |
212 | static SetNode node_find_next(SetNode node, CompareFunc compare, SetNode target) {
213 | if (node == target) {
214 | // Ο target είναι η ρίζα του υποδέντρου, o προηγούμενός του είναι ο μεγαλύτερος του αριστερού υποδέντρου.
215 | // (Aν δεν υπάρχει αριστερό παιδί, τότε ο κόμβος με τιμή value είναι ο μικρότερος του υποδέντρου, οπότε
216 | // η node_find_max θα επιστρέψει NULL όπως θέλουμε.)
217 | return node_find_min(node->right);
218 |
219 | } else if (compare(target->value, node->value) > 0) {
220 | // Ο target είναι στο αριστερό υποδέντρο, οπότε και ο προηγούμενός του είναι εκεί.
221 | return node_find_next(node->right, compare, target);
222 |
223 | } else {
224 | // Ο target είναι στο δεξί υποδέντρο, ο προηγούμενός του μπορεί να είναι επίσης εκεί, διαφορετικά
225 | // ο προηγούμενός του είναι ο ίδιος ο node.
226 | SetNode res = node_find_next(node->left, compare, target);
227 | return res != NULL ? res : node;
228 | }
229 | }
230 |
231 | // Αν υπάρχει κόμβος με τιμή ισοδύναμη της value, αλλάζει την τιμή του σε value, διαφορετικά προσθέτει
232 | // νέο κόμβο με τιμή value. Επιστρέφει τη νέα ρίζα του υποδέντρου, και θέτει το *inserted σε true
233 | // αν έγινε προσθήκη, ή false αν έγινε ενημέρωση.
234 |
235 | static SetNode node_insert(SetNode node, CompareFunc compare, Pointer value, bool* inserted, Pointer* old_value) {
236 | // Αν το υποδέντρο είναι κενό, δημιουργούμε νέο κόμβο ο οποίος γίνεται ρίζα του υποδέντρου
237 | if (node == NULL) {
238 | *inserted = true; // κάναμε προσθήκη
239 | return node_create(value);
240 | }
241 |
242 | // Το πού θα γίνει η προσθήκη εξαρτάται από τη διάταξη της τιμής
243 | // value σε σχέση με την τιμή του τρέχοντος κόμβου (node->value)
244 | //
245 | int compare_res = compare(value, node->value);
246 | if (compare_res == 0) {
247 | // βρήκαμε ισοδύναμη τιμή, κάνουμε update
248 | *inserted = false;
249 | *old_value = node->value;
250 | node->value = value;
251 |
252 | } else if (compare_res < 0) {
253 | // value < node->value, συνεχίζουμε αριστερά.
254 | node->left = node_insert(node->left, compare, value, inserted, old_value);
255 |
256 | } else {
257 | // value > node->value, συνεχίζουμε δεξιά
258 | node->right = node_insert(node->right, compare, value, inserted, old_value);
259 | }
260 |
261 | return node_repair_balance(node); // AVL
262 | }
263 |
264 | // Αφαιρεί και αποθηκεύει στο min_node τον μικρότερο κόμβο του υποδέντρου με ρίζα node.
265 | // Επιστρέφει τη νέα ρίζα του υποδέντρου.
266 |
267 | static SetNode node_remove_min(SetNode node, SetNode* min_node) {
268 | if (node->left == NULL) {
269 | // Δεν έχουμε αριστερό υποδέντρο, οπότε ο μικρότερος είναι ο ίδιος ο node
270 | *min_node = node;
271 | return node->right; // νέα ρίζα είναι το δεξιό παιδί
272 |
273 | } else {
274 | // Εχουμε αριστερό υποδέντρο, οπότε η μικρότερη τιμή είναι εκεί. Συνεχίζουμε αναδρομικά
275 | // και ενημερώνουμε το node->left με τη νέα ρίζα του υποδέντρου.
276 | node->left = node_remove_min(node->left, min_node);
277 |
278 | return node_repair_balance(node); // AVL
279 | }
280 | }
281 |
282 | // Διαγράφει το κόμβο με τιμή ισοδύναμη της value, αν υπάρχει. Επιστρέφει τη νέα ρίζα του
283 | // υποδέντρου, και θέτει το *removed σε true αν έγινε πραγματικά διαγραφή.
284 |
285 | static SetNode node_remove(SetNode node, CompareFunc compare, Pointer value, bool* removed, Pointer* old_value) {
286 | if (node == NULL) {
287 | *removed = false; // κενό υποδέντρο, δεν υπάρχει η τιμή
288 | return NULL;
289 | }
290 |
291 | int compare_res = compare(value, node->value);
292 | if (compare_res == 0) {
293 | // Βρέθηκε ισοδύναμη τιμή στον node, οπότε τον διαγράφουμε. Το πώς θα γίνει αυτό εξαρτάται από το αν έχει παιδιά.
294 | *removed = true;
295 | *old_value = node->value;
296 |
297 | if (node->left == NULL) {
298 | // Δεν υπάρχει αριστερό υποδέντρο, οπότε διαγράφεται απλά ο κόμβος και νέα ρίζα μπαίνει το δεξί παιδί
299 | SetNode right = node->right; // αποθήκευση πριν το free!
300 | free(node);
301 | return right;
302 |
303 | } else if (node->right == NULL) {
304 | // Δεν υπάρχει δεξί υποδέντρο, οπότε διαγράφεται απλά ο κόμβος και νέα ρίζα μπαίνει το αριστερό παιδί
305 | SetNode left = node->left; // αποθήκευση πριν το free!
306 | free(node);
307 | return left;
308 |
309 | } else {
310 | // Υπάρχουν και τα δύο παιδιά. Αντικαθιστούμε την τιμή του node με την μικρότερη του δεξιού υποδέντρου, η οποία
311 | // αφαιρείται. Η συνάρτηση node_remove_min κάνει ακριβώς αυτή τη δουλειά.
312 |
313 | SetNode min_right;
314 | node->right = node_remove_min(node->right, &min_right);
315 |
316 | // Σύνδεση του min_right στη θέση του node
317 | min_right->left = node->left;
318 | min_right->right = node->right;
319 |
320 | free(node);
321 |
322 | return node_repair_balance(min_right); // AVL
323 | }
324 | }
325 |
326 | // compare_res != 0, συνεχίζουμε στο αριστερό ή δεξί υποδέντρο, η ρίζα δεν αλλάζει.
327 | if (compare_res < 0)
328 | node->left = node_remove(node->left, compare, value, removed, old_value);
329 | else
330 | node->right = node_remove(node->right, compare, value, removed, old_value);
331 |
332 | return node_repair_balance(node); // AVL
333 | }
334 |
335 | // Καταστρέφει όλο το υποδέντρο με ρίζα node
336 |
337 | static void node_destroy(SetNode node, DestroyFunc destroy_value) {
338 | if (node == NULL)
339 | return;
340 |
341 | // πρώτα destroy τα παιδιά, μετά free το node
342 | node_destroy(node->left, destroy_value);
343 | node_destroy(node->right, destroy_value);
344 |
345 | if (destroy_value != NULL)
346 | destroy_value(node->value);
347 |
348 | free(node);
349 | }
350 |
351 |
352 | //// Συναρτήσεις του ADT Set. Γενικά πολύ απλές, αφού καλούν τις αντίστοιχες node_* //////////////////////////////////
353 | //
354 | // Επίσης ολόιδιες με αυτές του BST-based Set
355 |
356 | Set set_create(CompareFunc compare, DestroyFunc destroy_value) {
357 | assert(compare != NULL); // LCOV_EXCL_LINE
358 |
359 | // δημιουργούμε το stuct
360 | Set set = malloc(sizeof(*set));
361 | set->root = NULL; // κενό δέντρο
362 | set->size = 0;
363 | set->compare = compare;
364 | set->destroy_value = destroy_value;
365 |
366 | return set;
367 | }
368 |
369 | int set_size(Set set) {
370 | return set->size;
371 | }
372 |
373 | void set_insert(Set set, Pointer value) {
374 | bool inserted;
375 | Pointer old_value;
376 | set->root = node_insert(set->root, set->compare, value, &inserted, &old_value);
377 |
378 | // Το size αλλάζει μόνο αν μπει νέος κόμβος. Στα updates κάνουμε destroy την παλιά τιμή
379 | if (inserted)
380 | set->size++;
381 | else if (set->destroy_value != NULL)
382 | set->destroy_value(old_value);
383 | }
384 |
385 | bool set_remove(Set set, Pointer value) {
386 | bool removed;
387 | Pointer old_value = NULL;
388 | set->root = node_remove(set->root, set->compare, value, &removed, &old_value);
389 |
390 | // Το size αλλάζει μόνο αν πραγματικά αφαιρεθεί ένας κόμβος
391 | if (removed) {
392 | set->size--;
393 |
394 | if (set->destroy_value != NULL)
395 | set->destroy_value(old_value);
396 | }
397 |
398 | return removed;
399 | }
400 |
401 | Pointer set_find(Set set, Pointer value) {
402 | SetNode node = node_find_equal(set->root, set->compare, value);
403 | return node == NULL ? NULL : node->value;
404 | }
405 |
406 | DestroyFunc set_set_destroy_value(Set vec, DestroyFunc destroy_value) {
407 | DestroyFunc old = vec->destroy_value;
408 | vec->destroy_value = destroy_value;
409 | return old;
410 | }
411 |
412 | void set_destroy(Set set) {
413 | node_destroy(set->root, set->destroy_value);
414 | free(set);
415 | }
416 |
417 | SetNode set_first(Set set) {
418 | return node_find_min(set->root);
419 | }
420 |
421 | SetNode set_last(Set set) {
422 | return node_find_max(set->root);
423 | }
424 |
425 | SetNode set_previous(Set set, SetNode node) {
426 | return node_find_previous(set->root, set->compare, node);
427 | }
428 |
429 | SetNode set_next(Set set, SetNode node) {
430 | return node_find_next(set->root, set->compare, node);
431 | }
432 |
433 | Pointer set_node_value(Set set, SetNode node) {
434 | return node->value;
435 | }
436 |
437 | SetNode set_find_node(Set set, Pointer value) {
438 | return node_find_equal(set->root, set->compare, value);
439 | }
440 |
441 |
442 |
443 | // Συναρτήσεις που δεν υπάρχουν στο public interface αλλά χρησιμοποιούνται στα tests
444 | // Ελέγχουν ότι το δέντρο είναι ένα σωστό AVL.
445 |
446 | // LCOV_EXCL_START (δε μας ενδιαφέρει το coverage των test εντολών, και επιπλέον μόνο τα true branches εξετάζονται σε ένα επιτυχημένο test)
447 |
448 | bool node_is_avl(SetNode node, CompareFunc compare) {
449 | if (node == NULL)
450 | return true;
451 |
452 | // Ελέγχουμε την ιδιότητα:
453 | // κάθε κόμβος είναι > αριστερό παιδί, > δεξιότερο κόμβο του αριστερού υποδέντρου, < δεξί παιδί, < αριστερότερο κόμβο του δεξιού υποδέντρου.
454 | // Είναι ισοδύναμη με την BST ιδιότητα (κάθε κόμβος είναι > αριστερό υποδέντρο και < δεξί υποδέντρο) αλλά ευκολότερο να ελεγθεί.
455 | bool res = true;
456 | if(node->left != NULL)
457 | res = res && compare(node->left->value, node->value) < 0 && compare(node_find_max(node->left)->value, node->value) < 0;
458 | if(node->right != NULL)
459 | res = res && compare(node->right->value, node->value) > 0 && compare(node_find_min(node->right)->value, node->value) > 0;
460 |
461 | // Το ύψος είναι σωστό
462 | res = res && node->height == 1 + int_max(node_height(node->left), node_height(node->right));
463 |
464 | // Ο κόμβος έχει την AVL ιδιότητα
465 | int balance = node_balance(node);
466 | res = res && balance >= -1 && balance <= 1;
467 |
468 | // Τα υποδέντρα είναι σωστά
469 | res = res &&
470 | node_is_avl(node->left, compare) &&
471 | node_is_avl(node->right, compare);
472 |
473 | return res;
474 | }
475 |
476 | bool set_is_proper(Set node) {
477 | return node_is_avl(node->root, node->compare);
478 | }
479 |
480 | // LCOV_EXCL_STOP
--------------------------------------------------------------------------------
/modules/UsingBinarySearchTree/ADTSet.c:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////
2 | //
3 | // Υλοποίηση του ADT Set μέσω Binary Search Tree (BST)
4 | //
5 | ///////////////////////////////////////////////////////////
6 |
7 | #include
8 | #include
9 |
10 | #include "ADTSet.h"
11 |
12 |
13 | // Υλοποιούμε τον ADT Set μέσω BST, οπότε το struct set είναι ένα Δυαδικό Δέντρο Αναζήτησης.
14 | struct set {
15 | SetNode root; // η ρίζα, NULL αν είναι κενό δέντρο
16 | int size; // μέγεθος, ώστε η set_size να είναι Ο(1)
17 | CompareFunc compare; // η διάταξη
18 | DestroyFunc destroy_value; // Συνάρτηση που καταστρέφει ένα στοιχείο του set
19 | };
20 |
21 | // Ενώ το struct set_node είναι κόμβος ενός Δυαδικού Δέντρου Αναζήτησης
22 | struct set_node {
23 | SetNode left, right; // Παιδιά
24 | Pointer value;
25 | };
26 |
27 |
28 | // Παρατηρήσεις για τις node_* συναρτήσεις
29 | // - είναι βοηθητικές (κρυφές από το χρήστη) και υλοποιούν διάφορες λειτουργίες πάνω σε κόμβους του BST.
30 | // - είναι αναδρομικές, η αναδρομή είναι γενικά πολύ βοηθητική στα δέντρα.
31 | // - όσες συναρτήσεις _τροποποιούν_ το δέντρο, ουσιαστικά ενεργούν στο _υποδέντρο_ με ρίζα τον κόμβο node, και επιστρέφουν τη νέα
32 | // ρίζα του υποδέντρου μετά την τροποποίηση. Η νέα ρίζα χρησιμοποιείται από την προηγούμενη αναδρομική κλήση.
33 | //
34 | // Οι set_* συναρτήσεις (πιο μετά στο αρχείο), υλοποιούν τις συναρτήσεις του ADT Set, και είναι απλές, καλώντας τις αντίστοιχες node_*.
35 |
36 |
37 | // Δημιουργεί και επιστρέφει έναν κόμβο με τιμή value (χωρίς παιδιά)
38 |
39 | static SetNode node_create(Pointer value) {
40 | SetNode node = malloc(sizeof(*node));
41 | node->left = NULL;
42 | node->right = NULL;
43 | node->value = value;
44 | return node;
45 | }
46 |
47 | // Επιστρέφει τον κόμβο με τιμή ίση με value στο υποδέντρο με ρίζα node, διαφορετικά NULL
48 |
49 | static SetNode node_find_equal(SetNode node, CompareFunc compare, Pointer value) {
50 | // κενό υποδέντρο, δεν υπάρχει η τιμή
51 | if (node == NULL)
52 | return NULL;
53 |
54 | // Το πού βρίσκεται ο κόμβος που ψάχνουμε εξαρτάται από τη διάταξη της τιμής
55 | // value σε σχέση με την τιμή του τρέχοντος κόμβο (node->value)
56 | //
57 | int compare_res = compare(value, node->value); // αποθήκευση για να μην καλέσουμε την compare 2 φορές
58 | if (compare_res == 0) // value ισοδύναμη της node->value, βρήκαμε τον κόμβο
59 | return node;
60 | else if (compare_res < 0) // value < node->value, ο κόμβος που ψάχνουμε είναι στο αριστερό υποδέντρο
61 | return node_find_equal(node->left, compare, value);
62 | else // value > node->value, ο κόμβος που ψάχνουμε είνια στο δεξιό υποδέντρο
63 | return node_find_equal(node->right, compare, value);
64 | }
65 |
66 | // Επιστρέφει τον μικρότερο κόμβο του υποδέντρου με ρίζα node
67 |
68 | static SetNode node_find_min(SetNode node) {
69 | return node != NULL && node->left != NULL
70 | ? node_find_min(node->left) // Υπάρχει αριστερό υποδέντρο, η μικρότερη τιμή βρίσκεται εκεί
71 | : node; // Αλλιώς η μικρότερη τιμή είναι στο ίδιο το node
72 | }
73 |
74 | // Επιστρέφει τον μεγαλύτερο κόμβο του υποδέντρου με ρίζα node
75 |
76 | static SetNode node_find_max(SetNode node) {
77 | return node != NULL && node->right != NULL
78 | ? node_find_max(node->right) // Υπάρχει δεξί υποδέντρο, η μεγαλύτερη τιμή βρίσκεται εκεί
79 | : node; // Αλλιώς η μεγαλύτερη τιμή είναι στο ίδιο το node
80 | }
81 |
82 | // Επιστρέφει τον προηγούμενο (στη σειρά διάταξης) του κόμβου target στο υποδέντρο με ρίζα node,
83 | // ή NULL αν ο target είναι ο μικρότερος του υποδέντρου. Το υποδέντρο πρέπει να περιέχει τον κόμβο
84 | // target, οπότε δεν μπορεί να είναι κενό.
85 |
86 | static SetNode node_find_previous(SetNode node, CompareFunc compare, SetNode target) {
87 | if (node == target) {
88 | // Ο target είναι η ρίζα του υποδέντρου, o προηγούμενός του είναι ο μεγαλύτερος του αριστερού υποδέντρου.
89 | // (Aν δεν υπάρχει αριστερό παιδί, τότε ο κόμβος με τιμή value είναι ο μικρότερος του υποδέντρου, οπότε
90 | // η node_find_max θα επιστρέψει NULL όπως θέλουμε.)
91 | return node_find_max(node->left);
92 |
93 | } else if (compare(target->value, node->value) < 0) {
94 | // Ο target είναι στο αριστερό υποδέντρο, οπότε και ο προηγούμενός του είναι εκεί.
95 | return node_find_previous(node->left, compare, target);
96 |
97 | } else {
98 | // Ο target είναι στο δεξί υποδέντρο, ο προηγούμενός του μπορεί να είναι επίσης εκεί,
99 | // αν όχι ο προηγούμενός του είναι ο ίδιος ο node.
100 | SetNode res = node_find_previous(node->right, compare, target);
101 | return res != NULL ? res : node;
102 | }
103 | }
104 |
105 | // Επιστρέφει τον επόμενο (στη σειρά διάταξης) του κόμβου target στο υποδέντρο με ρίζα node,
106 | // ή NULL αν ο target είναι ο μεγαλύτερος του υποδέντρου. Το υποδέντρο πρέπει να περιέχει τον κόμβο
107 | // target, οπότε δεν μπορεί να είναι κενό.
108 |
109 | static SetNode node_find_next(SetNode node, CompareFunc compare, SetNode target) {
110 | if (node == target) {
111 | // Ο target είναι η ρίζα του υποδέντρου, o επόμενός του είναι ο μικρότερος του δεξιού υποδέντρου.
112 | // (Aν δεν υπάρχει δεξί παιδί, τότε ο κόμβος με τιμή value είναι ο μεγαλύτερος του υποδέντρου, οπότε
113 | // η node_find_min θα επιστρέψει NULL όπως θέλουμε.)
114 | return node_find_min(node->right);
115 |
116 | } else if (compare(target->value, node->value) > 0) {
117 | // Ο target είναι στο δεξί υποδέντρο, οπότε και ο επόμενός του είναι εκεί.
118 | return node_find_next(node->right, compare, target);
119 |
120 | } else {
121 | // Ο target είναι στο αριστερό υποδέντρο, ο επόμενός του μπορεί να είναι επίσης εκεί,
122 | // αν όχι ο επόμενός του είναι ο ίδιος ο node.
123 | SetNode res = node_find_next(node->left, compare, target);
124 | return res != NULL ? res : node;
125 | }
126 | }
127 |
128 | // Αν υπάρχει κόμβος με τιμή ισοδύναμη της value, αλλάζει την τιμή του σε value, διαφορετικά προσθέτει
129 | // νέο κόμβο με τιμή value. Επιστρέφει τη νέα ρίζα του υποδέντρου, και θέτει το *inserted σε true
130 | // αν έγινε προσθήκη, ή false αν έγινε ενημέρωση.
131 |
132 | static SetNode node_insert(SetNode node, CompareFunc compare, Pointer value, bool* inserted, Pointer* old_value) {
133 | // Αν το υποδέντρο είναι κενό, δημιουργούμε νέο κόμβο ο οποίος γίνεται ρίζα του υποδέντρου
134 | if (node == NULL) {
135 | *inserted = true; // κάναμε προσθήκη
136 | return node_create(value);
137 | }
138 |
139 | // Το που θα γίνει η προσθήκη εξαρτάται από τη διάταξη της τιμής
140 | // value σε σχέση με την τιμή του τρέχοντος κόμβου (node->value)
141 | //
142 | int compare_res = compare(value, node->value);
143 | if (compare_res == 0) {
144 | // βρήκαμε ισοδύναμη τιμή, κάνουμε update
145 | *inserted = false;
146 | *old_value = node->value;
147 | node->value = value;
148 |
149 | } else if (compare_res < 0) {
150 | // value < node->value, συνεχίζουμε αριστερά.
151 | node->left = node_insert(node->left, compare, value, inserted, old_value);
152 |
153 | } else {
154 | // value > node->value, συνεχίζουμε δεξιά
155 | node->right = node_insert(node->right, compare, value, inserted, old_value);
156 | }
157 |
158 | return node; // η ρίζα του υποδέντρου δεν αλλάζει
159 | }
160 |
161 | // Αφαιρεί και αποθηκεύει στο min_node τον μικρότερο κόμβο του υποδέντρου με ρίζα node.
162 | // Επιστρέφει τη νέα ρίζα του υποδέντρου.
163 |
164 | static SetNode node_remove_min(SetNode node, SetNode* min_node) {
165 | if (node->left == NULL) {
166 | // Δεν έχουμε αριστερό υποδέντρο, οπότε ο μικρότερος είναι ο ίδιος ο node
167 | *min_node = node;
168 | return node->right; // νέα ρίζα είναι το δεξιό παιδί
169 |
170 | } else {
171 | // Εχουμε αριστερό υποδέντρο, οπότε η μικρότερη τιμή είναι εκεί. Συνεχίζουμε αναδρομικά
172 | // και ενημερώνουμε το node->left με τη νέα ρίζα του υποδέντρου.
173 | node->left = node_remove_min(node->left, min_node);
174 | return node; // η ρίζα δεν μεταβάλλεται
175 | }
176 | }
177 |
178 | // Διαγράφει το κόμβο με τιμή ισοδύναμη της value, αν υπάρχει. Επιστρέφει τη νέα ρίζα του
179 | // υποδέντρου, και θέτει το *removed σε true αν έγινε πραγματικά διαγραφή.
180 |
181 | static SetNode node_remove(SetNode node, CompareFunc compare, Pointer value, bool* removed, Pointer* old_value) {
182 | if (node == NULL) {
183 | *removed = false; // κενό υποδέντρο, δεν υπάρχει η τιμή
184 | return NULL;
185 | }
186 |
187 | int compare_res = compare(value, node->value);
188 | if (compare_res == 0) {
189 | // Βρέθηκε ισοδύναμη τιμή στον node, οπότε τον διαγράφουμε. Το πώς θα γίνει αυτό εξαρτάται από το αν έχει παιδιά.
190 | *removed = true;
191 | *old_value = node->value;
192 |
193 | if (node->left == NULL) {
194 | // Δεν υπάρχει αριστερό υποδέντρο, οπότε διαγράφεται απλά ο κόμβος και νέα ρίζα μπαίνει το δεξί παιδί
195 | SetNode right = node->right; // αποθήκευση πριν το free!
196 | free(node);
197 | return right;
198 |
199 | } else if (node->right == NULL) {
200 | // Δεν υπάρχει δεξί υποδέντρο, οπότε διαγράφεται απλά ο κόμβος και νέα ρίζα μπαίνει το αριστερό παιδί
201 | SetNode left = node->left; // αποθήκευση πριν το free!
202 | free(node);
203 | return left;
204 |
205 | } else {
206 | // Υπάρχουν και τα δύο παιδιά. Αντικαθιστούμε την τιμή του node με την μικρότερη του δεξιού υποδέντρου, η οποία
207 | // αφαιρείται. Η συνάρτηση node_remove_min κάνει ακριβώς αυτή τη δουλειά.
208 |
209 | SetNode min_right;
210 | node->right = node_remove_min(node->right, &min_right);
211 |
212 | // Σύνδεση του min_right στη θέση του node
213 | min_right->left = node->left;
214 | min_right->right = node->right;
215 |
216 | free(node);
217 | return min_right;
218 | }
219 | }
220 |
221 | // compare_res != 0, συνεχίζουμε στο αριστερό ή δεξί υποδέντρο, η ρίζα δεν αλλάζει.
222 | if (compare_res < 0)
223 | node->left = node_remove(node->left, compare, value, removed, old_value);
224 | else
225 | node->right = node_remove(node->right, compare, value, removed, old_value);
226 |
227 | return node;
228 | }
229 |
230 | // Καταστρέφει όλο το υποδέντρο με ρίζα node
231 |
232 | static void node_destroy(SetNode node, DestroyFunc destroy_value) {
233 | if (node == NULL)
234 | return;
235 |
236 | // πρώτα destroy τα παιδιά, μετά free το node
237 | node_destroy(node->left, destroy_value);
238 | node_destroy(node->right, destroy_value);
239 |
240 | if (destroy_value != NULL)
241 | destroy_value(node->value);
242 |
243 | free(node);
244 | }
245 |
246 |
247 | //// Συναρτήσεις του ADT Set. Γενικά πολύ απλές, αφού καλούν τις αντίστοιχες node_*
248 |
249 | Set set_create(CompareFunc compare, DestroyFunc destroy_value) {
250 | assert(compare != NULL); // LCOV_EXCL_LINE
251 |
252 | // δημιουργούμε το stuct
253 | Set set = malloc(sizeof(*set));
254 | set->root = NULL; // κενό δέντρο
255 | set->size = 0;
256 | set->compare = compare;
257 | set->destroy_value = destroy_value;
258 |
259 | return set;
260 | }
261 |
262 | int set_size(Set set) {
263 | return set->size;
264 | }
265 |
266 | void set_insert(Set set, Pointer value) {
267 | bool inserted;
268 | Pointer old_value;
269 | set->root = node_insert(set->root, set->compare, value, &inserted, &old_value);
270 |
271 | // Το size αλλάζει μόνο αν μπει νέος κόμβος. Στα updates κάνουμε destroy την παλιά τιμή
272 | if (inserted)
273 | set->size++;
274 | else if (set->destroy_value != NULL)
275 | set->destroy_value(old_value);
276 | }
277 |
278 | bool set_remove(Set set, Pointer value) {
279 | bool removed;
280 | Pointer old_value = NULL;
281 | set->root = node_remove(set->root, set->compare, value, &removed, &old_value);
282 |
283 | // Το size αλλάζει μόνο αν πραγματικά αφαιρεθεί ένας κόμβος
284 | if (removed) {
285 | set->size--;
286 |
287 | if (set->destroy_value != NULL)
288 | set->destroy_value(old_value);
289 | }
290 |
291 | return removed;
292 | }
293 |
294 | Pointer set_find(Set set, Pointer value) {
295 | SetNode node = node_find_equal(set->root, set->compare, value);
296 | return node == NULL ? NULL : node->value;
297 | }
298 |
299 | DestroyFunc set_set_destroy_value(Set vec, DestroyFunc destroy_value) {
300 | DestroyFunc old = vec->destroy_value;
301 | vec->destroy_value = destroy_value;
302 | return old;
303 | }
304 |
305 | void set_destroy(Set set) {
306 | node_destroy(set->root, set->destroy_value);
307 | free(set);
308 | }
309 |
310 | SetNode set_first(Set set) {
311 | return node_find_min(set->root);
312 | }
313 |
314 | SetNode set_last(Set set) {
315 | return node_find_max(set->root);
316 | }
317 |
318 | SetNode set_previous(Set set, SetNode node) {
319 | return node_find_previous(set->root, set->compare, node);
320 | }
321 |
322 | SetNode set_next(Set set, SetNode node) {
323 | return node_find_next(set->root, set->compare, node);
324 | }
325 |
326 | Pointer set_node_value(Set set, SetNode node) {
327 | return node->value;
328 | }
329 |
330 | SetNode set_find_node(Set set, Pointer value) {
331 | return node_find_equal(set->root, set->compare, value);
332 | }
333 |
334 |
335 |
336 | // Συναρτήσεις που δεν υπάρχουν στο public interface αλλά χρησιμοποιούνται στα tests.
337 | // Ελέγχουν ότι το δέντρο είναι ένα σωστό BST.
338 |
339 | // LCOV_EXCL_START (δε μας ενδιαφέρει το coverage των test εντολών, και επιπλέον μόνο τα true branches εκτελούνται σε ένα επιτυχημένο test)
340 |
341 | static bool node_is_bst(SetNode node, CompareFunc compare) {
342 | if (node == NULL)
343 | return true;
344 |
345 | // Ελέγχουμε την ιδιότητα:
346 | // κάθε κόμβος είναι > αριστερό παιδί, > δεξιότερο κόμβο του αριστερού υποδέντρου, < δεξί παιδί, < αριστερότερο κόμβο του δεξιού υποδέντρου.
347 | // Είναι ισοδύναμη με την BST ιδιότητα (κάθε κόμβος είναι > αριστερό υποδέντρο και < δεξί υποδέντρο) αλλά ευκολότερο να ελεγθεί.
348 | bool res = true;
349 | if(node->left != NULL)
350 | res = res && compare(node->left->value, node->value) < 0 && compare(node_find_max(node->left)->value, node->value) < 0;
351 | if(node->right != NULL)
352 | res = res && compare(node->right->value, node->value) > 0 && compare(node_find_min(node->right)->value, node->value) > 0;
353 |
354 | return res &&
355 | node_is_bst(node->left, compare) &&
356 | node_is_bst(node->right, compare);
357 | }
358 |
359 | bool set_is_proper(Set node) {
360 | return node_is_bst(node->root, node->compare);
361 | }
362 |
363 | // LCOV_EXCL_STOP
--------------------------------------------------------------------------------
/modules/UsingDynamicArray/ADTVector.c:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////
2 | //
3 | // Υλοποίηση του ADT Vector μέσω Dynamic Array.
4 | //
5 | ///////////////////////////////////////////////////////////
6 |
7 | #include
8 | #include
9 |
10 | #include "ADTVector.h"
11 |
12 |
13 | // Το αρχικό μέγεθος που δεσμεύουμε
14 | #define VECTOR_MIN_CAPACITY 10
15 |
16 | // Ένα VectorNode είναι pointer σε αυτό το struct. (το struct περιέχει μόνο ένα στοιχείο, οπότε θα μπροούσαμε και να το αποφύγουμε, αλλά κάνει τον κώδικα απλούστερο)
17 | struct vector_node {
18 | Pointer value; // Η τιμή του κόμβου.
19 | };
20 |
21 | // Ενα Vector είναι pointer σε αυτό το struct
22 | struct vector {
23 | VectorNode array; // Τα δεδομένα, πίνακας από struct vector_node
24 | int size; // Πόσα στοιχεία έχουμε προσθέσει
25 | int capacity; // Πόσο χώρο έχουμε δεσμεύσει (το μέγεθος του array). Πάντα capacity >= size, αλλά μπορεί να έχουμε
26 | DestroyFunc destroy_value; // Συνάρτηση που καταστρέφει ένα στοιχείο του vector.
27 | };
28 |
29 |
30 | Vector vector_create(int size, DestroyFunc destroy_value) {
31 | // Δημιουργία του struct
32 | Vector vec = malloc(sizeof(*vec));
33 |
34 | vec->size = size;
35 | vec->destroy_value = destroy_value;
36 |
37 | // Δέσμευση μνήμης για τον πίνακα. Αρχικά το vector περιέχει size
38 | // μη-αρχικοποιημένα στοιχεία, αλλά εμείς δεσμεύουμε xώρο για τουλάχιστον
39 | // VECTOR_MIN_CAPACITY για να αποφύγουμε τα πολλαπλά resizes.
40 | //
41 | vec->capacity = size < VECTOR_MIN_CAPACITY ? VECTOR_MIN_CAPACITY : size;
42 | vec->array = calloc(vec->capacity, sizeof(*vec->array)); // αρχικοποίηση σε 0 (NULL)
43 |
44 | return vec;
45 | }
46 |
47 | int vector_size(Vector vec) {
48 | return vec->size;
49 | }
50 |
51 | Pointer vector_get_at(Vector vec, int pos) {
52 | assert(pos >= 0 && pos < vec->size); // LCOV_EXCL_LINE (αγνοούμε το branch από τα coverage reports, είναι δύσκολο να τεστάρουμε το false γιατί θα κρασάρει το test)
53 |
54 | return vec->array[pos].value;
55 | }
56 |
57 | void vector_set_at(Vector vec, int pos, Pointer value) {
58 | assert(pos >= 0 && pos < vec->size); // LCOV_EXCL_LINE
59 |
60 | // Αν υπάρχει συνάρτηση destroy_value, την καλούμε για το στοιχείο που αντικαθίσταται
61 | if (value != vec->array[pos].value && vec->destroy_value != NULL)
62 | vec->destroy_value(vec->array[pos].value);
63 |
64 | vec->array[pos].value = value;
65 | }
66 |
67 | void vector_insert_last(Vector vec, Pointer value) {
68 | // Μεγαλώνουμε τον πίνακα (αν χρειαστεί), ώστε να χωράει τουλάχιστον size στοιχεία
69 | // Διπλασιάζουμε κάθε φορά το capacity (σημαντικό για την πολυπλοκότητα!)
70 | if (vec->capacity == vec->size) {
71 | // Προσοχή: δεν πρέπει να κάνουμε free τον παλιό pointer, το κάνει η realloc
72 | vec->capacity *= 2;
73 | vec->array = realloc(vec->array, vec->capacity * sizeof(*vec->array));
74 | }
75 |
76 | // Μεγαλώνουμε τον πίνακα και προσθέτουμε το στοιχείο
77 | vec->array[vec->size].value = value;
78 | vec->size++;
79 | }
80 |
81 | void vector_remove_last(Vector vec) {
82 | assert(vec->size != 0); // LCOV_EXCL_LINE
83 |
84 | // Αν υπάρχει συνάρτηση destroy_value, την καλούμε για το στοιχείο που αφαιρείται
85 | if (vec->destroy_value != NULL)
86 | vec->destroy_value(vec->array[vec->size - 1].value);
87 |
88 | // Αφαιρούμε στοιχείο οπότε ο πίνακας μικραίνει
89 | vec->size--;
90 |
91 | // Μικραίνουμε τον πίνακα αν χρειαστεί, ώστε να μην υπάρχει υπερβολική σπατάλη χώρου.
92 | // Για την πολυπλοκότητα είναι σημαντικό να μειώνουμε το μέγεθος στο μισό, και μόνο
93 | // αν το capacity είναι τετραπλάσιο του size (δηλαδή το 75% του πίνακα είναι άδειος).
94 | //
95 | if (vec->capacity > vec->size * 4 && vec->capacity > 2*VECTOR_MIN_CAPACITY) {
96 | vec->capacity /= 2;
97 | vec->array = realloc(vec->array, vec->capacity * sizeof(*vec->array));
98 | }
99 | }
100 |
101 | Pointer vector_find(Vector vec, Pointer value, CompareFunc compare) {
102 | // Διάσχιση του vector
103 | for (int i = 0; i < vec->size; i++)
104 | if (compare(vec->array[i].value, value) == 0)
105 | return vec->array[i].value; // βρέθηκε
106 |
107 | return NULL; // δεν υπάρχει
108 | }
109 |
110 | DestroyFunc vector_set_destroy_value(Vector vec, DestroyFunc destroy_value) {
111 | DestroyFunc old = vec->destroy_value;
112 | vec->destroy_value = destroy_value;
113 | return old;
114 | }
115 |
116 | void vector_destroy(Vector vec) {
117 | // Αν υπάρχει συνάρτηση destroy_value, την καλούμε για όλα τα στοιχεία
118 | if (vec->destroy_value != NULL)
119 | for (int i = 0; i < vec->size; i++)
120 | vec->destroy_value(vec->array[i].value);
121 |
122 | // Πρέπει να κάνουμε free τόσο τον πίνακα όσο και το struct!
123 | free(vec->array);
124 | free(vec); // τελευταίο το vec!
125 | }
126 |
127 |
128 | // Συναρτήσεις για διάσχιση μέσω node /////////////////////////////////////////////////////
129 |
130 | VectorNode vector_first(Vector vec) {
131 | if (vec->size == 0)
132 | return VECTOR_BOF;
133 | else
134 | return &vec->array[0];
135 | }
136 |
137 | VectorNode vector_last(Vector vec) {
138 | if (vec->size == 0)
139 | return VECTOR_EOF;
140 | else
141 | return &vec->array[vec->size-1];
142 | }
143 |
144 | VectorNode vector_next(Vector vec, VectorNode node) {
145 | if (node == &vec->array[vec->size-1])
146 | return VECTOR_EOF;
147 | else
148 | return node + 1;
149 | }
150 |
151 | VectorNode vector_previous(Vector vec, VectorNode node) {
152 | if (node == &vec->array[0])
153 | return VECTOR_EOF;
154 | else
155 | return node - 1;
156 | }
157 |
158 | Pointer vector_node_value(Vector vec, VectorNode node) {
159 | return node->value;
160 | }
161 |
162 | VectorNode vector_find_node(Vector vec, Pointer value, CompareFunc compare) {
163 | // Διάσχιση του vector
164 | for (int i = 0; i < vec->size; i++)
165 | if (compare(vec->array[i].value, value) == 0)
166 | return &vec->array[i]; // βρέθηκε
167 |
168 | return VECTOR_EOF; // δεν υπάρχει
169 | }
--------------------------------------------------------------------------------
/modules/UsingHashTable/ADTMap.c:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Υλοποίηση του ADT Map μέσω Hash Table με open addressing (linear probing)
4 | //
5 | /////////////////////////////////////////////////////////////////////////////
6 |
7 | #include
8 |
9 | #include "ADTMap.h"
10 |
11 |
12 | // Οι κόμβοι του map στην υλοποίηση με hash table, μπορούν να είναι σε 3 διαφορετικές καταστάσεις,
13 | // ώστε αν διαγράψουμε κάποιον κόμβο, αυτός να μην είναι empty, ώστε να μην επηρεάζεται η αναζήτηση
14 | // αλλά ούτε occupied, ώστε η εισαγωγή να μπορεί να το κάνει overwrite.
15 | typedef enum {
16 | EMPTY, OCCUPIED, DELETED
17 | } State;
18 |
19 | // Το μέγεθος του Hash Table ιδανικά θέλουμε να είναι πρώτος αριθμός σύμφωνα με την θεωρία.
20 | // Η παρακάτω λίστα περιέχει πρώτους οι οποίοι έχουν αποδεδιγμένα καλή συμπεριφορά ως μεγέθη.
21 | // Κάθε re-hash θα γίνεται βάσει αυτής της λίστας. Αν χρειάζονται παραπάνω απο 1610612741 στοχεία, τότε σε καθε rehash διπλασιάζουμε το μέγεθος.
22 | int prime_sizes[] = {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241,
23 | 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741};
24 |
25 | // Χρησιμοποιούμε open addressing, οπότε σύμφωνα με την θεωρία, πρέπει πάντα να διατηρούμε
26 | // τον load factor του hash table μικρότερο ή ίσο του 0.5, για να έχουμε αποδoτικές πράξεις
27 | #define MAX_LOAD_FACTOR 0.5
28 |
29 | // Δομή του κάθε κόμβου που έχει το hash table (με το οποίο υλοιποιούμε το map)
30 | struct map_node{
31 | Pointer key; // Το κλειδί που χρησιμοποιείται για να hash-αρουμε
32 | Pointer value; // Η τιμή που αντισοιχίζεται στο παραπάνω κλειδί
33 | State state; // Μεταβλητή για να μαρκάρουμε την κατάσταση των κόμβων (βλέπε διαγραφή)
34 | };
35 |
36 | // Δομή του Map (περιέχει όλες τις πληροφορίες που χρεαζόμαστε για το HashTable)
37 | struct map {
38 | MapNode array; // Ο πίνακας που θα χρησιμοποιήσουμε για το map (remember, φτιάχνουμε ένα hash table)
39 | int capacity; // Πόσο χώρο έχουμε δεσμεύσει.
40 | int size; // Πόσα στοιχεία έχουμε προσθέσει
41 | int deleted; // Πόσα κελιά είναι DELETED
42 | CompareFunc compare; // Συνάρτηση για σύγκριση δεικτών, που πρέπει να δίνεται απο τον χρήστη
43 | HashFunc hash_function; // Συνάρτηση για να παίρνουμε το hash code του κάθε αντικειμένου.
44 | DestroyFunc destroy_key; // Συναρτήσεις που καλούνται όταν διαγράφουμε έναν κόμβο απο το map.
45 | DestroyFunc destroy_value;
46 | };
47 |
48 |
49 | Map map_create(CompareFunc compare, DestroyFunc destroy_key, DestroyFunc destroy_value) {
50 | // Δεσμεύουμε κατάλληλα τον χώρο που χρειαζόμαστε για το hash table
51 | Map map = malloc(sizeof(*map));
52 | map->capacity = prime_sizes[0];
53 | map->array = malloc(map->capacity * sizeof(struct map_node));
54 |
55 | // Αρχικοποιούμε τους κόμβους που έχουμε σαν διαθέσιμους.
56 | for (int i = 0; i < map->capacity; i++)
57 | map->array[i].state = EMPTY;
58 |
59 | map->size = 0;
60 | map->deleted = 0;
61 | map->compare = compare;
62 | map->destroy_key = destroy_key;
63 | map->destroy_value = destroy_value;
64 |
65 | return map;
66 | }
67 |
68 | // Επιστρέφει τον αριθμό των entries του map σε μία χρονική στιγμή.
69 | int map_size(Map map) {
70 | return map->size;
71 | }
72 |
73 | // Συνάρτηση για την επέκταση του Hash Table σε περίπτωση που ο load factor μεγαλώσει πολύ.
74 | static void rehash(Map map) {
75 | // Αποθήκευση των παλιών δεδομένων
76 | int old_capacity = map->capacity;
77 | MapNode old_array = map->array;
78 |
79 | // Βρίσκουμε τη νέα χωρητικότητα, διασχίζοντας τη λίστα των πρώτων ώστε να βρούμε τον επόμενο.
80 | int prime_no = sizeof(prime_sizes) / sizeof(int); // το μέγεθος του πίνακα
81 | for (int i = 0; i < prime_no; i++) { // LCOV_EXCL_LINE
82 | if (prime_sizes[i] > old_capacity) {
83 | map->capacity = prime_sizes[i];
84 | break;
85 | }
86 | }
87 | // Αν έχουμε εξαντλήσει όλους τους πρώτους, διπλασιάζουμε
88 | if (map->capacity == old_capacity) // LCOV_EXCL_LINE
89 | map->capacity *= 2; // LCOV_EXCL_LINE
90 |
91 | // Δημιουργούμε ένα μεγαλύτερο hash table
92 | map->array = malloc(map->capacity * sizeof(struct map_node));
93 | for (int i = 0; i < map->capacity; i++)
94 | map->array[i].state = EMPTY;
95 |
96 | // Τοποθετούμε ΜΟΝΟ τα entries που όντως περιέχουν ένα στοιχείο (το rehash είναι και μία ευκαιρία να ξεφορτωθούμε τα deleted nodes)
97 | map->size = 0;
98 | for (int i = 0; i < old_capacity; i++)
99 | if (old_array[i].state == OCCUPIED)
100 | map_insert(map, old_array[i].key, old_array[i].value);
101 |
102 | //Αποδεσμεύουμε τον παλιό πίνακα ώστε να μήν έχουμε leaks
103 | free(old_array);
104 | }
105 |
106 | // Εισαγωγή στο hash table του ζευγαριού (key, item). Αν το key υπάρχει,
107 | // ανανέωση του με ένα νέο value, και η συνάρτηση επιστρέφει true.
108 |
109 | void map_insert(Map map, Pointer key, Pointer value) {
110 | // Σκανάρουμε το Hash Table μέχρι να βρούμε διαθέσιμη θέση για να τοποθετήσουμε το ζευγάρι,
111 | // ή μέχρι να βρούμε το κλειδί ώστε να το αντικαταστήσουμε.
112 | bool already_in_map = false;
113 | MapNode node = NULL;
114 | uint pos;
115 | for (pos = map->hash_function(key) % map->capacity; // ξεκινώντας από τη θέση που κάνει hash το key
116 | map->array[pos].state != EMPTY; // αν φτάσουμε σε EMPTY σταματάμε
117 | pos = (pos + 1) % map->capacity) { // linear probing, γυρνώντας στην αρχή όταν φτάσουμε στη τέλος του πίνακα
118 |
119 | if (map->array[pos].state == DELETED) {
120 | // Βρήκαμε DELETED θέση. Θα μπορούσαμε να βάλουμε το ζευγάρι εδώ, αλλά _μόνο_ αν το key δεν υπάρχει ήδη.
121 | // Οπότε σημειώνουμε τη θέση, αλλά συνεχίζουμε την αναζήτηση, το key μπορεί να βρίσκεται πιο μετά.
122 | if (node == NULL)
123 | node = &map->array[pos];
124 |
125 | } else if (map->compare(map->array[pos].key, key) == 0) {
126 | already_in_map = true;
127 | node = &map->array[pos]; // βρήκαμε το key, το ζευγάρι θα μπει αναγκαστικά εδώ (ακόμα και αν είχαμε προηγουμένως βρει DELETED θέση)
128 | break; // και δε χρειάζεται να συνεχίζουμε την αναζήτηση.
129 | }
130 | }
131 | if (node == NULL) // αν βρήκαμε EMPTY (όχι DELETED, ούτε το key), το node δεν έχει πάρει ακόμα τιμή
132 | node = &map->array[pos];
133 |
134 | // Σε αυτό το σημείο, το node είναι ο κόμβος στον οποίο θα γίνει εισαγωγή.
135 | if (already_in_map) {
136 | // Αν αντικαθιστούμε παλιά key/value, τa κάνουμε destropy
137 | if (node->key != key && map->destroy_key != NULL)
138 | map->destroy_key(node->key);
139 |
140 | if (node->value != value && map->destroy_value != NULL)
141 | map->destroy_value(node->value);
142 |
143 | } else {
144 | // Νέο στοιχείο, αυξάνουμε τα συνολικά στοιχεία του map
145 | map->size++;
146 |
147 | if (node->state == DELETED) // αν βρήκαμε DELETED, θα αλλάξει σε OCCUPIED
148 | map->deleted--;
149 | }
150 |
151 | // Προσθήκη τιμών στον κόμβο
152 | node->state = OCCUPIED;
153 | node->key = key;
154 | node->value = value;
155 |
156 | // Αν με την νέα εισαγωγή ξεπερνάμε το μέγιστο load factor, πρέπει να κάνουμε rehash.
157 | // Στο load factor μετράμε και τα DELETED, γιατί και αυτά επηρρεάζουν τις αναζητήσεις.
158 | float load_factor = (float)(map->size + map->deleted) / map->capacity;
159 | if (load_factor > MAX_LOAD_FACTOR)
160 | rehash(map);
161 | }
162 |
163 | // Διαργραφή απο το Hash Table του κλειδιού με τιμή key
164 | bool map_remove(Map map, Pointer key) {
165 | MapNode node = map_find_node(map, key);
166 | if (node == MAP_EOF)
167 | return false;
168 |
169 | // destroy
170 | if (map->destroy_key != NULL)
171 | map->destroy_key(node->key);
172 | if (map->destroy_value != NULL)
173 | map->destroy_value(node->value);
174 |
175 | // θέτουμε ως "deleted", ώστε να μην διακόπτεται η αναζήτηση, αλλά ταυτόχρονα να γίνεται ομαλά η εισαγωγή
176 | node->state = DELETED;
177 | map->deleted++;
178 | map->size--;
179 |
180 | return true;
181 | }
182 |
183 | // Αναζήτηση στο map, με σκοπό να επιστραφεί το value του κλειδιού που περνάμε σαν όρισμα.
184 |
185 | Pointer map_find(Map map, Pointer key) {
186 | MapNode node = map_find_node(map, key);
187 | if (node != MAP_EOF)
188 | return node->value;
189 | else
190 | return NULL;
191 | }
192 |
193 |
194 | DestroyFunc map_set_destroy_key(Map map, DestroyFunc destroy_key) {
195 | DestroyFunc old = map->destroy_key;
196 | map->destroy_key = destroy_key;
197 | return old;
198 | }
199 |
200 | DestroyFunc map_set_destroy_value(Map map, DestroyFunc destroy_value) {
201 | DestroyFunc old = map->destroy_value;
202 | map->destroy_value = destroy_value;
203 | return old;
204 | }
205 |
206 | // Απελευθέρωση μνήμης που δεσμεύει το map
207 | void map_destroy(Map map) {
208 | for (int i = 0; i < map->capacity; i++) {
209 | if (map->array[i].state == OCCUPIED) {
210 | if (map->destroy_key != NULL)
211 | map->destroy_key(map->array[i].key);
212 | if (map->destroy_value != NULL)
213 | map->destroy_value(map->array[i].value);
214 | }
215 | }
216 |
217 | free(map->array);
218 | free(map);
219 | }
220 |
221 | /////////////////////// Διάσχιση του map μέσω κόμβων ///////////////////////////
222 |
223 | MapNode map_first(Map map) {
224 | //Ξεκινάμε την επανάληψή μας απο το 1ο στοιχείο, μέχρι να βρούμε κάτι όντως τοποθετημένο
225 | for (int i = 0; i < map->capacity; i++)
226 | if (map->array[i].state == OCCUPIED)
227 | return &map->array[i];
228 |
229 | return MAP_EOF;
230 | }
231 |
232 | MapNode map_next(Map map, MapNode node) {
233 | // Το node είναι pointer στο i-οστό στοιχείο του array, οπότε node - array == i (pointer arithmetic!)
234 | for (int i = node - map->array + 1; i < map->capacity; i++)
235 | if (map->array[i].state == OCCUPIED)
236 | return &map->array[i];
237 |
238 | return MAP_EOF;
239 | }
240 |
241 | Pointer map_node_key(Map map, MapNode node) {
242 | return node->key;
243 | }
244 |
245 | Pointer map_node_value(Map map, MapNode node) {
246 | return node->value;
247 | }
248 |
249 | MapNode map_find_node(Map map, Pointer key) {
250 | // Διασχίζουμε τον πίνακα, ξεκινώντας από τη θέση που κάνει hash το key, και για όσο δε βρίσκουμε EMPTY
251 | for (uint pos = map->hash_function(key) % map->capacity; // ξεκινώντας από τη θέση που κάνει hash το key
252 | map->array[pos].state != EMPTY; // αν φτάσουμε σε EMPTY σταματάμε
253 | pos = (pos + 1) % map->capacity) { // linear probing, γυρνώντας στην αρχή όταν φτάσουμε στη τέλος του πίνακα
254 |
255 | // Μόνο σε OCCUPIED θέσεις (όχι DELETED), ελέγχουμε αν το key είναι εδώ
256 | if (map->array[pos].state == OCCUPIED && map->compare(map->array[pos].key, key) == 0)
257 | return &map->array[pos];
258 | }
259 |
260 | return MAP_EOF;
261 | }
262 |
263 | // Αρχικοποίηση της συνάρτησης κατακερματισμού του συγκεκριμένου map.
264 | void map_set_hash_function(Map map, HashFunc func) {
265 | map->hash_function = func;
266 | }
267 |
268 | uint hash_string(Pointer value) {
269 | // djb2 hash function, απλή, γρήγορη, και σε γενικές γραμμές αποδοτική
270 | uint hash = 5381;
271 | for (char* s = value; *s != '\0'; s++)
272 | hash = (hash << 5) + hash + *s; // hash = (hash * 33) + *s. Το foo << 5 είναι γρηγορότερη εκδοχή του foo * 32.
273 | return hash;
274 | }
275 |
276 | uint hash_int(Pointer value) {
277 | return *(int*)value;
278 | }
279 |
280 | uint hash_pointer(Pointer value) {
281 | return (size_t)value; // cast σε sizt_t, που έχει το ίδιο μήκος με έναν pointer
282 | }
--------------------------------------------------------------------------------
/modules/UsingHeap/ADTPriorityQueue.c:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////
2 | //
3 | // Υλοποίηση του ADT Priority Queue μέσω σωρού.
4 | //
5 | ///////////////////////////////////////////////////////////
6 |
7 | #include
8 | #include
9 |
10 | #include "ADTPriorityQueue.h"
11 | #include "ADTVector.h" // Η υλοποίηση του PriorityQueue χρησιμοποιεί Vector
12 |
13 | // Ενα PriorityQueue είναι pointer σε αυτό το struct
14 | struct priority_queue {
15 | Vector values; // Τα δεδομένα, σε Vector ώστε να έχουμε μεταβλητό μέγεθος χωρίς κόπο
16 | CompareFunc compare; // Η διάταξη
17 | DestroyFunc destroy_value; // Συνάρτηση που καταστρέφει ένα στοιχείο του vector.
18 | };
19 |
20 |
21 | // Βοηθητικές συναρτήσεις ////////////////////////////////////////////////////////////////////////////
22 |
23 | // Προσοχή: στην αναπαράσταση ενός complete binary tree με πίνακα, είναι βολικό τα ids των κόμβων να
24 | // ξεκινάνε από το 1 (ρίζα), το οποίο απλοποιεί τις φόρμουλες για εύρεση πατέρα/παιδιών. Οι θέσεις
25 | // ενός vector όμως ξεκινάνε από το 0. Θα μπορούσαμε απλά να αφήσουμε μία θέση κενή, αλλά δεν είναι ανάγκη,
26 | // μπορούμε απλά να αφαιρούμε 1 όταν διαβάζουμε/γράφουμε στο vector. Για απλοποίηση του κώδικα, η
27 | // πρόσβαση στα στοιχεία του vector γίνεται από τις παρακάτω 2 βοηθητικές συναρτήσεις.
28 |
29 | // Επιστρέφει την τιμή του κόμβου node_id
30 |
31 | static Pointer node_value(PriorityQueue pqueue, int node_id) {
32 | // τα node_ids είναι 1-based, το node_id αποθηκεύεται στη θέση node_id - 1
33 | return vector_get_at(pqueue->values, node_id - 1);
34 | }
35 |
36 | // Ανταλλάσει τις τιμές των κόμβων node_id1 και node_id2
37 |
38 | static void node_swap(PriorityQueue pqueue, int node_id1, int node_id2) {
39 | // τα node_ids είναι 1-based, το node_id αποθηκεύεται στη θέση node_id - 1
40 | Pointer value1 = node_value(pqueue, node_id1);
41 | Pointer value2 = node_value(pqueue, node_id2);
42 |
43 | vector_set_at(pqueue->values, node_id1 - 1, value2);
44 | vector_set_at(pqueue->values, node_id2 - 1, value1);
45 | }
46 |
47 | // Αποκαθιστά την ιδιότητα του σωρού.
48 | // Πριν: όλοι οι κόμβοι ικανοποιούν την ιδιότητα του σωρού, εκτός από
49 | // τον node_id που μπορεί να είναι _μεγαλύτερος_ από τον πατέρα του.
50 | // Μετά: όλοι οι κόμβοι ικανοποιούν την ιδιότητα του σωρού.
51 |
52 | static void bubble_up(PriorityQueue pqueue, int node_id) {
53 | // Αν φτάσαμε στη ρίζα, σταματάμε
54 | if (node_id == 1)
55 | return;
56 |
57 | int parent = node_id / 2; // Ο πατέρας του κόμβου. Τα node_ids είναι 1-based
58 |
59 | // Αν ο πατέρας έχει μικρότερη τιμή από τον κόμβο, swap και συνεχίζουμε αναδρομικά προς τα πάνω
60 | if (pqueue->compare(node_value(pqueue, parent), node_value(pqueue, node_id)) < 0) {
61 | node_swap(pqueue, parent, node_id);
62 | bubble_up(pqueue, parent);
63 | }
64 | }
65 |
66 | // Αποκαθιστά την ιδιότητα του σωρού.
67 | // Πριν: όλοι οι κόμβοι ικανοποιούν την ιδιότητα του σωρού, εκτός από τον
68 | // node_id που μπορεί να είναι _μικρότερος_ από κάποιο από τα παιδιά του.
69 | // Μετά: όλοι οι κόμβοι ικανοποιούν την ιδιότητα του σωρού.
70 |
71 | static void bubble_down(PriorityQueue pqueue, int node_id) {
72 | // βρίσκουμε τα παιδιά του κόμβου (αν δεν υπάρχουν σταματάμε)
73 | int left_child = 2 * node_id;
74 | int right_child = left_child + 1;
75 |
76 | int size = pqueue_size(pqueue);
77 | if (left_child > size)
78 | return;
79 |
80 | // βρίσκουμε το μέγιστο από τα 2 παιδιά
81 | int max_child = left_child;
82 | if (right_child <= size && pqueue->compare(node_value(pqueue, left_child), node_value(pqueue, right_child)) < 0)
83 | max_child = right_child;
84 |
85 | // Αν ο κόμβος είναι μικρότερος από το μέγιστο παιδί, swap και συνεχίζουμε προς τα κάτω
86 | if (pqueue->compare(node_value(pqueue, node_id), node_value(pqueue, max_child)) < 0) {
87 | node_swap(pqueue, node_id, max_child);
88 | bubble_down(pqueue, max_child);
89 | }
90 | }
91 |
92 | // Αρχικοποιεί το σωρό από τα στοιχεία του vector values.
93 |
94 | static void naive_heapify(PriorityQueue pqueue, Vector values) {
95 | // Απλά κάνουμε insert τα στοιχεία ένα ένα.
96 | // TODO: υπάρχει πιο αποδοτικός τρόπος να γίνει αυτό!
97 | int size = vector_size(values);
98 | for (int i = 0; i < size; i++)
99 | pqueue_insert(pqueue, vector_get_at(values, i));
100 | }
101 |
102 |
103 | // Συναρτήσεις του ADTPriorityQueue //////////////////////////////////////////////////
104 |
105 | PriorityQueue pqueue_create(CompareFunc compare, DestroyFunc destroy_value, Vector values) {
106 | assert(compare != NULL); // LCOV_EXCL_LINE
107 |
108 | PriorityQueue pqueue = malloc(sizeof(*pqueue));
109 | pqueue->compare = compare;
110 | pqueue->destroy_value = destroy_value;
111 |
112 | // Δημιουργία του vector που αποθηκεύει τα στοιχεία.
113 | // ΠΡΟΣΟΧΗ: ΔΕΝ περνάμε την destroy_value στο vector!
114 | // Αν την περάσουμε θα καλείται όταν κάνουμε swap 2 στοιχεία, το οποίο δεν το επιθυμούμε.
115 | pqueue->values = vector_create(0, NULL);
116 |
117 | // Αν values != NULL, αρχικοποιούμε το σωρό.
118 | if (values != NULL)
119 | naive_heapify(pqueue, values);
120 |
121 | return pqueue;
122 | }
123 |
124 | int pqueue_size(PriorityQueue pqueue) {
125 | return vector_size(pqueue->values);
126 | }
127 |
128 | Pointer pqueue_max(PriorityQueue pqueue) {
129 | return node_value(pqueue, 1); // root
130 | }
131 |
132 | void pqueue_insert(PriorityQueue pqueue, Pointer value) {
133 | // Προσθέτουμε την τιμή στο τέλος το σωρού
134 | vector_insert_last(pqueue->values, value);
135 |
136 | // Ολοι οι κόμβοι ικανοποιούν την ιδιότητα του σωρού εκτός από τον τελευταίο, που μπορεί να είναι
137 | // μεγαλύτερος από τον πατέρα του. Αρα μπορούμε να επαναφέρουμε την ιδιότητα του σωρού καλώντας
138 | // τη bubble_up γα τον τελευταίο κόμβο (του οποίου το 1-based id ισούται με το νέο μέγεθος του σωρού).
139 | bubble_up(pqueue, pqueue_size(pqueue));
140 | }
141 |
142 | void pqueue_remove_max(PriorityQueue pqueue) {
143 | int last_node = pqueue_size(pqueue);
144 | assert(last_node != 0); // LCOV_EXCL_LINE
145 |
146 | // Destroy την τιμή που αφαιρείται
147 | if (pqueue->destroy_value != NULL)
148 | pqueue->destroy_value(pqueue_max(pqueue));
149 |
150 | // Αντικαθιστούμε τον πρώτο κόμβο με τον τελευταίο και αφαιρούμε τον τελευταίο
151 | node_swap(pqueue, 1, last_node);
152 | vector_remove_last(pqueue->values);
153 |
154 | // Ολοι οι κόμβοι ικανοποιούν την ιδιότητα του σωρού εκτός από τη νέα ρίζα
155 | // που μπορεί να είναι μικρότερη από κάποιο παιδί της. Αρα μπορούμε να
156 | // επαναφέρουμε την ιδιότητα του σωρού καλώντας τη bubble_down για τη ρίζα.
157 | bubble_down(pqueue, 1);
158 | }
159 |
160 | DestroyFunc pqueue_set_destroy_value(PriorityQueue pqueue, DestroyFunc destroy_value) {
161 | DestroyFunc old = pqueue->destroy_value;
162 | pqueue->destroy_value = destroy_value;
163 | return old;
164 | }
165 |
166 | void pqueue_destroy(PriorityQueue pqueue) {
167 | // Αντί να κάνουμε εμείς destroy τα στοιχεία, είναι απλούστερο να
168 | // προσθέσουμε τη destroy_value στο vector ώστε να κληθεί κατά το vector_destroy.
169 | vector_set_destroy_value(pqueue->values, pqueue->destroy_value);
170 | vector_destroy(pqueue->values);
171 |
172 | free(pqueue);
173 | }
--------------------------------------------------------------------------------
/modules/UsingLinkedList/ADTList.c:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////
2 | //
3 | // Υλοποίηση του ADT List μέσω συνδεδεμένης λίστας.
4 | //
5 | ///////////////////////////////////////////////////////////
6 |
7 | #include
8 | #include
9 |
10 | #include "ADTList.h"
11 |
12 |
13 | // Ενα List είναι pointer σε αυτό το struct
14 | struct list {
15 | ListNode dummy; // χρησιμοποιούμε dummy κόμβο, ώστε ακόμα και η κενή λίστα να έχει έναν κόμβο.
16 | ListNode last; // δείκτης στον τελευταίο κόμβο, ή στον dummy (αν η λίστα είναι κενή)
17 | int size; // μέγεθος, ώστε η list_size να είναι Ο(1)
18 | DestroyFunc destroy_value; // Συνάρτηση που καταστρέφει ένα στοιχείο της λίστας.
19 | };
20 |
21 | struct list_node {
22 | ListNode next; // Δείκτης στον επόμενο
23 | Pointer value; // Η τιμή που αποθηκεύουμε στον κόμβο
24 | };
25 |
26 |
27 | List list_create(DestroyFunc destroy_value) {
28 | // Πρώτα δημιουργούμε το stuct
29 | List list = malloc(sizeof(*list));
30 | list->size = 0;
31 | list->destroy_value = destroy_value;
32 |
33 | // Χρησιμοποιούμε dummy κόμβο, ώστε ακόμα και μια άδεια λίστα να έχει ένα κόμβο
34 | // (απλοποιεί τους αλγορίθμους). Οπότε πρέπει να τον δημιουργήσουμε.
35 | //
36 | list->dummy = malloc(sizeof(*list->dummy));
37 | list->dummy->next = NULL; // άδεια λίστα, ο dummy δεν έχει επόμενο
38 |
39 | // Σε μια κενή λίστα, τελευταίος κόμβος είναι επίσης ο dummy
40 | list->last = list->dummy;
41 |
42 | return list;
43 | }
44 |
45 | int list_size(List list) {
46 | return list->size;
47 | }
48 |
49 | void list_insert_next(List list, ListNode node, Pointer value) {
50 | // Αν το node είναι NULL απλά εισάγουμε μετά τον dummy κόμβο!
51 | // Αυτή ακριβώς είναι η αξία του dummy, δε χρειαζόμαστε ξεχωριστή υλοποίηση.
52 | if (node == NULL)
53 | node = list->dummy;
54 |
55 | // Δημιουργία του νέου κόμβου
56 | ListNode new = malloc(sizeof(*new));
57 | new->value = value;
58 |
59 | // Σύνδεση του new ανάμεσα στο node και το node->next
60 | new->next = node->next;
61 | node->next = new;
62 |
63 | // Ενημέρωση των size & last
64 | list->size++;
65 | if (list->last == node)
66 | list->last = new;
67 | }
68 |
69 | void list_remove_next(List list, ListNode node) {
70 | // Αν το node είναι NULL απλά διαγράφουμε μετά τον dummy κόμβο!
71 | // Αυτή ακριβώς είναι η αξία του dummy, δε χρειαζόμαστε ξεχωριστή υλοποίηση.
72 | if (node == NULL)
73 | node = list->dummy;
74 |
75 | // Ο κόμβος προς διαγραφή είναι ο επόμενος του node, ο οποίος πρέπει να υπάρχει
76 | ListNode removed = node->next;
77 | assert(removed != NULL); // LCOV_EXCL_LINE
78 |
79 | if (list->destroy_value != NULL)
80 | list->destroy_value(removed->value);
81 |
82 | // Σύνδεση του node με τον επόμενο του removed
83 | node->next = removed->next; // πριν το free!
84 |
85 | free(removed);
86 |
87 | // Ενημέρωση των size & last
88 | list->size--;
89 | if (list->last == removed)
90 | list->last = node;
91 | }
92 |
93 | Pointer list_find(List list, Pointer value, CompareFunc compare) {
94 | ListNode node = list_find_node(list, value, compare);
95 | return node == NULL ? NULL : node->value;
96 | }
97 |
98 | DestroyFunc list_set_destroy_value(List list, DestroyFunc destroy_value) {
99 | DestroyFunc old = list->destroy_value;
100 | list->destroy_value = destroy_value;
101 | return old;
102 | }
103 |
104 | void list_destroy(List list) {
105 | // Διασχίζουμε όλη τη λίστα και κάνουμε free όλους τους κόμβους,
106 | // συμπεριλαμβανομένου και του dummy!
107 | //
108 | ListNode node = list->dummy;
109 | while (node != NULL) { // while αντί για for, γιατί θέλουμε να διαβάσουμε
110 | ListNode next = node->next; // το node->next _πριν_ κάνουμε free!
111 |
112 | // Καλούμε τη destroy_value, αν υπάρχει (προσοχή, όχι στον dummy!)
113 | if (node != list->dummy && list->destroy_value != NULL)
114 | list->destroy_value(node->value);
115 |
116 | free(node);
117 | node = next;
118 | }
119 |
120 | // Τέλος free το ίδιο το struct
121 | free(list);
122 | }
123 |
124 |
125 | // Διάσχιση της λίστας /////////////////////////////////////////////
126 |
127 | ListNode list_first(List list) {
128 | // Ο πρώτος κόμβος είναι ο επόμενος του dummy.
129 | //
130 | return list->dummy->next;
131 | }
132 |
133 | ListNode list_last(List list) {
134 | // Προσοχή, αν η λίστα είναι κενή το last δείχνει στον dummy, εμείς όμως θέλουμε να επιστρέψουμε NULL, όχι τον dummy!
135 | //
136 | if (list->last == list->dummy)
137 | return LIST_EOF; // κενή λίστα
138 | else
139 | return list->last;
140 | }
141 |
142 | ListNode list_next(List list, ListNode node) {
143 | assert(node != NULL); // LCOV_EXCL_LINE (αγνοούμε το branch από τα coverage reports, είναι δύσκολο να τεστάρουμε το false γιατί θα κρασάρει το test)
144 | return node->next;
145 | }
146 |
147 | Pointer list_node_value(List list, ListNode node) {
148 | assert(node != NULL); // LCOV_EXCL_LINE
149 | return node->value;
150 | }
151 |
152 | ListNode list_find_node(List list, Pointer value, CompareFunc compare) {
153 | // διάσχιση όλης της λίστας, καλούμε την compare μέχρι να επιστρέψει 0
154 | //
155 | for (ListNode node = list->dummy->next; node != NULL; node = node->next)
156 | if (compare(value, node->value) == 0)
157 | return node; // βρέθηκε
158 |
159 | return NULL; // δεν υπάρχει
160 | }
--------------------------------------------------------------------------------
/programs/cat/Makefile:
--------------------------------------------------------------------------------
1 | # Λίστα με objects (.o) για κάθε εκτελέσιμο που θέλουμε να φτιάξουμε.
2 | #
3 | # Για το ADTVector χρησιμοποιούμε την υλοποίηση με Dynamic Array
4 | #
5 | cat_OBJS = cat.o io.o $(MODULES)/UsingDynamicArray/ADTVector.o
6 | io_test_OBJS = io_test.o io.o $(MODULES)/UsingDynamicArray/ADTVector.o
7 |
8 | # Ορίσματα που χρησιμοποιούνται από το make run
9 | cat_ARGS = input-file
10 |
11 | # Ο βασικός κορμός του Makefile
12 | include ../../common.mk
13 |
--------------------------------------------------------------------------------
/programs/cat/cat.c:
--------------------------------------------------------------------------------
1 | // Υλοποίηση της εντολής cat του unix.
2 | // cat [FILE1] [FILE2] ...
3 | //
4 | // Διαβάζει και τυπώνει τα περιεχόμενα του κάθε αρχείου που δίνεται ως είσοδος
5 | // (με τη διαφορά ότι διαβάζει ολόκληρο το αρχείο πριν αρχίσει να γράφει).
6 | // Αν δε δοθεί αρχείο, ή όταν το αρχείο είναι ίσο με "-", διαβάζει από το stdin.
7 |
8 | #include
9 |
10 | #include "io.h"
11 |
12 |
13 | void process_file(char* filename) {
14 | // Διαβάζουμε το αρχείο ή την είσοδο. Το io.h module κάνει όλη τη δουλειά.
15 | Vector vec = strcmp(filename, "-") == 0
16 | ? io_read_stream_as_vector(stdin)
17 | : io_read_file_as_vector(filename);
18 |
19 | if (vec == NULL) {
20 | fprintf(stderr, "cat: %s: cannot read file\n", filename);
21 | return;
22 | }
23 |
24 | io_write_vector_to_stream(stdout, vec);
25 | vector_destroy(vec);
26 | }
27 |
28 | int main(int argc, char* argv[]) {
29 | for (int i = 1; i < argc; i++)
30 | process_file(argv[i]);
31 |
32 | // χωρίς κανένα αρχείο διαβάζουμε από την είσοδο
33 | if (argc == 1)
34 | process_file("-");
35 | }
36 |
--------------------------------------------------------------------------------
/programs/cat/input-file:
--------------------------------------------------------------------------------
1 | Going To California
2 | Robert Plant / Jimmy Page
3 | Album: Led Zeppelin IV
4 |
5 | Spent my days with a woman unkind, Smoked my stuff and drank all my wine.
6 | Made up my mind to make a new start, Going To California with an aching in my heart.
7 | Someone told me there's a girl out there with love in her eyes and flowers in her hair.
8 | Took my chances on a big jet plane, never let them tell you that they're all the same.
9 | The sea was red and the sky was grey, wondered how tomorrow could ever follow today.
10 | The mountains and the canyons started to tremble and shake
11 | as the children of the sun began to awake.
12 |
13 | Seems that the wrath of the Gods
14 | Got a punch on the nose and it started to flow;
15 | I think I might be sinking.
16 | Throw me a line if I reach it in time
17 | I'll meet you up there where the path
18 | Runs straight and high.
19 |
20 | To find a queen without a king,
21 | They say she plays guitar and cries and sings... la la la
22 | Ride a white mare in the footsteps of dawn
23 | Tryin' to find a woman who's never, never, never been born.
24 | Standing on a hill in my mountain of dreams,
25 | Telling myself it's not as hard, hard, hard as it seems.
26 |
--------------------------------------------------------------------------------
/programs/cat/io.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "io.h"
5 |
6 |
7 | Vector io_read_stream_as_vector(FILE* stream) {
8 | // TODO: θεωρούμε ότι κάθε γραμμή έχει max 500 χαρακτήρες. Πώς μπορούμε να αφαιρέσουμε αυτόν τον περιορισμό;
9 | int max_len = 500;
10 | char line[max_len];
11 |
12 | Vector vec = vector_create(0, free); // αυτόματο free για κάθε στοιχείο που αφαιρείται
13 |
14 | while (fgets(line, max_len, stream)) {
15 | // αφαίρεση του newline, αν υπάρχει
16 | char* newline = strchr(line, '\n');
17 | if (newline != NULL)
18 | *newline = '\0';
19 |
20 | // προσθήκη στο vector
21 | vector_insert_last(vec, strdup(line));
22 | }
23 |
24 | return vec;
25 | }
26 |
27 | Vector io_read_file_as_vector(char* filename) {
28 | // απλά ανοίγουμε το αρχείο και καλούμε την io_read_stream_as_vector
29 | FILE* file = fopen(filename, "r");
30 | if (file == NULL)
31 | return NULL;
32 |
33 | Vector vec = io_read_stream_as_vector(file);
34 | fclose(file);
35 |
36 | return vec;
37 | }
38 |
39 | int io_write_vector_to_stream(FILE* stream, Vector vec) {
40 | int written = 0;
41 | int size = vector_size(vec);
42 |
43 | // διασχίζουμε το vector και τυπώνουμε κάθε γραμμή
44 | for (int i = 0; i < size; i++) {
45 | char* line = vector_get_at(vec, i);
46 | written += fprintf(stream, "%s\n", line);
47 | }
48 |
49 | return written;
50 | }
51 |
52 | int io_write_vector_to_file(char* filename, Vector vec) {
53 | // απλά ανοίγουμε το αρχείο και καλούμε την io_write_vector_to_stream
54 | FILE* file = fopen(filename, "w");
55 | if (file == NULL)
56 | return 0;
57 |
58 | int written = io_write_vector_to_stream(file, vec);
59 | fclose(file);
60 |
61 | return written;
62 | }
63 |
--------------------------------------------------------------------------------
/programs/cat/io.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "ADTVector.h"
6 |
7 | // Module που διευκολύνει το input/output ολόκληρων αρχείων. Ενα τέτοιο module θα μπορούσε να είναι
8 | // κοινόχρηστο ώστε να χρησιμοποιείται από διαφορετικά προγράμματα ή άλλα modules.
9 |
10 | // Διαβάζει τα περιεχόμενα του stream και τα επιστρέφει ως ένα Vector που περιέχει
11 | // ένα στοιχείο για κάθε γραμμή του αρχείου (χωρίς την αλλαγή γραμμής "\n")
12 |
13 | Vector io_read_stream_as_vector(FILE* stream);
14 |
15 | // Διαβάζει τα περιεχόμενα του αρχείου filename και τα επιστρέφει ως ένα Vector που περιέχει
16 | // ένα στοιχείο για κάθε γραμμή του αρχείου (χωρίς την αλλαγή γραμμής "\n")
17 |
18 | Vector io_read_file_as_vector(char* filename);
19 |
20 | // Γράφει τα περιεχόμενα του string Vector vec στο stream, με αλλαγή γραμμής μετά από
21 | // κάθε στοιχείο. Επιστρέφει τον αριθμό των χαρακτήρων που γράφτηκαν.
22 |
23 | int io_write_vector_to_stream(FILE* stream, Vector vec);
24 |
25 | // Γράφει τα περιεχόμενα του string Vector vec στο αρχείο filename, με αλλαγή γραμμής
26 | // μετά από κάθε στοιχείο. Επιστρέφει τον αριθμό των χαρακτήρων που γράφτηκαν.
27 |
28 | int io_write_vector_to_file(char* filename, Vector vec);
29 |
--------------------------------------------------------------------------------
/programs/cat/io_test.c:
--------------------------------------------------------------------------------
1 | //
2 | // Unit tests για το io.h module
3 | //
4 |
5 | #include
6 | #include
7 |
8 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
9 |
10 | #include "io.h"
11 |
12 |
13 | void test_io_write_vector_to_file(void) {
14 | Vector vec = vector_create(0, NULL);
15 | vector_insert_last(vec, "foo");
16 | vector_insert_last(vec, "bar");
17 |
18 | // δημιουργία ενός προσωρινού αρχείου
19 | TEST_ASSERT(io_write_vector_to_file("io_tests_temp", vec) > 0);
20 |
21 | vector_destroy(vec);
22 | }
23 |
24 | void test_io_read_file_as_vector(void) {
25 | Vector vec = io_read_file_as_vector("io_tests_temp");
26 | TEST_ASSERT(vec != NULL);
27 |
28 | TEST_ASSERT(strcmp(vector_get_at(vec, 0), "foo") == 0);
29 | TEST_ASSERT(strcmp(vector_get_at(vec, 1), "bar") == 0);
30 |
31 | // διαγραφή του προσωρινού αρχείου
32 | remove("io_tests_temp");
33 |
34 | TEST_ASSERT(io_read_file_as_vector("a_file_that_doesnt_exist") == NULL);
35 |
36 | vector_destroy(vec);
37 | }
38 |
39 |
40 | // Λίστα με όλα τα tests προς εκτέλεση
41 | TEST_LIST = {
42 | { "io_write_vector_to_file", test_io_write_vector_to_file },
43 | { "io_read_file_as_vector", test_io_read_file_as_vector },
44 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
45 | };
--------------------------------------------------------------------------------
/programs/fibonacci/ADTIntVector.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "ADTIntVector.h"
4 |
5 | // Στο module αυτό υλοποιούμε ένα IntVector χρησιμοποιώντας μια **ήδη υπάρχουσα υλοποίηδη του Vector**.
6 | // Ολες οι int_vector_* συναρτήσεις είναι απλές και απλά καλούν τις αντίστοιχες συναρτήσεις vector_*.
7 | //
8 | // Για να προσθέσουμε ένα int value στο vector:
9 | // - δεσμεύουμε μνήμη με malloc
10 | // - αντιγράφουμε εκεί το value
11 | // - προσθέρουμε στο vector τον pointer στην μνήμη που δεσμεύσαμε.
12 | //
13 | // Για ευκολία χρησιμοποιούμε pointer cast, επεξήγηση υπάρχει στο modules/UsingADTList/ADTStack.h που χρησιμοποιεί την ίδια τεχνική.
14 |
15 |
16 | IntVector int_vector_create(int size) {
17 | // Περνάμε τη free στην vector_create, ώστε κάθε στοιχείο που αφαιρείται να γίνεται free αυτόματα
18 | Vector vec = vector_create(size, free);
19 |
20 | // Ο πίνακας περιέχει size μη-αρχικοποιημένους ακεραίους, δεσμεύουμε μνήμη για αυτούς
21 | for (int i = 0; i < size; i ++)
22 | vector_set_at(vec, i, malloc(sizeof(int)));
23 |
24 | return (IntVector)vec;
25 | }
26 |
27 | int int_vector_size(IntVector vec) {
28 | return vector_size((Vector)vec); // trivial
29 | }
30 |
31 | void int_vector_set_at(IntVector vec, int pos, int value) {
32 | // Αλλάζουμε την τιμή ενός υπάρχοντος στοιχείου, έχουμε ήδη μνήμη για αυτό! (από το αντίστοιχο insert)
33 | int *p = vector_get_at((Vector)vec, pos); // το vector περιέχει int pointers
34 | *p = value; // αλλαγή τιμής
35 | }
36 |
37 | int int_vector_get_at(IntVector vec, int pos) {
38 | return *(int*)vector_get_at((Vector)vec, pos); // το vector περιέχει int pointers
39 | }
40 |
41 | void int_vector_insert_last(IntVector vec, int value) {
42 | // Νέο στοιχείο, χρειαζόμαστε μνήμη!
43 | int *p = malloc(sizeof(int));
44 | *p = value;
45 | vector_insert_last((Vector)vec, p); // προσθήκη του int pointer στο vector
46 | }
47 |
48 | void int_vector_remove_last(IntVector vec) {
49 | vector_remove_last((Vector)vec);
50 | }
51 |
52 | int compare(Pointer a, Pointer b) {
53 | return *(int*)a - *(int*)b;
54 | }
55 |
56 | int int_vector_find(IntVector vec, int value) {
57 | int* res = vector_find((Vector)vec, &value, compare);
58 | return res == NULL ? INT_MIN : *res;
59 | }
60 |
61 | void int_vector_destroy(IntVector vec) {
62 | vector_destroy((Vector)vec);
63 | }
64 |
--------------------------------------------------------------------------------
/programs/fibonacci/ADTIntVector.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////
2 | //
3 | // ADT IntVector - vector από ακεραίους
4 | //
5 | // Απλό specialization του ADT Vector ώστε τα στοιχεία που περιέχει
6 | // να είναι ints.
7 | //
8 | ///////////////////////////////////////////////////////////////////
9 |
10 | #pragma once // #include το πολύ μία φορά
11 |
12 | #include
13 |
14 | #include "ADTVector.h"
15 |
16 | typedef struct int_vector* IntVector;
17 |
18 |
19 | // Οι συναρτήσεις είναι ολόιδιες με αυτές του ADTVector, documentation υπάρχει στο ADTVector.h
20 | //
21 | // Η μόνη διαφορά είναι ότι τα στοιχεία είναι τύπου int αντί για Pointer.
22 | // Η συνάρτηση vector_find επιστρέφει INT_MIN αν δεν βρεθεί το στοιχείο.
23 |
24 |
25 | IntVector int_vector_create(int size);
26 |
27 | int int_vector_size(IntVector vec);
28 |
29 | void int_vector_insert_last(IntVector vec, int value);
30 |
31 | void int_vector_remove_last(IntVector vec);
32 |
33 | void int_vector_set_at(IntVector vec, int pos, int value);
34 |
35 | int int_vector_get_at(IntVector vec, int pos);
36 |
37 | int int_vector_find(IntVector vec, int value);
38 |
39 | void int_vector_destroy(IntVector vec);
40 |
--------------------------------------------------------------------------------
/programs/fibonacci/ADTIntVector_test.c:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////
2 | //
3 | // Unit tests για τον ADT IntVector.
4 | // Οποιαδήποτε υλοποίηση οφείλει να περνάει όλα τα tests.
5 | //
6 | //////////////////////////////////////////////////////////////////
7 |
8 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
9 |
10 | #include "ADTIntVector.h"
11 |
12 |
13 | void test_create(void) {
14 | IntVector vec = int_vector_create(0);
15 |
16 | TEST_ASSERT(int_vector_size(vec) == 0);
17 |
18 | int_vector_destroy(vec);
19 | }
20 |
21 | void test_insert(void) {
22 | IntVector vec = int_vector_create(0);
23 |
24 | // insert 1000 στοιχεία ώστε να συμβούν πολλαπλά resizes
25 | for (int i = 0; i < 1000; i++) {
26 | int_vector_insert_last(vec, i);
27 | TEST_ASSERT(int_vector_size(vec) == i+1); // Το size ενημερώθηκε;
28 | TEST_ASSERT(int_vector_get_at(vec, i) == i); // Μπορούμε να κάνουμε at το στοιχείο που μόλις βάλαμε;
29 | }
30 |
31 | // Δοκιμή ότι μετά τα resizes τα στοιχεία είναι ακόμα προσπελάσιμα
32 | for (int i = 0; i < 1000; i++)
33 | TEST_ASSERT(int_vector_get_at(vec, i) == i);
34 |
35 | int_vector_destroy(vec);
36 | }
37 |
38 | void test_remove(void) {
39 | IntVector vec = int_vector_create(1000);
40 |
41 | // replace για προσθήκη δεδομένων, χωρίς ελέγχους (έχουμε ξεχωριστό test για το replace)
42 | for (int i = 0; i < 1000; i++)
43 | int_vector_set_at(vec, i, i);
44 |
45 | // Διαδοχικά remove ώστε να συμβούν και resizes
46 | for (int i = 999; i >= 0; i--) {
47 | TEST_ASSERT(int_vector_get_at(vec, i) == i);
48 | int_vector_remove_last(vec);
49 | TEST_ASSERT(int_vector_size(vec) == i);
50 | }
51 |
52 | int_vector_destroy(vec);
53 | }
54 |
55 | void test_get_set_at(void) {
56 | IntVector vec = int_vector_create(0);
57 |
58 | // insert πολλαπλά 0, θα τα αλλάξουμε μετά με replace
59 | for (int i = 0; i < 1000; i++)
60 | int_vector_insert_last(vec, 0);
61 |
62 | for (int i = 0; i < 1000; i++) {
63 | TEST_ASSERT(int_vector_get_at(vec, i) == 0);
64 | int_vector_set_at(vec, i, i);
65 | TEST_ASSERT(int_vector_get_at(vec, i) == i);
66 | }
67 |
68 | int_vector_destroy(vec);
69 | }
70 |
71 | void test_find(void) {
72 | IntVector vec = int_vector_create(1000);
73 |
74 | // replace για προσθήκη δεδομένων
75 | for (int i = 0; i < 1000; i++)
76 | int_vector_set_at(vec, i, i);
77 |
78 | for (int i = 0; i < 1000; i++) {
79 | int p = int_vector_find(vec, i);
80 | TEST_ASSERT(p == i);
81 | }
82 |
83 | TEST_ASSERT(int_vector_find(vec, -12) == INT_MIN);
84 |
85 | int_vector_destroy(vec);
86 | }
87 |
88 | void test_destroy(void) {
89 | // Απλά εκτελούμε την destroy, για να ελέγξουμε αν όντως δουλεύει
90 | // σωστά τρέχουμε το test με valgrind.
91 |
92 | IntVector vec = int_vector_create(1);
93 |
94 | int_vector_set_at(vec, 0, 1);
95 | int_vector_insert_last(vec, 1);
96 | int_vector_remove_last(vec);
97 |
98 | int_vector_destroy(vec);
99 | }
100 |
101 |
102 | // Λίστα με όλα τα tests προς εκτέλεση
103 | TEST_LIST = {
104 | { "create", test_create },
105 | { "insert", test_insert },
106 | { "remove", test_remove },
107 | { "get_set_at", test_get_set_at },
108 | { "find", test_find },
109 | { "destroy", test_destroy },
110 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
111 | };
--------------------------------------------------------------------------------
/programs/fibonacci/Makefile:
--------------------------------------------------------------------------------
1 | # Λίστα με objects (.o) για κάθε εκτελέσιμο που θέλουμε να φτιάξουμε.
2 | #
3 | # Χρειαζόμαστε την υλοποίηση του ADTIntVector και του ADTVector
4 | #
5 | fibonacci_test_OBJS = fibonacci_test.o fibonacci.o ADTIntVector.o $(MODULES)/UsingDynamicArray/ADTVector.o
6 |
7 | ADTIntVector_test_OBJS = ADTIntVector_test.o ADTIntVector.o $(MODULES)/UsingDynamicArray/ADTVector.o
8 |
9 | # Ο βασικός κορμός του Makefile
10 | include ../../common.mk
11 |
--------------------------------------------------------------------------------
/programs/fibonacci/fibonacci.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | // Χρησιμοποιούμε ένα specialization του ADTVector που παρέχει Vectors από ακεραίους
4 | #include "ADTIntVector.h"
5 |
6 | IntVector memory = NULL; // int vector για την αποθήκευση των στοιχείων
7 |
8 | // Αποδοτική αναδρομική υλοποίηση της ακολουθίας fibonacci
9 | // απομνημονεύοντας στοιχεία που έχουμε ήδη υπολογίσει.
10 |
11 | int fibonacci(int n) {
12 | if (memory == NULL) {
13 | // Αρχικοποίηση της μνήμης με τα πρώτα 2 στοιχεία της ακολουθίας
14 | memory = int_vector_create(0);
15 | int_vector_insert_last(memory, 0);
16 | int_vector_insert_last(memory, 1);
17 | }
18 |
19 | // Αν δεν έχουμε ήδη το αποτέλεσμα το υπολογίζουμε και το αποθηκεύουμε
20 | if (n >= int_vector_size(memory)) {
21 | int res = fibonacci(n-2) + fibonacci(n-1);
22 | int_vector_insert_last(memory, res);
23 | }
24 |
25 | return int_vector_get_at(memory, n);
26 | }
27 |
28 | void fibonacci_destroy(void) {
29 | int_vector_destroy(memory);
30 | }
--------------------------------------------------------------------------------
/programs/fibonacci/fibonacci.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // Επιστρέφει τον n-οστό όρο της ακολουθίας fibonacci
4 |
5 | int fibonacci(int n);
6 |
7 | // Απελευθερώνει τη μνήμη που έχει δεσμευτεί για την υλοποίηση της fibonacci.
8 |
9 | int fibonacci_destroy();
--------------------------------------------------------------------------------
/programs/fibonacci/fibonacci_test.c:
--------------------------------------------------------------------------------
1 | //
2 | // Unit tests για το fibonacci.h module
3 | //
4 |
5 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
6 | #include "fibonacci.h"
7 |
8 |
9 | char* lateralus[] = {
10 | "black",
11 | "then",
12 | "white are",
13 | "all I see",
14 | "in my in-fan-cy",
15 | "red and yel-low then came to be",
16 | "reach-ing out to me",
17 | "lets me see",
18 |
19 | "as be-low so a-bove and be-yond I i-mag-ine",
20 | "drawn be-yond the lines of rea-son",
21 | "push the en-ve-lope",
22 | "watch it bend",
23 |
24 | // over thinking, over analyzing separates the body from the mind
25 | // withering my intuition, missing opportunities and I must
26 | // feed my will to feel my moment drawing way outside the line
27 |
28 | "black",
29 | "then",
30 | "white are",
31 | "all I see",
32 | "in my in-fan-cy",
33 | "red and yel-low then came to be",
34 | "reach-ing out to me",
35 | "lets me see",
36 |
37 | "there is",
38 | "so",
39 | "much",
40 | "more and",
41 | "beck-ons me",
42 | "to look through to these",
43 | "in-fi-nite pos-si-bil-i-ties",
44 |
45 | "as be-low so a-bove and be-yond I i-mag-ine",
46 | "drawn be-yond the lines of rea-son",
47 | "push the en-ve-lope",
48 | "watch it bend"
49 | };
50 |
51 | // Επιστρέφει τον αριθμό των συλλαβών στο s (σε κάθε λέξη πρέπει να χωρίζονται με '-')
52 | int syllables_no(char* s) {
53 | int num = 1;
54 | for (; *s != '\0'; s++)
55 | if (*s == ' ' || *s == '-')
56 | num++;
57 |
58 | return num;
59 | }
60 |
61 | void test_fibonacci(void) {
62 | // genius
63 | TEST_ASSERT(syllables_no(lateralus[ 0]) == fibonacci(1));
64 | TEST_ASSERT(syllables_no(lateralus[ 1]) == fibonacci(2));
65 | TEST_ASSERT(syllables_no(lateralus[ 2]) == fibonacci(3));
66 | TEST_ASSERT(syllables_no(lateralus[ 3]) == fibonacci(4));
67 | TEST_ASSERT(syllables_no(lateralus[ 4]) == fibonacci(5));
68 | TEST_ASSERT(syllables_no(lateralus[ 5]) == fibonacci(6));
69 | TEST_ASSERT(syllables_no(lateralus[ 6]) == fibonacci(5));
70 | TEST_ASSERT(syllables_no(lateralus[ 7]) == fibonacci(4));
71 |
72 | TEST_ASSERT(syllables_no(lateralus[ 8]) == fibonacci(7));
73 | TEST_ASSERT(syllables_no(lateralus[ 9]) == fibonacci(6));
74 | TEST_ASSERT(syllables_no(lateralus[10]) == fibonacci(5));
75 | TEST_ASSERT(syllables_no(lateralus[11]) == fibonacci(4));
76 |
77 | TEST_ASSERT(syllables_no(lateralus[12]) == fibonacci(1));
78 | TEST_ASSERT(syllables_no(lateralus[13]) == fibonacci(2));
79 | TEST_ASSERT(syllables_no(lateralus[14]) == fibonacci(3));
80 | TEST_ASSERT(syllables_no(lateralus[15]) == fibonacci(4));
81 | TEST_ASSERT(syllables_no(lateralus[16]) == fibonacci(5));
82 | TEST_ASSERT(syllables_no(lateralus[17]) == fibonacci(6));
83 | TEST_ASSERT(syllables_no(lateralus[18]) == fibonacci(5));
84 | TEST_ASSERT(syllables_no(lateralus[19]) == fibonacci(4));
85 | TEST_ASSERT(syllables_no(lateralus[20]) == fibonacci(3));
86 | TEST_ASSERT(syllables_no(lateralus[21]) == fibonacci(2));
87 |
88 | TEST_ASSERT(syllables_no(lateralus[22]) == fibonacci(2));
89 | TEST_ASSERT(syllables_no(lateralus[23]) == fibonacci(3));
90 | TEST_ASSERT(syllables_no(lateralus[24]) == fibonacci(4));
91 | TEST_ASSERT(syllables_no(lateralus[25]) == fibonacci(5));
92 | TEST_ASSERT(syllables_no(lateralus[26]) == fibonacci(6));
93 |
94 | TEST_ASSERT(syllables_no(lateralus[27]) == fibonacci(7));
95 | TEST_ASSERT(syllables_no(lateralus[28]) == fibonacci(6));
96 | TEST_ASSERT(syllables_no(lateralus[29]) == fibonacci(5));
97 | TEST_ASSERT(syllables_no(lateralus[30]) == fibonacci(4));
98 |
99 | // Και κάποια μεγάλα
100 | TEST_ASSERT(fibonacci(20) == 6765);
101 | TEST_ASSERT(fibonacci(30) == 832040);
102 | TEST_ASSERT(fibonacci(45) == 1134903170);
103 |
104 | fibonacci_destroy();
105 | }
106 |
107 | // Λίστα με όλα τα tests προς εκτέλεση
108 | TEST_LIST = {
109 | { "fibonacci", test_fibonacci },
110 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
111 | };
--------------------------------------------------------------------------------
/programs/pair_sum/Makefile:
--------------------------------------------------------------------------------
1 | # Λίστα με objects (.o) για κάθε εκτελέσιμο που θέλουμε να φτιάξουμε.
2 | #
3 | # Χρειαζόμαστε υλοποιήσεις των ADTVector και ADTMap, αλλά και του ADTSet που χρησιμοποιείται από την υλοποίηση του ADTMap
4 | #
5 | pair_sum_test_OBJS = pair_sum_test.o pair_sum.o $(MODULES)/UsingDynamicArray/ADTVector.o $(MODULES)/UsingADTSet/ADTMap.o $(MODULES)/UsingAVL/ADTSet.o
6 |
7 | # Ο βασικός κορμός του Makefile
8 | include ../../common.mk
9 |
--------------------------------------------------------------------------------
/programs/pair_sum/pair_sum.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "ADTMap.h"
4 | #include "pair_sum.h"
5 |
6 |
7 | int compare_ints(int* a, int* b) {
8 | return *a - *b;
9 | }
10 |
11 | bool pair_sum(int target, Vector numbers, int* res_a, int* res_b) {
12 | // Στο map θα αποθηκεύουμε τα στοιχεία του vector. Δεν κάνουμε malloc
13 | // οπότε δε θέλουμε και free, εμείς αποθηκεύουμε στοιχεία που έχουν δημιουργηθεί αλλού!
14 | Map seen = map_create((CompareFunc)compare_ints, NULL, NULL);
15 | bool result = false;
16 |
17 | int size = vector_size(numbers);
18 | for (int i = 0; i < size; i++) {
19 | int* a = vector_get_at(numbers, i);
20 | int b = target - *a;
21 |
22 | if (map_find(seen, &b) != NULL) {
23 | *res_a = *a;
24 | *res_b = b;
25 | result = true;
26 | break;
27 | }
28 |
29 | map_insert(seen, a, a); // Για value θέλουμε μια οποιαδήποτε μη-NULL τιμή, οπότε το θέτουμε και αυτό ίσο με a
30 | }
31 |
32 | map_destroy(seen);
33 | return result;
34 | }
--------------------------------------------------------------------------------
/programs/pair_sum/pair_sum.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ADTVector.h"
4 |
5 |
6 | // Βρίσκει αν υπάρχουν ακέραιοι a,b στο Vector numbers τέτοιοι ώστε a + b = target.
7 | // Επιστρέφει true αν βρεθούν, και τους αποθηκεύει στα a,b, και false διαφορετικά.
8 |
9 | bool pair_sum(int target, Vector numbers, int* a, int* b);
--------------------------------------------------------------------------------
/programs/pair_sum/pair_sum_test.c:
--------------------------------------------------------------------------------
1 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
2 |
3 | #include "pair_sum.h"
4 |
5 |
6 | // Δεσμεύει μνήμη για έναν ακέραιο, αντιγράφει το value εκεί και επιστρέφει pointer
7 | int* create_int(int value) {
8 | int* pointer = malloc(sizeof(int)); // δέσμευση μνήμης
9 | *pointer = value; // αντιγραφή του value στον νέο ακέραιο
10 | return pointer;
11 | }
12 |
13 | void test_pair_sum(void) {
14 | Vector numbers = vector_create(0, free);
15 | for (int i = 0; i < 10; i++)
16 | vector_insert_last(numbers, create_int(i));
17 |
18 | int a, b;
19 | TEST_ASSERT(!pair_sum(90, numbers, &a, &b));
20 | TEST_ASSERT( pair_sum(15, numbers, &a, &b));
21 | TEST_ASSERT(a + b == 15);
22 |
23 | vector_destroy(numbers);
24 | }
25 |
26 |
27 | // Λίστα με όλα τα tests προς εκτέλεση
28 | TEST_LIST = {
29 | { "pair_sum", test_pair_sum },
30 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
31 | };
--------------------------------------------------------------------------------
/programs/set_example/Makefile:
--------------------------------------------------------------------------------
1 | # Λίστα με objects (.o) για κάθε εκτελέσιμο που θέλουμε να φτιάξουμε.
2 | #
3 | # Για το ADTSet χρησιμοποιούμε την υλοποίηση μέσω AVL
4 | #
5 | set_example_OBJS = tests.o $(MODULES)/UsingAVL/ADTSet.o
6 |
7 | # Ο βασικός κορμός του Makefile
8 | include ../../common.mk
9 |
--------------------------------------------------------------------------------
/programs/set_example/tests.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
6 |
7 | #include "ADTSet.h"
8 |
9 |
10 | void strings(void) {
11 | char* s1 = "FOO";
12 | char* s2 = "BAR";
13 |
14 | Set set = set_create((CompareFunc)strcmp, NULL);
15 | set_insert(set, s1);
16 | set_insert(set, s2);
17 |
18 | TEST_ASSERT(set_size(set) == 2);
19 |
20 | char* value1 = set_find(set, "FOO");
21 | char* value2 = set_find(set, "BAR");
22 |
23 | TEST_ASSERT(s1 == value1);
24 | TEST_ASSERT(s2 == value2);
25 |
26 | set_destroy(set);
27 | }
28 |
29 | int compare_ints(int* a, int* b) {
30 | return *a - *b;
31 | }
32 |
33 | void integers(void) {
34 | int a1 = 5;
35 | int a2 = 42;
36 |
37 | Set set = set_create((CompareFunc)compare_ints, NULL);
38 |
39 | // ΠΡΟΣΟΧΗ: προσθέτουμε στο set pointers προς τοπικές μεταβλητές! Αυτό είναι ok αν το set
40 | // χρησιμοποιείται ΜΟΝΟ όσο οι μεταβλητές βρίσκονται στο scope! (δλαδή μέχρι το τέλος της κλήσης της συνάρτησης)
41 | set_insert(set, &a1);
42 | set_insert(set, &a2);
43 |
44 | TEST_ASSERT(set_size(set) == 2);
45 |
46 | int b1 = 5;
47 | int b2 = 42;
48 | int* value1 = set_find(set, &b1);
49 | int* value2 = set_find(set, &b2);
50 |
51 | TEST_ASSERT(value1 == &a1);
52 | TEST_ASSERT(value2 == &a2);
53 |
54 | set_destroy(set);
55 | }
56 |
57 | // Δεσμεύει μνήμη για έναν ακέραιο, αντιγράφει το value εκεί και επιστρέφει pointer
58 | int* create_int(int value) {
59 | int* pointer = malloc(sizeof(int)); // δέσμευση μνήμης
60 | *pointer = value; // αντιγραφή του value στον νέο ακέραιο
61 | return pointer;
62 | }
63 |
64 | void integers_loop(void) {
65 | // Χρησιμοποιούμε destroy_value = free ώστε να γίνονται αυτόματα free οι τιμές που αφαιρούνται
66 | Set set = set_create((CompareFunc)compare_ints, free);
67 |
68 | // Για να αποθηκεύσουμε 100 διαφορετικούς ακεραίους
69 | // πρέπει κάθε φορά να δημιουργήσουμε έναν νέο ακέραιο.
70 | for (int i = 0; i < 100; i++)
71 | set_insert(set, create_int(i));
72 |
73 | // set_min and set_next
74 | int i = 0;
75 | for (SetNode node = set_first(set); node != SET_EOF; node = set_next(set, node)) {
76 | int* value = set_node_value(set, node);
77 | TEST_ASSERT(*value == i++);
78 | }
79 |
80 | // set_max and set_previous
81 | i = 99;
82 | for (SetNode node = set_last(set); node != SET_BOF; node = set_previous(set, node)) {
83 | int* value = set_node_value(set, node);
84 | TEST_ASSERT(*value == i--);
85 | }
86 |
87 | // destroy, με free_values = true για να κάνουμε free και τα περιεχόμενα
88 | set_destroy(set);
89 | }
90 |
91 |
92 | // Λίστα με όλα τα tests προς εκτέλεση
93 | TEST_LIST = {
94 | { "strings", strings },
95 | { "integers", integers },
96 | { "integers_loop", integers_loop },
97 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
98 | };
--------------------------------------------------------------------------------
/tests/ADTList_test.c:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////
2 | //
3 | // Unit tests για τον ADT List.
4 | // Οποιαδήποτε υλοποίηση οφείλει να περνάει όλα τα tests.
5 | //
6 | //////////////////////////////////////////////////////////////////
7 |
8 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
9 |
10 | #include "ADTList.h"
11 |
12 |
13 | void test_create(void) {
14 | // Δημιουργούμε μια κενή λίστα με NULL δείκτη συνάρτησης delete_value
15 | List list = list_create(NULL);
16 | list_set_destroy_value(list, NULL);
17 |
18 | // Ελέγχουμε ότι δεν απέτυχε η malloc στην λίστα, και ότι
19 | // αρχικοποιείται με μέγεθος 0 (δηλαδή χωρίς κόμβους)
20 | TEST_ASSERT(list != NULL);
21 | TEST_ASSERT(list_size(list) == 0);
22 |
23 | list_destroy(list);
24 | }
25 |
26 |
27 | void test_insert(void) {
28 | List list = list_create(NULL);
29 |
30 | // Θα προσθέτουμε, μέσω της insert, δείκτες ως προς τα στοιχεία του π΄ίνακα
31 | int N = 1000;
32 | int* array = malloc(N * sizeof(*array));
33 |
34 | for (int i = 0; i < N; i++) {
35 | // LIST_BOF για εισαγωγή στην αρχή
36 | list_insert_next(list, LIST_BOF, &array[i]);
37 |
38 | // Ελέγχουμε εάν ενημερώθηκε (αυξήθηκε) το μέγεθος της λίστας.
39 | TEST_ASSERT(list_size(list) == (i + 1));
40 |
41 | // Ελέγχουμε εάν ο πρώτος κόμβος περιέχει σαν τιμή τον δείκτη που μόλις κάναμε insert
42 | TEST_ASSERT(list_node_value(list, list_first(list)) == &array[i]);
43 | }
44 |
45 | // Ελέγχουμε εάν τα στοιχεία έχουν μπει με την αντίστροφη σειρά
46 | ListNode node = list_first(list);
47 |
48 | for (int i = N - 1; i >= 0; i--) {
49 | TEST_ASSERT(list_node_value(list, node) == &array[i]);
50 | node = list_next(list, node);
51 | }
52 |
53 | // Εισαγωγή σε ενδιάμεσο κόμβο: προσθέτουμε το NULL σαν δεύτερο κόμβο
54 | ListNode first_node = list_first(list);
55 | list_insert_next(list, first_node, NULL);
56 | TEST_ASSERT(list_node_value(list, list_next(list, first_node)) == NULL);
57 |
58 | list_destroy(list);
59 | free(array);
60 | }
61 |
62 | void test_remove_next(void) {
63 | // Δημιουργία λίστας που καλεί αυτόματα τη free σε κάθε στοιχείο που αφαιρείται
64 | List list = list_create(free);
65 |
66 | int N = 1000;
67 | int** array = malloc(N * sizeof(*array));
68 |
69 | // Χρησιμοποιούμε την insert για να γεμίσουμε την λίστα, αφού την έχουμε δοκιμάσει ήδη στην test_insert()
70 | for (int i = 0; i < N; i++) {
71 |
72 | // Δημιουργούμε δυναμικά δεσμευμένα αντικείμενα για να δοκιμάσουμε την destroy_function
73 | array[i] = malloc(sizeof(int));
74 | *array[i] = i;
75 | list_insert_next(list, LIST_BOF, array[i]);
76 | }
77 |
78 |
79 | for (int i = N - 1; i >= 0; i--) {
80 | // Διαγράφουμε απο την αρχή και ελέγχουμε εάν η τιμή του πρώτου κόμβου
81 | // ήταν η ίδια με αυτή που κάναμε insert παραπάνω
82 | TEST_ASSERT(list_node_value(list, list_first(list)) == array[i]);
83 | list_remove_next(list, LIST_BOF);
84 |
85 | // Ελέγχουμε ότι ενημερώνεται (μειώνεται) το size/μέγεθος της λίστας
86 | TEST_ASSERT(list_size(list) == i);
87 | }
88 |
89 | // Ξαναγεμίζουμε την λίστα για να δοκιμάσουμε την διαγραφή απο ενδιάμεσο κόμβο
90 | for (int i = 0; i < N; i++) {
91 | array[i] = malloc(sizeof(int));
92 | *array[i] = i;
93 | list_insert_next(list, LIST_BOF, array[i]);
94 | }
95 |
96 | // Δοκιμάζουμε την διαγραφή κόμβων ενδιάμεσα της λίστας, και συγκεκριμένα του δεύτερου κόμβου απο την αρχή
97 | list_remove_next(list, list_first(list));
98 | TEST_ASSERT(list_size(list) == N - 1);
99 |
100 | list_destroy(list);
101 | free(array);
102 | }
103 |
104 |
105 | // Σύγκριση δύο int pointers
106 | int compare_ints(Pointer a, Pointer b) {
107 | return *(int*)a - *(int*)b;
108 | }
109 |
110 |
111 | void test_find(void) {
112 | List list = list_create(NULL);
113 | int N = 1000;
114 | int* array = malloc(N * sizeof(*array));
115 |
116 | // Εισάγουμε δοκιμαστικές τιμές στον πίνακα, για να ελέγξουμε την test_find
117 | for (int i = 0; i < N; i++) {
118 | array[i] = i;
119 | list_insert_next(list, LIST_BOF, &array[i]);
120 | }
121 |
122 | // Εύρεση όλων των στοιχείων
123 | for (int i = 0; i < N; i++) {
124 | int* value = list_find(list, &i, compare_ints);
125 | TEST_ASSERT(value == &array[i]);
126 | }
127 |
128 | // Δοκιμάζουμε, για μια τυχαία τιμή που δεν μπορεί πιθανώς να υπάρχει στην λίστα,
129 | // αν η list_find γυρνάει σωστά NULL pointer
130 | int not_exists = -1;
131 | TEST_ASSERT(list_find(list, ¬_exists, compare_ints) == NULL);
132 |
133 | list_destroy(list);
134 | free(array);
135 | }
136 |
137 |
138 | void test_find_node(void) {
139 | List list = list_create(NULL);
140 |
141 | // Εισαγωγή τιμών στον πίνακα
142 | int N = 1000;
143 | int* array = malloc(N * sizeof(*array));
144 |
145 | for (int i = 0; i < N; i++) {
146 | array[i] = i;
147 | list_insert_next(list, LIST_BOF, &array[i]);
148 | }
149 |
150 | // Ξεκινάμε από την αρχή της λίστας
151 | ListNode node = list_first(list);
152 |
153 | for (int i = N - 1; i >= 0; i--) {
154 | // Ελέγχουμε ότι η list_find_node βρίσκει σωστά τον πρώτο κόμβο με value τον δείκτη &array[i].
155 | // Σε αυτή την λίστα, δοκιμάζουμε ότι ο πρώτος κόμβος περιέχει τον δείκτη &array[N - 1],
156 | // o δεύτερος τον &array[998] κοκ
157 | ListNode found_node = list_find_node(list, &i, compare_ints);
158 | TEST_ASSERT(found_node == node);
159 | TEST_ASSERT(list_node_value(list, found_node) == &array[i]);
160 |
161 | // Προχωράμε στον επόμενο κόμβο για να προσπελάσουμε όλη την λίστα
162 | node = list_next(list, node);
163 | }
164 |
165 | list_destroy(list);
166 | free(array);
167 | }
168 |
169 |
170 |
171 | // destroy
172 | //
173 | // Η destroy καλείται σε όλα τα tests, για να βρούμε αν δουλεύει σωστά τρέχουμε
174 | // make valgrind
175 |
176 |
177 | // Λίστα με όλα τα tests προς εκτέλεση
178 | TEST_LIST = {
179 | { "list_create", test_create },
180 | { "list_insert_next", test_insert },
181 | { "list_remove_next", test_remove_next },
182 | { "list_find", test_find },
183 | { "list_find_node", test_find_node },
184 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
185 | };
--------------------------------------------------------------------------------
/tests/ADTMap_test.c:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////
2 | //
3 | // Unit tests για τον ADT Map.
4 | // Οποιαδήποτε υλοποίηση οφείλει να περνάει όλα τα tests.
5 | //
6 | //////////////////////////////////////////////////////////////////
7 | #include
8 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
9 |
10 | #include "ADTMap.h"
11 |
12 |
13 | // Δημιουργούμε μια ειδική compare συνάρτηση
14 | int compare_ints(Pointer a, Pointer b) {
15 | return *(int*)a - *(int*)b;
16 | }
17 |
18 |
19 | void test_create(void) {
20 |
21 | // Δημιουργούμε μια κενή λίστα (χωρίς αυτόματο free)
22 | Map map = map_create(compare_ints, NULL, NULL);
23 | map_set_hash_function(map, hash_int);
24 | map_set_destroy_key(map, NULL);
25 | map_set_destroy_value(map, NULL);
26 |
27 | // Ελέγχουμε ότι δεν απέτυχε η malloc στην λίστα, και ότι
28 | // αρχικοποιείται με μέγεθος 0 (δηλαδή χωρίς κόμβους)
29 | TEST_ASSERT(map != NULL);
30 | TEST_ASSERT(map_size(map) == 0);
31 |
32 | map_destroy(map);
33 | }
34 |
35 | // Επιστρέφει έναν ακέραιο σε νέα μνήμη με τιμή value
36 | int* create_int(int value) {
37 | int* p = malloc(sizeof(int));
38 | *p = value;
39 | return p;
40 | }
41 |
42 | // Βοηθητική συνάρτηση, κάνει insert και ελέγχει αν έγινε η εισαγωγή
43 | void insert_and_test(Map map, Pointer key, Pointer value) {
44 | map_insert(map, key, value);
45 | TEST_ASSERT(map_find(map, key) == value);
46 | }
47 |
48 | // Βοηθητική συνάρτηση για το ανακάτεμα του πίνακα τιμών
49 | void shuffle(int* array[], int n) {
50 | for (int i = 0; i < n; i++) {
51 | int j = i + rand() / (RAND_MAX / (n - i) + 1);
52 | int* t = array[j];
53 | array[j] = array[i];
54 | array[i] = t;
55 | }
56 | }
57 |
58 | void test_insert(void) {
59 |
60 | Map map = map_create(compare_ints, free, free);
61 | map_set_hash_function(map, hash_int);
62 |
63 | int N = 1000;
64 | int** key_array = malloc(N * sizeof(*key_array));
65 | int** value_array = malloc(N * sizeof(*value_array));
66 |
67 | for (int i = 0; i < N; i++) {
68 | key_array[i] = create_int(i);
69 | }
70 |
71 | // Ανακατεύουμε το key_array ώστε να υπάρχει ομοιόμορφη εισαγωγή τιμών
72 | shuffle(key_array, N);
73 |
74 | // Δοκιμάζουμε την insert εισάγοντας κάθε φορά νέους κόμβους
75 | for (int i = 0; i < N; i++) {
76 | value_array[i] = create_int(i);
77 |
78 | // Εισαγωγή, δοκιμή και έλεγχος ότι ενημερώθηκε το size
79 | insert_and_test(map, key_array[i], value_array[i]);
80 |
81 | TEST_ASSERT(map_size(map) == (i + 1));
82 | }
83 |
84 | // Προσθέτουμε ένα κλειδί που είναι __ισοδύναμο__ (όχι ίσο) με το κλειδί του πρώτου κόμβο
85 | // Και ελέγχουμε αν και το key και το value έχουν ενημερωθεί
86 | int* new_key = create_int(*key_array[0]);
87 | int* new_value = create_int(99);
88 |
89 | insert_and_test(map, new_key, new_value);
90 |
91 | map_destroy(map);
92 |
93 | // Δοκιμάζουμε ότι insert/replace δουλεύει σωστά και χωρίς αυτόματο free
94 | Map map2 = map_create(compare_ints, NULL, NULL);
95 | map_set_hash_function(map2, hash_int);
96 |
97 | int key1 = 0, key2 = 0;
98 | int value1 = 0, value2 = 0;
99 |
100 | insert_and_test(map2, &key1, &value1);
101 | insert_and_test(map2, &key1, &value2);
102 | insert_and_test(map2, &key2, &value2);
103 |
104 | map_destroy(map2);
105 | free(key_array);
106 | free(value_array);
107 |
108 | // Δοκιμάζουμε ότι η συμπεριφορά είναι σωστή όταν 2 keys κάνουν hash στην ίδια τιμή,
109 | // ακόμα και μετά από διαγραφή του ενός.
110 | Map map3 = map_create(compare_ints, NULL, NULL);
111 | map_set_hash_function(map3, hash_int);
112 |
113 | key1 = 1;
114 | key2 = 54;
115 |
116 | map_insert(map3, &key1, &value1); // Τα key1,key2 κάνουν hash στην ίδια τιμή (σε hash table μεγέθους 53)
117 | map_insert(map3, &key2, &value1);
118 | TEST_ASSERT(map_remove(map3, &key1));
119 | map_insert(map3, &key2, &value2); // πρέπει να αντικαταστήσει το key2
120 | TEST_ASSERT(map_size(map3) == 1);
121 | TEST_ASSERT(map_remove(map3, &key2));
122 | TEST_ASSERT(map_find(map3, &key2) == NULL);
123 |
124 | map_destroy(map3);
125 | }
126 |
127 |
128 | void test_remove(void) {
129 |
130 | Map map = map_create(compare_ints, free, free);
131 | map_set_hash_function(map, hash_int);
132 |
133 | int N = 1000;
134 | int** key_array = malloc(N * sizeof(*key_array));
135 | int** value_array = malloc(N * sizeof(*value_array));
136 |
137 | for (int i = 0; i < N; i++) {
138 | key_array[i] = create_int(i);
139 | value_array[i] = create_int(i);
140 |
141 | map_insert(map, key_array[i], value_array[i]);
142 | // Ανά τακτά χρονικά διαστήματα διαγράφουμε κάποιο κλειδί που μόλις βάλαμε
143 | if (i % (N / 20) == 0)
144 | TEST_ASSERT(map_remove(map, key_array[i]));
145 | }
146 |
147 | // Δοκιμάζουμε, πριν διαγράψουμε κανονικά τους κόμβους, ότι η map_remove
148 | // διαχειρίζεται σωστά ένα κλειδί που δεν υπάρχει στο Map
149 | int not_exists = 2000;
150 | TEST_ASSERT(!map_remove(map, ¬_exists));
151 |
152 | // Διαγράφουμε όλους τους κόμβους και ελέγχουμε εάν η τιμή που μας επιστρέφει η map_remove είναι σωστή
153 | for (int i = 0; i < N; i++) {
154 | // (Αν δεν το έχουμε διαγράψει ήδη)
155 | if (i % (N / 20) != 0) {
156 | TEST_ASSERT(map_remove(map, key_array[i]));
157 | }
158 | }
159 | // Ελέγχουμε την συμπεριφορά της remove σε κάτι που έχει ήδη διαγραφεί.
160 | int key1 = 100;
161 | TEST_ASSERT(!map_remove(map, &key1));
162 | map_destroy(map);
163 |
164 |
165 | // Σειριακή εισαγωγή στοιχείων και αμέσως διαγραφή. Αυτό σε έναν πίνακα κατακερματισμού
166 | // μπορεί να προκαλέσει όλα τα κελιά να είναι μαρκαρισμένα ως DELETED.
167 | map = map_create(compare_ints, free, free);
168 | map_set_hash_function(map, hash_int);
169 |
170 | for (int i = 0; i < N; i++) {
171 | key_array[i] = create_int(i);
172 | value_array[i] = create_int(i);
173 |
174 | map_insert(map, key_array[i], value_array[i]);
175 | map_remove(map, key_array[i]);
176 |
177 | TEST_ASSERT(map_size(map) == 0);
178 | }
179 | map_destroy(map);
180 |
181 | free(key_array);
182 | free(value_array);
183 | }
184 |
185 |
186 | void test_find(void) {
187 |
188 | Map map = map_create(compare_ints, free, free);
189 | map_set_hash_function(map, hash_int);
190 |
191 | int N = 1000;
192 | int** key_array = malloc(N * sizeof(*key_array));
193 | int** value_array = malloc(N * sizeof(*value_array));
194 |
195 | for (int i = 0; i < N; i++) {
196 | key_array[i] = create_int(i);
197 | value_array[i] = create_int(i);
198 |
199 | map_insert(map, key_array[i], value_array[i]);
200 | MapNode found = map_find_node(map, key_array[i]);
201 | Pointer found_key = map_node_key(map, found);
202 | Pointer found_val = map_node_value(map, found);
203 |
204 | // Δοκιμάζουμε ότι ο κόμβος που μόλις κάναμε insert έχει το ίδιο Key και Value
205 | TEST_ASSERT(found != MAP_EOF);
206 | TEST_ASSERT(found_key == key_array[i]);
207 | TEST_ASSERT(found_val == value_array[i]);
208 | }
209 |
210 | // Αναζήτηση στοιχείου που δεν υπάρχει στο map
211 | int not_exists = 2000;
212 | TEST_ASSERT(map_find_node(map, ¬_exists) == MAP_EOF);
213 | TEST_ASSERT(map_find(map, ¬_exists) == NULL);
214 |
215 | // Δοκιμή αναζήτησης μετά από διαδοχικά inserts/deletes (στην υλοποίηση με hashtable αυτό θα γεμίσει τον πίνακα με DELETED τιμές)
216 | Map map2 = map_create(compare_ints, NULL, NULL);
217 | map_set_hash_function(map2, hash_int);
218 |
219 | int M = 53; // αρχικό μέγεθος στην υλοποίηση με hashtable
220 | for (int i = 0; i < M; i++) {
221 | map_insert(map2, key_array[i], value_array[i]);
222 | TEST_ASSERT(map_remove(map2, key_array[i]));
223 | }
224 | TEST_ASSERT(map_find(map2, &key_array[M]) == NULL);
225 |
226 | map_destroy(map); // frees keys/values
227 | map_destroy(map2);
228 |
229 | free(key_array);
230 | free(value_array);
231 | }
232 |
233 | void test_iterate(void) {
234 | Map map = map_create(compare_ints, free, free);
235 | map_set_hash_function(map, hash_int);
236 | // first σε κενό map
237 | TEST_ASSERT(map_first(map) == MAP_EOF);
238 | // Προσθέτουμε Ν ακεραίους, το value κάθε ακεραίου i είναι 2*i
239 | int N = 1000;
240 | for (int i = 0; i < N; i++)
241 | map_insert(map, create_int(i), create_int(2*i));
242 |
243 | // Ελέγχουμε ότι διατρέχοντας το map βρίσκουμε όλους τους ακεραίους από μία φορά τον καθένα
244 | // Στον πίνακα seen κρατάμε αν έχουμε ήδη δει τον κάθε αριθμό
245 | bool seen[N];
246 | for (int i = 0; i < N; i++)
247 | seen[i] = false;
248 |
249 | int count = 0;
250 | for (MapNode node = map_first(map); node != MAP_EOF; node = map_next(map, node)) {
251 | int* key = map_node_key(map, node);
252 | int* value = map_node_value(map, node);
253 |
254 | TEST_ASSERT(*key >= 0 && *key < N && !seen[*key]);
255 | TEST_ASSERT(*value == 2 * *key);
256 |
257 | seen[*key] = true;
258 | count++;
259 | }
260 |
261 | // Αν κάναμε N επαναλήψεις, τότε σίγουρα βρήκαμε όλους τους αριθμούς
262 | TEST_ASSERT(count == N);
263 |
264 | map_destroy(map);
265 | }
266 |
267 | // Λίστα με όλα τα tests προς εκτέλεση
268 | TEST_LIST = {
269 | // { "create", test_create },
270 |
271 | { "map_create", test_create },
272 | { "map_insert", test_insert },
273 | { "map_remove", test_remove },
274 | { "map_find", test_find },
275 | { "map_iterate",test_iterate },
276 |
277 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
278 | };
--------------------------------------------------------------------------------
/tests/ADTPriorityQueue_test.c:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////
2 | //
3 | // Unit tests για τον ADT Priority Queue.
4 | // Οποιαδήποτε υλοποίηση οφείλει να περνάει όλα τα tests.
5 | //
6 | //////////////////////////////////////////////////////////////////
7 |
8 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
9 |
10 | #include "ADTPriorityQueue.h"
11 |
12 | // θέτει τα στοιχεία του πίνακα array σε τυχαία σειρά
13 | void shuffle(int* array[], int size) {
14 | for (int i = 0; i < size; i++) {
15 | int new_pos = i + rand() / (RAND_MAX / (size - i) + 1);
16 | int* temp = array[new_pos];
17 | array[new_pos] = array[i];
18 | array[i] = temp;
19 | }
20 | }
21 |
22 | // Επιστρέφει έναν ακέραιο σε νέα μνήμη με τιμή value
23 | int* create_int(int value) {
24 | int* p = malloc(sizeof(int));
25 | *p = value;
26 | return p;
27 | }
28 |
29 | int compare_ints(Pointer a, Pointer b) {
30 | return *(int*)a - *(int*)b;
31 | }
32 |
33 | void test_create(void) {
34 | PriorityQueue pqueue = pqueue_create(compare_ints, NULL, NULL);
35 | pqueue_set_destroy_value(pqueue, NULL);
36 |
37 | TEST_ASSERT(pqueue != NULL);
38 | TEST_ASSERT(pqueue_size(pqueue) == 0);
39 |
40 | pqueue_destroy(pqueue);
41 |
42 | // create με αρχικά στοιχεία
43 | Vector values = vector_create(0, NULL); // χωρίς destroy function, το destroy θα το κάνει η ουρά!
44 | vector_insert_last(values, create_int(0));
45 | vector_insert_last(values, create_int(1));
46 | vector_insert_last(values, create_int(2));
47 | vector_insert_last(values, create_int(3));
48 |
49 | pqueue = pqueue_create(compare_ints, free, values);
50 | TEST_ASSERT(pqueue != NULL);
51 | TEST_ASSERT(pqueue_size(pqueue) == 4);
52 |
53 | TEST_ASSERT(*(int*)pqueue_max(pqueue) == 3);
54 | pqueue_remove_max(pqueue);
55 | TEST_ASSERT(*(int*)pqueue_max(pqueue) == 2);
56 |
57 | vector_destroy(values);
58 | pqueue_destroy(pqueue);
59 | }
60 |
61 | void test_insert(void) {
62 | PriorityQueue pqueue = pqueue_create(compare_ints, NULL, NULL);
63 | int N = 1000;
64 | int* array = malloc(N * sizeof(*array)); // Στο pqueue θα προσθέσουμε pointers προς τα στοιχεία αυτού του πίνακα
65 |
66 | // insert N στοιχεία
67 | for (int i = 0; i < N; i++) {
68 | array[i] = i;
69 | pqueue_insert(pqueue, &array[i]);
70 | TEST_ASSERT(pqueue_size(pqueue) == i+1); // Το size ενημερώθηκε;
71 | TEST_ASSERT(pqueue_max(pqueue) == &array[i]); // Εισαγωγή σε αύξουσα σειρά, το στοιχείο που μόλις βάλαμε πρέπει να είναι στην κορυφή
72 | }
73 |
74 | pqueue_destroy(pqueue);
75 | free(array);
76 | }
77 |
78 | void test_remove(void) {
79 | PriorityQueue pqueue = pqueue_create(compare_ints, free, NULL);
80 |
81 | // προσθήκη δεδομένων, τυχαία σειρά
82 | int N = 10;
83 | int** array = malloc(N * sizeof(*array));
84 | for (int i = 0; i < N; i++)
85 | array[i] = create_int(i);
86 | shuffle(array, N);
87 |
88 | for (int i = 0; i < N; i++)
89 | pqueue_insert(pqueue, array[i]);
90 |
91 | // Διαδοχικά remove ώστε να συμβούν και resizes
92 | for (int i = N-1; i >= 0; i--) {
93 | int* value = pqueue_max(pqueue);
94 | TEST_ASSERT(*value == i);
95 | pqueue_remove_max(pqueue);
96 | TEST_ASSERT(pqueue_size(pqueue) == i);
97 | }
98 |
99 | pqueue_destroy(pqueue);
100 |
101 | // remove από ουρά χωρίς συνάρτηση destroy
102 | pqueue = pqueue_create(compare_ints, NULL, NULL);
103 | pqueue_insert(pqueue, &N);
104 | TEST_ASSERT(pqueue_max(pqueue) == &N);
105 | pqueue_remove_max(pqueue);
106 | pqueue_destroy(pqueue);
107 | free(array);
108 | }
109 |
110 |
111 | // Λίστα με όλα τα tests προς εκτέλεση
112 | TEST_LIST = {
113 | { "pqueue_create", test_create },
114 | { "pqueue_insert", test_insert },
115 | { "pqueue_remove_max", test_remove },
116 |
117 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
118 | };
119 |
--------------------------------------------------------------------------------
/tests/ADTQueue_test.c:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////
2 | //
3 | // Unit tests για τον ADT Queue.
4 | // Οποιαδήποτε υλοποίηση οφείλει να περνάει όλα τα tests.
5 | //
6 | //////////////////////////////////////////////////////////////////
7 |
8 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
9 |
10 | #include "ADTQueue.h"
11 |
12 |
13 | void test_create(void) {
14 | Queue queue = queue_create(NULL);
15 | queue_set_destroy_value(queue, NULL);
16 |
17 | TEST_ASSERT(queue != NULL);
18 | TEST_ASSERT(queue_size(queue) == 0);
19 |
20 | queue_destroy(queue);
21 | }
22 |
23 | void test_insert(void) {
24 | Queue queue = queue_create(NULL);
25 | int N = 1000;
26 | int* array = malloc(N * sizeof(*array)); // Στο queue θα προσθέσουμε pointers προς τα στοιχεία αυτού του πίνακα
27 |
28 | // insert 1000 στοιχεία
29 | for (int i = 0; i < 1000; i++) {
30 | queue_insert_back(queue, &array[i]);
31 | TEST_ASSERT(queue_size(queue) == i+1); // Το size πρέπει να μεγαλώσει
32 | TEST_ASSERT(queue_front(queue) == &array[0]); // Το μπροστινό στοιχείο στην κορυφή είναι πάντα το array[0]
33 | TEST_ASSERT(queue_back(queue) == &array[i]); // Το πίσω στοιχείο είναι αυτό που μόλις βάλαμε
34 | }
35 |
36 | queue_destroy(queue);
37 | free(array);
38 | }
39 |
40 | void test_remove(void) {
41 | Queue queue = queue_create(NULL);
42 | int N = 1000;
43 | int* array = malloc(N * sizeof(*array));
44 |
45 | // insert για προσθήκη δεδομένων, χωρίς ελέγχους (έχουμε ξεχωριστό test για το insert)
46 | for (int i = 0; i < 1000; i++)
47 | queue_insert_back(queue, &array[i]);
48 |
49 | // Διαδοχικά remove, πρέπει να βγουν με την ίδια σειρά που είναι στο array
50 | for (int i = 0; i < 1000; i++) {
51 | TEST_ASSERT(queue_front(queue) == &array[i]);
52 | queue_remove_front(queue);
53 | TEST_ASSERT(queue_size(queue) == 999-i);
54 | }
55 |
56 | queue_destroy(queue);
57 | free(array);
58 | }
59 |
60 | // destroy
61 | //
62 | // Η destroy καλείται σε όλα τα tests, για να βρούμε αν δουλεύει σωστά τρέχουμε
63 | // make valgrind
64 |
65 |
66 | // Λίστα με όλα τα tests προς εκτέλεση
67 | TEST_LIST = {
68 | { "queue_create", test_create },
69 | { "queue_insert_back", test_insert },
70 | { "queue_remove_front", test_remove },
71 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
72 | };
--------------------------------------------------------------------------------
/tests/ADTSet_test.c:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////
2 | //
3 | // Unit tests για τον ADT Set.
4 | // Οποιαδήποτε υλοποίηση οφείλει να περνάει όλα τα tests.
5 | //
6 | //////////////////////////////////////////////////////////////////
7 |
8 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
9 |
10 | #include "ADTSet.h"
11 |
12 |
13 | // Η συνάρτηση αυτή δεν υπάρχει στο public interface του Set αλλά χρησιμεύει
14 | // στα tests, για να ελέγχει αν το set είναι σωστό μετά από κάθε λειτουργία.
15 | bool set_is_proper(Set set);
16 |
17 |
18 | //
19 | // Βοηθητικές συναρτήσεις
20 | //
21 |
22 | // Επιστρέφει έναν ακέραιο σε νέα μνήμη με τιμή value
23 | int* create_int(int value) {
24 | int* p = malloc(sizeof(int));
25 | *p = value;
26 | return p;
27 | }
28 |
29 | // Δημιουργούμε μια ειδική compare συνάρτηση
30 | int compare_ints(Pointer a, Pointer b) {
31 | return *(int*)a - *(int*)b;
32 | }
33 |
34 | // Έλεγχος της insert σε λιγότερο χώρο
35 | void insert_and_test(Set set, int* value) {
36 |
37 | set_insert(set, value);
38 | TEST_ASSERT(set_is_proper(set));
39 | int search = *value;
40 | TEST_ASSERT(set_find(set, &search) == value);
41 | }
42 |
43 | // Βοηθητική συνάρτηση για το ανακάτεμα του πίνακα τιμών
44 | void shuffle(int** array, int n) {
45 | for (int i = 0; i < n; i++) {
46 | int j = i + rand() / (RAND_MAX / (n - i) + 1);
47 | int* t = array[j];
48 | array[j] = array[i];
49 | array[i] = t;
50 | }
51 | }
52 |
53 | //
54 | // Test συναρτήσεις
55 | //
56 |
57 | void test_create(void) {
58 |
59 | // Δημιουργούμε ένα κενό set (χωρίς συνάρτηση αποδεύσμευσης)
60 | Set set = set_create(compare_ints, NULL);
61 | set_set_destroy_value(set, NULL);
62 |
63 | TEST_ASSERT(set != NULL);
64 | TEST_ASSERT(set_size(set) == 0);
65 |
66 | set_destroy(set);
67 | }
68 |
69 | void test_insert(void) {
70 |
71 | Set set = set_create(compare_ints, free);
72 |
73 | int N = 1000;
74 |
75 | int** value_array = malloc(N * sizeof(*value_array));
76 |
77 | // Δοκιμάζουμε την insert με νέες τιμές κάθε φορά και με αυτόματο free
78 | for (int i = 0; i < N; i++) {
79 |
80 | value_array[i] = create_int(i);
81 |
82 | insert_and_test(set, value_array[i]);
83 |
84 | TEST_ASSERT(set_size(set) == (i + 1));
85 |
86 | }
87 |
88 | // Δοκιμάζουμε την insert με τιμές που υπάρχουν ήδη στο Set
89 | // και ελέγχουμε ότι δεν ενημερώθηκε το size (καθώς δεν προστέθηκε νέος κόμβος)
90 | int* new_value = create_int(0);
91 | insert_and_test(set, new_value);
92 |
93 | TEST_ASSERT(set_size(set) == N);
94 |
95 | set_destroy(set);
96 |
97 | // Δοκιμάζουμε την insert χωρίς αυτόματο free
98 | Set set2 = set_create(compare_ints, NULL);
99 |
100 | int local_value1 = 0, local_value2 = 1, local_value3 = 1;
101 |
102 | insert_and_test(set2, &local_value1);
103 | insert_and_test(set2, &local_value2);
104 | insert_and_test(set2, &local_value3); // ισοδύναμη τιμή => replace
105 |
106 | set_destroy(set2);
107 | free(value_array);
108 |
109 | }
110 |
111 |
112 | void test_remove(void) {
113 |
114 | Set set = set_create(compare_ints, free);
115 |
116 | int N = 1000;
117 |
118 | int** value_array = malloc(N * sizeof(*value_array));
119 |
120 | for (int i = 0; i < N; i++)
121 | value_array[i] = create_int(i);
122 |
123 | // Ανακατεύουμε το value_array ώστε να υπάρχει ομοιόμορφη εισαγωγή τιμών
124 | // Πχ εάν εισάγουμε δείκτες με αύξουσα σειρά τιμών, τότε εάν το Set υλοποιείται με BST,
125 | // οι κόμβοι θα προστίθενται μόνο δεξιά της ρίζας, άρα και η set_remove δεν θα δοκιμάζεται πλήρως
126 | shuffle(value_array, N);
127 |
128 | for (int i = 0; i < N; i++)
129 | set_insert(set, value_array[i]);
130 |
131 | // Δοκιμάζουμε, πριν διαγράψουμε κανονικά τους κόμβους, ότι η set_remove
132 | // διαχειρίζεται σωστά μια τιμή που δεν υπάρχει στο Set
133 | int not_exists = 2000;
134 | TEST_ASSERT(!set_remove(set, ¬_exists));
135 |
136 | // Διαγράφουμε όλους τους κόμβους
137 | for (int i = 0; i < N; i++) {
138 | TEST_ASSERT(set_remove(set, value_array[i]));
139 | TEST_ASSERT(set_is_proper(set));
140 | }
141 |
142 | set_destroy(set);
143 |
144 | // Δοκιμάζουμε τη remove χωρίς αυτόματο free
145 | Set set2 = set_create(compare_ints, NULL);
146 |
147 | int local_value1 = 0;
148 |
149 | insert_and_test(set2, &local_value1);
150 | TEST_ASSERT(set_remove(set2, &local_value1));
151 | TEST_ASSERT(set_is_proper(set2));
152 | TEST_ASSERT(set_size(set2) == 0);
153 |
154 | set_destroy(set2);
155 | free(value_array);
156 | }
157 |
158 |
159 | void test_find(void) {
160 |
161 | Set set = set_create(compare_ints, free);
162 |
163 | int N = 1000;
164 |
165 | int** value_array = malloc(N * sizeof(*value_array));
166 |
167 | for (int i = 0; i < N; i++)
168 | value_array[i] = create_int(i);
169 |
170 | // Παρόμοια με την set_remove, εάν το δέντρο δεν είναι σωστά ισορροπημένο, οι συναρτήσεις εύρεσης
171 | // στοιχείων δεν θα ελέγχονται πλήρως
172 | shuffle(value_array, N);
173 |
174 | for (int i = 0; i < N; i++) {
175 | set_insert(set, value_array[i]);
176 |
177 | SetNode found_node = set_find_node(set, value_array[i]);
178 | Pointer found_value = set_node_value(set, found_node);
179 |
180 | TEST_ASSERT(found_node != SET_EOF);
181 | TEST_ASSERT(found_value == value_array[i]);
182 | }
183 |
184 | // Αναζήτηση στοιχείου που δεν υπάρχει στο set
185 | int not_exists = 2000;
186 | TEST_ASSERT(set_find_node(set, ¬_exists) == SET_EOF);
187 | TEST_ASSERT(set_find(set, ¬_exists) == NULL);
188 |
189 | // Αναζήτηση μέγιστων/ελάχιστων στοιχείων
190 | // Συγκρίνουμε τις τιμές των δεικτών και όχι τους ίδιους τους δείκτες, καθώς
191 | // δεν γνωρίζουμε την θέση τους μετά απο το ανακάτεμα του πίνακα, αλλά γνωρίζουμε
192 | // ποιές τιμές υπάρχουν στο Set. Στη συγκεκριμένη περίπτωση, γνωρίζουμε ότι Set = {0, 1, ..., N-1}
193 | SetNode first_node = set_first(set);
194 | Pointer first_value = set_node_value(set, first_node);
195 | TEST_ASSERT((*(int *)first_value) == 0);
196 |
197 | SetNode next = set_next(set, first_node);
198 | Pointer next_value = set_node_value(set, next);
199 | TEST_ASSERT((*(int *)next_value) == 1);
200 |
201 | SetNode last_node = set_last(set);
202 | Pointer last_node_value = set_node_value(set, last_node);
203 | TEST_ASSERT((*(int *)last_node_value) == N-1);
204 |
205 | SetNode prev = set_previous(set, last_node);
206 | Pointer prev_value = set_node_value(set, prev);
207 | TEST_ASSERT((*(int *)prev_value) == N-2);
208 |
209 | // Ελέγχουμε και ότι βρίσκουμε σωστά τις τιμές από ενδιάμεσους κόμβους
210 | SetNode middle_node = set_find_node(set, value_array[N/2]);
211 | SetNode middle_node_prev = set_previous(set, middle_node);
212 |
213 | Pointer middle_node_value = set_node_value(set, middle_node);
214 | Pointer middle_node_value_prev = set_node_value(set, middle_node_prev);
215 |
216 | TEST_ASSERT(*(int *)middle_node_value == *(int *)middle_node_value_prev + 1);
217 |
218 |
219 | set_destroy(set);
220 | free(value_array);
221 | }
222 |
223 | void test_iterate(void) {
224 | Set set = set_create(compare_ints, free);
225 |
226 | int N = 1000;
227 | int** value_array = malloc(N * sizeof(*value_array));
228 |
229 | for (int i = 0; i < N; i++)
230 | value_array[i] = create_int(i);
231 |
232 | // εισαγωγή τιμών σε τυχαία σειρά
233 | shuffle(value_array, N);
234 |
235 | for (int i = 0; i < N; i++)
236 | set_insert(set, value_array[i]);
237 |
238 | // iterate, τα στοιχεία πρέπει να τα βρούμε στη σειρά διάταξης
239 | int i = 0;
240 | for (SetNode node = set_first(set); node != SET_EOF; node = set_next(set, node)) {
241 | TEST_ASSERT(*(int*)set_node_value(set, node) == i++);
242 | }
243 |
244 | // Κάποια removes
245 | i = N - 1;
246 | set_remove(set, &i);
247 | i = 40;
248 | set_remove(set, &i);
249 |
250 | // iterate, αντίστροφη σειρά, τα στοιχεία πρέπει να τα βρούμε στη σειρά διάταξης
251 | i = N - 2;
252 | for (SetNode node = set_last(set); node != SET_EOF; node = set_previous(set, node)) {
253 | if(i == 40)
254 | i--; // το 40 το έχουμε αφαιρέσει
255 |
256 | TEST_ASSERT(*(int*)set_node_value(set, node) == i--);
257 | }
258 |
259 | set_destroy(set);
260 | free(value_array);
261 | }
262 |
263 | void test_node_value(void) {
264 | // Η συνάρτηση αυτή ελέγχει ότι ένας κόμβος περιέχει πάντα την αρχική του τιμή,
265 | // χωρίς να επηρρεάζεται από άλλους κόμβους που προστίθενται ή διαγράφονται.
266 |
267 | Set set = set_create(compare_ints, free);
268 |
269 | int N = 1000;
270 | int** value_array = malloc(N * sizeof(*value_array));
271 |
272 | for (int i = 0; i < N; i++)
273 | value_array[i] = create_int(i);
274 |
275 | shuffle(value_array, N);
276 |
277 | // Εισάγουμε έναν αριθμό και αποθηκεύουμε το node
278 | set_insert(set, value_array[0]);
279 | SetNode node = set_first(set);
280 | TEST_ASSERT(set_node_value(set, node) == value_array[0]);
281 |
282 | // Προσθήκη τιμών, και έλεγχος μετά από κάθε προσθήκη
283 | for (int i = 1; i < N; i++) {
284 | set_insert(set, value_array[i]);
285 |
286 | TEST_ASSERT(set_node_value(set, node) == value_array[0]);
287 | }
288 |
289 | // Διαγραφή τιμών, και έλεγχος μετά από κάθε διαγραφή
290 | for (int i = 1; i < N; i++) {
291 | set_remove(set, value_array[i]);
292 |
293 | TEST_ASSERT(set_node_value(set, node) == value_array[0]);
294 | }
295 |
296 | set_destroy(set);
297 | free(value_array);
298 | }
299 |
300 | // Λίστα με όλα τα tests προς εκτέλεση
301 | TEST_LIST = {
302 | { "set_create", test_create },
303 | { "set_insert", test_insert },
304 | { "set_remove", test_remove },
305 | { "set_find", test_find },
306 | { "set_iterate", test_iterate },
307 | { "set_node_value", test_node_value },
308 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
309 | };
--------------------------------------------------------------------------------
/tests/ADTStack_test.c:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////
2 | //
3 | // Unit tests για τον ADT Stack.
4 | // Οποιαδήποτε υλοποίηση οφείλει να περνάει όλα τα tests.
5 | //
6 | //////////////////////////////////////////////////////////////////
7 |
8 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
9 |
10 | #include "ADTStack.h"
11 |
12 |
13 | void test_create(void) {
14 | Stack stack = stack_create(NULL);
15 | stack_set_destroy_value(stack, NULL);
16 |
17 | TEST_ASSERT(stack != NULL);
18 | TEST_ASSERT(stack_size(stack) == 0);
19 |
20 | stack_destroy(stack);
21 | }
22 |
23 | void test_insert(void) {
24 | Stack stack = stack_create(NULL);
25 | int N = 1000;
26 | int* array = malloc(N * sizeof(*array)); // Στο stack θα προσθέσουμε pointers προς τα στοιχεία αυτού του πίνακα
27 |
28 | // insert 1000 στοιχεία
29 | for (int i = 0; i < 1000; i++) {
30 | stack_insert_top(stack, &array[i]);
31 | TEST_ASSERT(stack_size(stack) == i+1); // Το μέγεθος πρέπει να μεγαλώσει
32 | TEST_ASSERT(stack_top(stack) == &array[i]); // Στην κορυφή είναι πάντα το στοιχείο που μόλις βάλαμε!
33 | }
34 |
35 | stack_destroy(stack);
36 | free(array);
37 | }
38 |
39 | void test_remove(void) {
40 | Stack stack = stack_create(NULL);
41 | int N = 1000;
42 | int* array = malloc(N * sizeof(*array));
43 |
44 | // insert για προσθήκη δεδομένων, χωρίς ελέγχους (έχουμε ξεχωριστό test για το insert)
45 | for (int i = 0; i < 1000; i++)
46 | stack_insert_top(stack, &array[i]);
47 |
48 | // Διαδοχικά remove
49 | for (int i = 999; i >= 0; i--) {
50 | TEST_ASSERT(stack_top(stack) == &array[i]);
51 | stack_remove_top(stack);
52 | TEST_ASSERT(stack_size(stack) == i);
53 | }
54 |
55 | stack_destroy(stack);
56 | free(array);
57 | }
58 |
59 | // destroy
60 | //
61 | // Η destroy καλείται σε όλα τα tests, για να βρούμε αν δουλεύει σωστά τρέχουμε
62 | // make valgrind
63 |
64 |
65 | // Λίστα με όλα τα tests προς εκτέλεση
66 | TEST_LIST = {
67 | { "stack_create", test_create },
68 | { "stack_insert_top", test_insert },
69 | { "stack_remove_top", test_remove },
70 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
71 | };
--------------------------------------------------------------------------------
/tests/ADTVector_test.c:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////
2 | //
3 | // Unit tests για τον ADT Vector.
4 | // Οποιαδήποτε υλοποίηση οφείλει να περνάει όλα τα tests.
5 | //
6 | //////////////////////////////////////////////////////////////////
7 |
8 | #include "acutest.h" // Απλή βιβλιοθήκη για unit testing
9 |
10 | #include "ADTVector.h"
11 |
12 |
13 | void test_create(void) {
14 | Vector vec = vector_create(0, NULL);
15 | Vector vec2 = vector_create(10, NULL); // 10 αρχικά στοιχεία
16 |
17 | vector_set_destroy_value(vec, NULL);
18 | vector_set_destroy_value(vec2, NULL);
19 |
20 | TEST_ASSERT(vector_size(vec) == 0);
21 | TEST_ASSERT(vector_size(vec2) == 10);
22 |
23 | vector_destroy(vec);
24 | vector_destroy(vec2);
25 | }
26 |
27 | void test_insert_last(void) {
28 | Vector vec = vector_create(0, NULL);
29 | int N = 1000;
30 | int* array = malloc(N * sizeof(*array)); // Στο vector θα προσθέσουμε pointers προς τα στοιχεία αυτού του πίνακα
31 |
32 | // insert 1000 στοιχεία ώστε να συμβούν πολλαπλά resizes
33 | for (int i = 0; i < 1000; i++) {
34 | vector_insert_last(vec, &array[i]);
35 | TEST_ASSERT(vector_size(vec) == i+1); // Το size ενημερώθηκε;
36 | TEST_ASSERT(vector_get_at(vec, i) == &array[i]); // Μπορούμε να κάνουμε at το στοιχείο που μόλις βάλαμε;
37 | }
38 |
39 | // Δοκιμή ότι μετά τα resizes τα στοιχεία είναι ακόμα προσπελάσιμα
40 | for (int i = 0; i < 1000; i++)
41 | TEST_ASSERT(vector_get_at(vec, i) == &array[i]);
42 |
43 | vector_destroy(vec);
44 | free(array);
45 | }
46 |
47 | void test_remove_last(void) {
48 | Vector vec = vector_create(1000, NULL);
49 | int N = 1000;
50 | int* array = malloc(N * sizeof(*array));
51 |
52 | // replace για προσθήκη δεδομένων, χωρίς ελέγχους (έχουμε ξεχωριστό test για το replace)
53 | for (int i = 0; i < 1000; i++)
54 | vector_set_at(vec, i, &array[i]);
55 |
56 | // Διαδοχικά remove ώστε να συμβούν και resizes
57 | for (int i = 999; i >= 0; i--) {
58 | TEST_ASSERT(vector_get_at(vec, i) == &array[i]);
59 | vector_remove_last(vec);
60 | TEST_ASSERT(vector_size(vec) == i);
61 | }
62 |
63 | vector_destroy(vec);
64 | free(array);
65 | }
66 |
67 | void test_get_set_at(void) {
68 | int N = 1000;
69 | Vector vec = vector_create(N/2, NULL); // αρχικοποίηση με N/2 NULLs
70 | TEST_ASSERT(vector_size(vec) == N/2);
71 |
72 | // insert επιπλέον N/2 NULLs, θα τα αλλάξουμε μετά με replace
73 | for (int i = 0; i < N/2; i++)
74 | vector_insert_last(vec, NULL);
75 |
76 | int* array = malloc(N * sizeof(*array));
77 | for (int i = 0; i < N; i++) {
78 | TEST_ASSERT(vector_get_at(vec, i) == NULL);
79 | vector_set_at(vec, i, &array[i]);
80 | TEST_ASSERT(vector_get_at(vec, i) == &array[i]);
81 | }
82 |
83 | vector_destroy(vec);
84 | free(array);
85 | }
86 |
87 | void test_iterate(void) {
88 | Vector vec = vector_create(0, NULL);
89 | int N = 1000;
90 | int* array = malloc(N * sizeof(*array)); // Στο vector θα προσθέσουμε pointers προς τα στοιχεία αυτού του πίνακα
91 |
92 | // first/last σε κενό vector
93 | TEST_ASSERT(vector_first(vec) == VECTOR_BOF);
94 | TEST_ASSERT(vector_last(vec) == VECTOR_EOF);
95 |
96 | // εισαγωγή στοιχείων
97 | for (int i = 0; i < 1000; i++)
98 | vector_insert_last(vec, &array[i]);
99 |
100 | int i = 0;
101 | for (VectorNode node = vector_first(vec); node != VECTOR_EOF; node = vector_next(vec, node))
102 | TEST_ASSERT(vector_node_value(vec, node) == &array[i++]);
103 | TEST_ASSERT(i == N);
104 |
105 | for (VectorNode node = vector_last(vec); node != VECTOR_BOF; node = vector_previous(vec, node))
106 | TEST_ASSERT(vector_node_value(vec, node) == &array[--i]);
107 | TEST_ASSERT(i == 0);
108 |
109 | vector_destroy(vec);
110 | free(array);
111 | }
112 |
113 | int compare_ints(Pointer a, Pointer b) {
114 | return *(int*)a - *(int*)b;
115 | }
116 |
117 | void test_find(void) {
118 | Vector vec = vector_create(1000, NULL);
119 | int N = 1000;
120 | int* array = malloc(N * sizeof(*array));
121 |
122 | // replace για προσθήκη δεδομένων
123 | for (int i = 0; i < 1000; i++) {
124 | array[i] = i;
125 | vector_set_at(vec, i, &array[i]);
126 | }
127 |
128 | for (int i = 0; i < 1000; i++) {
129 | int* p = vector_find(vec, &i, compare_ints);
130 | TEST_ASSERT(*p == i);
131 |
132 | VectorNode node = vector_find_node(vec, &i, compare_ints);
133 | TEST_ASSERT(*(int*)vector_node_value(vec, node) == i);
134 | }
135 |
136 | int not_exists = -12;
137 | TEST_ASSERT(vector_find(vec, ¬_exists, compare_ints) == NULL);
138 | TEST_ASSERT(vector_find_node(vec, ¬_exists, compare_ints) == VECTOR_EOF);
139 |
140 | vector_destroy(vec);
141 | free(array);
142 | }
143 |
144 | void test_destroy(void) {
145 | // Απλά εκτελούμε την destroy, για να ελέγξουμε αν όντως δουλεύει
146 | // σωστά τρέχουμε το test με valgrind.
147 |
148 | Vector vec = vector_create(1, free);
149 |
150 | vector_set_at(vec, 0, malloc(1));
151 | vector_insert_last(vec, malloc(1));
152 | vector_remove_last(vec);
153 |
154 | vector_destroy(vec);
155 | }
156 |
157 |
158 | // Λίστα με όλα τα tests προς εκτέλεση
159 | TEST_LIST = {
160 | { "vector_create", test_create },
161 | { "vector_insert_last", test_insert_last },
162 | { "vector_remove_last", test_remove_last },
163 | { "vector_get_set_at", test_get_set_at },
164 | { "vector_iterate", test_iterate },
165 | { "vector_find", test_find },
166 | { "vector_destroy", test_destroy },
167 | { NULL, NULL } // τερματίζουμε τη λίστα με NULL
168 | };
--------------------------------------------------------------------------------
/tests/Makefile:
--------------------------------------------------------------------------------
1 | # Κάνοντας compile το _test.c με μια υλοποίηση .c του
2 | # συγκεκριμένου τύπου, παράγουμε ένα tets για την υλοποίηση αυτή.
3 |
4 | # Υλοποιήσεις μέσω dynamic array: ADTVector
5 | #
6 | UsingDynamicArray_ADTVector_test_OBJS = ADTVector_test.o $(MODULES)/UsingDynamicArray/ADTVector.o
7 |
8 | # Υλοποιήσεις μέσω συνδεδεμένης λίστας: ADTList
9 | #
10 | UsingLinkedList_ADTList_test_OBJS = ADTList_test.o $(MODULES)/UsingLinkedList/ADTList.o
11 |
12 | # Υλοποιήσεις μέσω ADTList: ADTStack, ADTQueue
13 | # Πέρα από το .o της κάθε υλοποίησης (πχ ADTStack.o), χρειάζεται και μια υλοποίηση του ίδιου του ADTList!
14 | #
15 | UsingADTList_ADTStack_test_OBJS = ADTStack_test.o $(MODULES)/UsingADTList/ADTStack.o $(MODULES)/UsingLinkedList/ADTList.o
16 | UsingADTList_ADTQueue_test_OBJS = ADTQueue_test.o $(MODULES)/UsingADTList/ADTQueue.o $(MODULES)/UsingLinkedList/ADTList.o
17 |
18 | # Υλοποιήσεις μέσω Heap: ADTPriorityQueue
19 | # Το Heap χρησιμοποιεί Vector, οπότε χρειαζόμαστε και μια υλοποίηση του ADTVector.
20 | #
21 | UsingHeap_ADTPriorityQueue_test_OBJS = ADTPriorityQueue_test.o $(MODULES)/UsingHeap/ADTPriorityQueue.o $(MODULES)/UsingDynamicArray/ADTVector.o
22 |
23 | # Υλοποιήσεις μέσω BinarySearchTree: ADTSet
24 | #
25 | UsingBinarySearchTree_ADTSet_test_OBJS = ADTSet_test.o $(MODULES)/UsingBinarySearchTree/ADTSet.o
26 |
27 | # Υλοποιήσεις μέσω AVL Tree: ADTSet
28 | #
29 | UsingAVL_ADTSet_test_OBJS = ADTSet_test.o $(MODULES)/UsingAVL/ADTSet.o
30 |
31 | # Υλοποιήσεις μέσω B Tree: ADTSet
32 | #
33 | UsingBTree_ADTSet_test_OBJS = ADTSet_test.o $(MODULES)/UsingBTree/ADTSet.o
34 |
35 | # Υλοποιήσεις μέσω HashTable: ADTMap
36 | #
37 | UsingHashTable_ADTMap_test_OBJS = ADTMap_test.o $(MODULES)/UsingHashTable/ADTMap.o
38 |
39 | # Υλοποιήσεις μέσω ADTSet: ADTMap
40 | # Πέρα από το .o της κάθε υλοποίησης (πχ ADTMap.o), χρειάζεται και μια υλοποίηση του ίδιου του ADTSet.
41 | # Οπότε μπορούμενα φτιάξουμε πολλαπλά tests, για διαφορετικές υλοποιήσεις του ADTSet, ώστε να τις τεστάρουμε όλες.
42 | #
43 | UsingADTSet1_ADTMap_test_OBJS = ADTMap_test.o $(MODULES)/UsingADTSet/ADTMap.o $(MODULES)/UsingBinarySearchTree/ADTSet.o
44 | UsingADTSet2_ADTMap_test_OBJS = ADTMap_test.o $(MODULES)/UsingADTSet/ADTMap.o $(MODULES)/UsingAVL/ADTSet.o
45 | UsingADTSet3_ADTMap_test_OBJS = ADTMap_test.o $(MODULES)/UsingADTSet/ADTMap.o $(MODULES)/UsingBTree/ADTSet.o
46 |
47 | # Ο βασικός κορμός του Makefile
48 | include ../common.mk
--------------------------------------------------------------------------------