├── .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 | ![run-tests](../../workflows/run-tests/badge.svg) 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 --------------------------------------------------------------------------------