diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..16b3247
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,464 @@
+EnglishData
+.fake
+# Created by https://www.toptal.com/developers/gitignore/api/java,maven,visualstudio,visualstudiocode
+# Edit at https://www.toptal.com/developers/gitignore?templates=java,maven,visualstudio,visualstudiocode
+
+### Java ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+### Maven ###
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+# https://github.com/takari/maven-wrapper#usage-without-binary-jar
+.mvn/wrapper/maven-wrapper.jar
+
+# Eclipse m2e generated files
+# Eclipse Core
+.project
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+### VisualStudio ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+*.code-workspace
+
+# Local History for Visual Studio Code
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
+### VisualStudio Patch ###
+# Additional files built by Visual Studio
+
+# End of https://www.toptal.com/developers/gitignore/api/java,maven,visualstudio,visualstudiocode
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..c5f3f6b
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "java.configuration.updateBuildConfiguration": "interactive"
+}
\ No newline at end of file
diff --git a/phase01_search/pom.xml b/phase01_search/pom.xml
new file mode 100644
index 0000000..75faa79
--- /dev/null
+++ b/phase01_search/pom.xml
@@ -0,0 +1,27 @@
+
+
+ 4.0.0
+
+ com.example
+ phase01_search
+ 1.0-SNAPSHOT
+
+
+ 1.8
+ 1.8
+
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+ provided
+
+
+
+
+
\ No newline at end of file
diff --git a/phase01_search/src/main/java/com/example/EntryStructure.java b/phase01_search/src/main/java/com/example/EntryStructure.java
new file mode 100644
index 0000000..d5a205e
--- /dev/null
+++ b/phase01_search/src/main/java/com/example/EntryStructure.java
@@ -0,0 +1,17 @@
+package com.example;
+
+import java.util.HashSet;
+import java.util.Set;
+import lombok.Getter;
+
+public class EntryStructure {
+ @Getter
+ private String word;
+ @Getter
+ private Set fileNames = new HashSet();
+
+ public EntryStructure(String word, Set fileNames) {
+ this.word = word;
+ this.fileNames = fileNames;
+ }
+}
\ No newline at end of file
diff --git a/phase01_search/src/main/java/com/example/FileReader.java b/phase01_search/src/main/java/com/example/FileReader.java
new file mode 100644
index 0000000..d850fb3
--- /dev/null
+++ b/phase01_search/src/main/java/com/example/FileReader.java
@@ -0,0 +1,28 @@
+package com.example;
+
+import java.nio.file.*;
+import java.util.ArrayList;
+
+public class FileReader {
+ public static ArrayList getFileNames(String path){
+ ArrayList fileNames = new ArrayList();
+ Path folder = Paths.get(path);
+
+ if (Files.isDirectory(folder)) {
+ try {
+ DirectoryStream directoryStream = Files.newDirectoryStream(folder);
+ for (Path filePath : directoryStream) {
+
+ fileNames.add(String.valueOf(filePath.getFileName()));
+ }
+ directoryStream.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ System.out.println("Specified path is not a directory.");
+ }
+
+ return fileNames;
+ }
+}
diff --git a/phase01_search/src/main/java/com/example/Main.java b/phase01_search/src/main/java/com/example/Main.java
new file mode 100644
index 0000000..6ed4002
--- /dev/null
+++ b/phase01_search/src/main/java/com/example/Main.java
@@ -0,0 +1,122 @@
+package com.example;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.Map;
+
+public class Main {
+
+ // Searchs a word in inverted index then returns name of files that are containing that word
+ public static Set searchWord(String word, Map> invertedIndex) {
+ return invertedIndex.getOrDefault(word, Collections.emptySet());
+ }
+
+ public static void main(String[] args) {
+
+ // Getting directory path and user input
+ Scanner myScanner = new Scanner(System.in);
+
+ System.out.print("Enter Data Folder Path: ");
+ final String directoryPath = myScanner.nextLine();
+
+ System.out.print("Enter Search Query: ");
+ final String userInput = myScanner.nextLine();
+
+ // Finding name of all files that are in EnglishData directory
+ ArrayList fileNamesInFolder = FileReader.getFileNames(directoryPath);
+
+ // Reading files that are in data direcotry and constructing inverted index table
+ Map> invertedIndex = new HashMap<>();
+
+ for (String fileName : fileNamesInFolder) {
+ try {
+ File file = new File(directoryPath + "/" + fileName);
+ Scanner myReader = new Scanner(file);
+
+ while (myReader.hasNextLine()) {
+ Set documentIds = new HashSet();
+ documentIds.add(fileName);
+
+ String data = myReader.nextLine();
+ String[] splittedData = data.split(" ");
+
+ for (String word : splittedData) {
+ // Ignoring empty words
+ if (word == "") {
+ continue;
+ }
+ // Inserting new entry to inverted index table
+ invertedIndex.computeIfAbsent(word, k -> new HashSet<>()).addAll(documentIds);
+ }
+ }
+ myReader.close();
+ } catch (FileNotFoundException e) {
+ System.out.println("An error occurred.");
+ e.printStackTrace();
+ }
+ }
+
+ // Splitting user input to undrestand the command
+ String[] userInputWords = userInput.split(" ");
+
+ ArrayList necessaryWords = new ArrayList();
+ ArrayList orWords = new ArrayList();
+ ArrayList notWords = new ArrayList();
+
+ // Categorizing command words into orWords, necessary and forbidden groups
+ for (String word : userInputWords) {
+ if (word.startsWith("+")) {
+ word = word.replace('+', ' ').trim();
+ orWords.add(word);
+ } else if (word.startsWith("-")) {
+ word = word.replace('-', ' ').trim();
+ notWords.add(word);
+ } else {
+ necessaryWords.add(word);
+ }
+ }
+ myScanner.close();
+
+ // Initializing some sets for forming final answer
+ Set necessaryFiles = new HashSet();
+ Set forbiddenFiles = new HashSet();
+ Set orFiles = new HashSet();
+
+ // Finding intersection between necessary words' file names
+ for (String nWord : necessaryWords) {
+ if(necessaryFiles.isEmpty()){
+ necessaryFiles.addAll(searchWord(nWord, invertedIndex));
+ }
+ else{
+ necessaryFiles.retainAll(searchWord(nWord, invertedIndex));
+ }
+ }
+
+ // Finding all orWords' file names
+ for (String orWord : orWords) {
+ orFiles.addAll(searchWord(orWord, invertedIndex));
+ }
+
+ // Finding all forbidden words' file names
+ for (String notWord : notWords) {
+ forbiddenFiles.addAll(searchWord(notWord, invertedIndex));
+ }
+
+ // Final answer is intersection between necessary files and orFiles - forbidden files
+ Set intersection = new HashSet<>(necessaryFiles);
+ intersection.retainAll(orFiles);
+ intersection.removeAll(forbiddenFiles);
+
+ //Printing the result
+ System.out.println("Results: ");
+ for (String entry : intersection) {
+ System.out.println(entry);
+ }
+ }
+}
\ No newline at end of file
diff --git a/phase02_clean_code/pom.xml b/phase02_clean_code/pom.xml
new file mode 100644
index 0000000..07d09f3
--- /dev/null
+++ b/phase02_clean_code/pom.xml
@@ -0,0 +1,27 @@
+
+
+ 4.0.0
+
+ com.example
+ phase02_clean_code
+ 1.0-SNAPSHOT
+
+
+ 1.8
+ 1.8
+
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+ provided
+
+
+
+
+
\ No newline at end of file
diff --git a/phase02_clean_code/src/main/java/com/example/EntryStructure.java b/phase02_clean_code/src/main/java/com/example/EntryStructure.java
new file mode 100644
index 0000000..d5a205e
--- /dev/null
+++ b/phase02_clean_code/src/main/java/com/example/EntryStructure.java
@@ -0,0 +1,17 @@
+package com.example;
+
+import java.util.HashSet;
+import java.util.Set;
+import lombok.Getter;
+
+public class EntryStructure {
+ @Getter
+ private String word;
+ @Getter
+ private Set fileNames = new HashSet();
+
+ public EntryStructure(String word, Set fileNames) {
+ this.word = word;
+ this.fileNames = fileNames;
+ }
+}
\ No newline at end of file
diff --git a/phase02_clean_code/src/main/java/com/example/FileNameCategorizer.java b/phase02_clean_code/src/main/java/com/example/FileNameCategorizer.java
new file mode 100644
index 0000000..d5e79d5
--- /dev/null
+++ b/phase02_clean_code/src/main/java/com/example/FileNameCategorizer.java
@@ -0,0 +1,31 @@
+package com.example;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class FileNameCategorizer {
+ public static Set categorizer(Map> invertedIndex, ArrayList words, boolean isNecessary){
+ Set finalResult = new HashSet();
+
+ if(isNecessary){
+ for (String word : words) {
+ Set searchResult = WordFileNameFinder.searchWord(word, invertedIndex);
+ if(finalResult.isEmpty()){
+ finalResult.addAll(searchResult);
+ }
+ else{
+ finalResult.retainAll(searchResult);
+ }
+ }
+ }else{
+ for (String word : words) {
+ Set searchResult = WordFileNameFinder.searchWord(word, invertedIndex);
+ finalResult.addAll(searchResult);
+ }
+ }
+
+ return finalResult;
+ }
+}
diff --git a/phase02_clean_code/src/main/java/com/example/FileReader.java b/phase02_clean_code/src/main/java/com/example/FileReader.java
new file mode 100644
index 0000000..d850fb3
--- /dev/null
+++ b/phase02_clean_code/src/main/java/com/example/FileReader.java
@@ -0,0 +1,28 @@
+package com.example;
+
+import java.nio.file.*;
+import java.util.ArrayList;
+
+public class FileReader {
+ public static ArrayList getFileNames(String path){
+ ArrayList fileNames = new ArrayList();
+ Path folder = Paths.get(path);
+
+ if (Files.isDirectory(folder)) {
+ try {
+ DirectoryStream directoryStream = Files.newDirectoryStream(folder);
+ for (Path filePath : directoryStream) {
+
+ fileNames.add(String.valueOf(filePath.getFileName()));
+ }
+ directoryStream.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ System.out.println("Specified path is not a directory.");
+ }
+
+ return fileNames;
+ }
+}
diff --git a/phase02_clean_code/src/main/java/com/example/Main.java b/phase02_clean_code/src/main/java/com/example/Main.java
new file mode 100644
index 0000000..d1b24b9
--- /dev/null
+++ b/phase02_clean_code/src/main/java/com/example/Main.java
@@ -0,0 +1,82 @@
+package com.example;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.Map;
+
+public class Main {
+
+ public static void main(String[] args) {
+
+ // Getting directory path and user input
+ Scanner myScanner = new Scanner(System.in);
+
+ System.out.print("Enter Data Folder Path: ");
+ final String directoryPath = myScanner.nextLine();
+
+ System.out.print("Enter Search Query: ");
+ final String userInput = myScanner.nextLine();
+
+ myScanner.close();
+
+ // Finding name of all files that are in EnglishData directory
+ ArrayList fileNamesInFolder = FileReader.getFileNames(directoryPath);
+
+ // Reading files that are in data direcotry and constructing inverted index table
+ Map> invertedIndex = new HashMap<>();
+
+ for (String fileName : fileNamesInFolder) {
+ try {
+ File file = new File(directoryPath + "/" + fileName);
+ Scanner myReader = new Scanner(file);
+
+ while (myReader.hasNextLine()) {
+ Set documentIds = new HashSet();
+ documentIds.add(fileName);
+
+ String data = myReader.nextLine();
+ String[] splittedData = data.split(" ");
+
+ for (String word : splittedData) {
+ // Ignoring empty words
+ if (word == "") {
+ continue;
+ }
+ // Inserting new entry to inverted index table
+ invertedIndex.computeIfAbsent(word, k -> new HashSet<>()).addAll(documentIds);
+ }
+ }
+ myReader.close();
+ } catch (FileNotFoundException e) {
+ System.out.println("An error occurred.");
+ e.printStackTrace();
+ }
+ }
+
+ // Categorizing command words into or_words, necessary and forbidden groups
+ ArrayList necessaryWords = WordCategorizer.categorizer(userInput, "");
+ ArrayList orWords = WordCategorizer.categorizer(userInput, "+");
+ ArrayList notWords = WordCategorizer.categorizer(userInput, "-");
+
+ // Initializing some sets for forming final answer
+ Set necessaryFiles = FileNameCategorizer.categorizer(invertedIndex, necessaryWords, true);
+ Set orFiles = FileNameCategorizer.categorizer(invertedIndex, orWords, false);
+ Set forbiddenFiles = FileNameCategorizer.categorizer(invertedIndex, notWords, false);
+
+ // Final answer is intersection between necessary files and orFiles - forbidden files
+ Set intersection = new HashSet<>(necessaryFiles);
+ intersection.retainAll(orFiles);
+ intersection.removeAll(forbiddenFiles);
+
+ //Printing the result
+ System.out.println("Results: ");
+ for (String entry : intersection) {
+ System.out.println(entry);
+ }
+ }
+}
\ No newline at end of file
diff --git a/phase02_clean_code/src/main/java/com/example/WordCategorizer.java b/phase02_clean_code/src/main/java/com/example/WordCategorizer.java
new file mode 100644
index 0000000..bdfc220
--- /dev/null
+++ b/phase02_clean_code/src/main/java/com/example/WordCategorizer.java
@@ -0,0 +1,31 @@
+package com.example;
+
+import java.util.ArrayList;
+
+public class WordCategorizer {
+ // Takes two strings, str and target and finds all words in str that starts with target character
+ public static ArrayList categorizer(String str, String target){
+ // Splitting user input to undrestand the command
+ String[] userInputWords = str.split(" ");
+
+ ArrayList result = new ArrayList();
+
+ if(target == ""){
+ for(String word : userInputWords){
+ if((!word.startsWith("+")) && (!word.startsWith("-"))){
+ result.add(word);
+ }
+ }
+ }
+ else{
+ for(String word : userInputWords){
+ if(word.startsWith(target)){
+ word = word.replace(target.charAt(0), ' ').trim();
+ result.add(word);
+ }
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/phase02_clean_code/src/main/java/com/example/WordFileNameFinder.java b/phase02_clean_code/src/main/java/com/example/WordFileNameFinder.java
new file mode 100644
index 0000000..da0cb90
--- /dev/null
+++ b/phase02_clean_code/src/main/java/com/example/WordFileNameFinder.java
@@ -0,0 +1,12 @@
+package com.example;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public class WordFileNameFinder {
+ // Searchs a word in inverted index then returns name of files that are containing that word
+ public static Set searchWord(String word, Map> invertedIndex) {
+ return invertedIndex.getOrDefault(word, Collections.emptySet());
+ }
+}
diff --git a/phase03-C#/.vscode/settings.json b/phase03-C#/.vscode/settings.json
new file mode 100644
index 0000000..9aed938
--- /dev/null
+++ b/phase03-C#/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "dotnet.defaultSolution": "StudentsTopScores.sln"
+}
\ No newline at end of file
diff --git a/phase03-C#/StudentsTopScores.sln b/phase03-C#/StudentsTopScores.sln
new file mode 100644
index 0000000..1e70e35
--- /dev/null
+++ b/phase03-C#/StudentsTopScores.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StudentsTopScores", "StudentsTopScores\StudentsTopScores.csproj", "{9C8310E8-3177-4798-BCA1-E7FC2BB98095}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {9C8310E8-3177-4798-BCA1-E7FC2BB98095}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9C8310E8-3177-4798-BCA1-E7FC2BB98095}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9C8310E8-3177-4798-BCA1-E7FC2BB98095}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9C8310E8-3177-4798-BCA1-E7FC2BB98095}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/phase03-C#/StudentsTopScores/AverageCalculator.cs b/phase03-C#/StudentsTopScores/AverageCalculator.cs
new file mode 100644
index 0000000..a1a4d80
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/AverageCalculator.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace StudentsTopScores
+{
+ internal class AverageCalculator
+ {
+ public static float CalculateAvgForStudent(Student student)
+ {
+ return student.Scores.Average(s => s.Score);
+ }
+ }
+}
diff --git a/phase03-C#/StudentsTopScores/FileReader.cs b/phase03-C#/StudentsTopScores/FileReader.cs
new file mode 100644
index 0000000..05e947c
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/FileReader.cs
@@ -0,0 +1,24 @@
+using System;
+using Newtonsoft.Json;
+
+namespace StudentsTopScores
+{
+ class FileReader
+ {
+ public static List ReadFromFile(string path)
+ {
+ try
+ {
+ string studentsJsonContext = File.ReadAllText(path);
+ List result = JsonConvert.DeserializeObject>(studentsJsonContext)
+ ?? new List();
+ return result;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.ToString());
+ return new List();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/phase03-C#/StudentsTopScores/Printer.cs b/phase03-C#/StudentsTopScores/Printer.cs
new file mode 100644
index 0000000..56bfc98
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/Printer.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace StudentsTopScores
+{
+ class Printer
+ {
+ public static void PrintTopStudents(List students, int numberOfStudents)
+ {
+ int count = 1;
+ foreach (var student in students)
+ {
+ if (count > numberOfStudents)
+ {
+ break;
+ }
+ Console.WriteLine("Stage " + count.ToString() + ": " + student.FirstName + " " + student.LastName + " with Average of: " + student.AverageScore.ToString());
+ count++;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/phase03-C#/StudentsTopScores/Program.cs b/phase03-C#/StudentsTopScores/Program.cs
new file mode 100644
index 0000000..e6612df
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/Program.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Text.Json;
+using Newtonsoft.Json;
+
+namespace StudentsTopScores
+{
+ class Program
+ {
+ public static void Main(string[] args)
+ {
+ string studentsFilePath = UserInput.GetInput("Enter students data file path: ");
+ string scoresFilePath = UserInput.GetInput("Enter scores data file path: ");
+ int topStudentsNum = int.Parse(UserInput.GetInput("Enter number of top students to print: "));
+
+ StudentsInformation students = new(studentsFilePath, scoresFilePath);
+ List scoredStudents = students.ConstructStudentsInformation();
+
+ TopStudents topStudents = new();
+ List topNStudents = topStudents.FindtopStudetns(scoredStudents, topStudentsNum);
+
+ Printer.PrintTopStudents(topNStudents, topStudentsNum);
+
+ Console.ReadKey();
+ }
+ }
+}
\ No newline at end of file
diff --git a/phase03-C#/StudentsTopScores/Student.cs b/phase03-C#/StudentsTopScores/Student.cs
new file mode 100644
index 0000000..14e261e
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/Student.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Text.Json;
+using System.IO;
+
+namespace StudentsTopScores
+{
+ class Student
+ {
+ public int StudentNumber { get; set; }
+ public required string FirstName { get; set; }
+ public required string LastName { get; set; }
+ public float AverageScore;
+ required public List Scores { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/phase03-C#/StudentsTopScores/StudentScore.cs b/phase03-C#/StudentsTopScores/StudentScore.cs
new file mode 100644
index 0000000..f844412
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/StudentScore.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace StudentsTopScores
+{
+ class StudentScore
+ {
+ public int StudentNumber { get; set; }
+ public required string Lesson { get; set; }
+ public float Score { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/phase03-C#/StudentsTopScores/StudentsInformation.cs b/phase03-C#/StudentsTopScores/StudentsInformation.cs
new file mode 100644
index 0000000..e2d61e9
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/StudentsInformation.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace StudentsTopScores
+{
+ internal class StudentsInformation
+ {
+ private readonly string StudentsFilePath;
+ private readonly string ScoresFilePath;
+
+ public StudentsInformation(string studentsFilePath, string scoresFilePath)
+ {
+ this.StudentsFilePath = studentsFilePath;
+ this.ScoresFilePath = scoresFilePath;
+ }
+
+ public List ConstructStudentsInformation()
+ {
+ List scoredStudents = new();
+
+ try
+ {
+ List students = FileReader.ReadFromFile(StudentsFilePath);
+ List scores = FileReader.ReadFromFile(ScoresFilePath);
+
+ foreach (var student in students)
+ {
+ student.Scores = new List();
+ student.Scores.AddRange(scores.FindAll(s => s.StudentNumber == student.StudentNumber).ToList());
+ student.AverageScore = AverageCalculator.CalculateAvgForStudent(student);
+ scoredStudents.Add(student);
+ }
+
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ }
+
+ return scoredStudents;
+ }
+ }
+}
diff --git a/phase03-C#/StudentsTopScores/StudentsTopScores.csproj b/phase03-C#/StudentsTopScores/StudentsTopScores.csproj
new file mode 100644
index 0000000..9cf5c96
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/StudentsTopScores.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/phase03-C#/StudentsTopScores/TopStudents.cs b/phase03-C#/StudentsTopScores/TopStudents.cs
new file mode 100644
index 0000000..9a69b50
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/TopStudents.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace StudentsTopScores
+{
+ internal class TopStudents
+ {
+ public List FindtopStudetns(List scoredStudents, int numberOfStudents)
+ {
+ List topStudents = scoredStudents.OrderByDescending(s => s.AverageScore).Take(numberOfStudents).ToList();
+ return topStudents;
+ }
+ }
+}
diff --git a/phase03-C#/StudentsTopScores/UserInput.cs b/phase03-C#/StudentsTopScores/UserInput.cs
new file mode 100644
index 0000000..c22e643
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/UserInput.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace StudentsTopScores
+{
+ internal class UserInput
+ {
+ public static String GetInput(string message)
+ {
+ Console.WriteLine(message);
+ String userInput = Console.ReadLine() ?? "";
+
+ return userInput;
+ }
+ }
+}
diff --git a/phase03-C#/StudentsTopScores/scores.json b/phase03-C#/StudentsTopScores/scores.json
new file mode 100644
index 0000000..2dfb40a
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/scores.json
@@ -0,0 +1,162 @@
+[
+ {
+ "StudentNumber": 1,
+ "Lesson": "DB",
+ "Score": 14.63433486
+ },
+ {
+ "StudentNumber": 1,
+ "Lesson": "AP",
+ "Score": 12.0218272
+ },
+ {
+ "StudentNumber": 1,
+ "Lesson": "DA",
+ "Score": 18.98081619
+ },
+ {
+ "StudentNumber": 1,
+ "Lesson": "DS",
+ "Score": 15.52422954
+ },
+ {
+ "StudentNumber": 2,
+ "Lesson": "DB",
+ "Score": 19.9984161
+ },
+ {
+ "StudentNumber": 2,
+ "Lesson": "AP",
+ "Score": 17.51669733
+ },
+ {
+ "StudentNumber": 2,
+ "Lesson": "DA",
+ "Score": 10.26295696
+ },
+ {
+ "StudentNumber": 2,
+ "Lesson": "DS",
+ "Score": 10.01975171
+ },
+ {
+ "StudentNumber": 3,
+ "Lesson": "DB",
+ "Score": 16.607692
+ },
+ {
+ "StudentNumber": 3,
+ "Lesson": "AP",
+ "Score": 12.2630708
+ },
+ {
+ "StudentNumber": 3,
+ "Lesson": "DA",
+ "Score": 18.02715037
+ },
+ {
+ "StudentNumber": 3,
+ "Lesson": "DS",
+ "Score": 14.12720018
+ },
+ {
+ "StudentNumber": 4,
+ "Lesson": "DB",
+ "Score": 17.70425414
+ },
+ {
+ "StudentNumber": 4,
+ "Lesson": "AP",
+ "Score": 17.57739023
+ },
+ {
+ "StudentNumber": 4,
+ "Lesson": "DA",
+ "Score": 16.22962447
+ },
+ {
+ "StudentNumber": 4,
+ "Lesson": "DS",
+ "Score": 17.45579008
+ },
+ {
+ "StudentNumber": 5,
+ "Lesson": "DB",
+ "Score": 10.69591387
+ },
+ {
+ "StudentNumber": 5,
+ "Lesson": "AP",
+ "Score": 13.74945505
+ },
+ {
+ "StudentNumber": 5,
+ "Lesson": "DA",
+ "Score": 10.47906005
+ },
+ {
+ "StudentNumber": 5,
+ "Lesson": "DS",
+ "Score": 10.64942138
+ },
+ {
+ "StudentNumber": 6,
+ "Lesson": "DB",
+ "Score": 15.89915904
+ },
+ {
+ "StudentNumber": 6,
+ "Lesson": "AP",
+ "Score": 10.45254317
+ },
+ {
+ "StudentNumber": 6,
+ "Lesson": "DA",
+ "Score": 19.92515277
+ },
+ {
+ "StudentNumber": 6,
+ "Lesson": "DS",
+ "Score": 14.2964831
+ },
+ {
+ "StudentNumber": 7,
+ "Lesson": "DB",
+ "Score": 19.99800627
+ },
+ {
+ "StudentNumber": 7,
+ "Lesson": "AP",
+ "Score": 17.89393279
+ },
+ {
+ "StudentNumber": 7,
+ "Lesson": "DA",
+ "Score": 10.28261242
+ },
+ {
+ "StudentNumber": 7,
+ "Lesson": "DS",
+ "Score": 11.97554454
+ },
+ {
+ "StudentNumber": 8,
+ "Lesson": "DB",
+ "Score": 14.11228504
+ },
+ {
+ "StudentNumber": 8,
+ "Lesson": "AP",
+ "Score": 18.17301775
+ },
+ {
+ "StudentNumber": 8,
+ "Lesson": "DA",
+ "Score": 12.03562658
+ },
+ {
+ "StudentNumber": 8,
+ "Lesson": "DS",
+ "Score": 11.91781334
+ }
+]
diff --git a/phase03-C#/StudentsTopScores/students.json b/phase03-C#/StudentsTopScores/students.json
new file mode 100644
index 0000000..f4ea1a0
--- /dev/null
+++ b/phase03-C#/StudentsTopScores/students.json
@@ -0,0 +1,42 @@
+[
+ {
+ "StudentNumber": 1,
+ "FirstName": "Mahdi",
+ "LastName": "Malverdi"
+ },
+ {
+ "StudentNumber": 2,
+ "FirstName": "Mohammad",
+ "LastName": "Haghighat"
+ },
+ {
+ "StudentNumber": 3,
+ "FirstName": "Mohammad Hossein",
+ "LastName": "Mostmand"
+ },
+ {
+ "StudentNumber": 4,
+ "FirstName": "Hossein",
+ "LastName": "Behbodi"
+ },
+ {
+ "StudentNumber": 5,
+ "FirstName": "Ahmad",
+ "LastName": "Salimi"
+ },
+ {
+ "StudentNumber": 6,
+ "FirstName": "Hadi",
+ "LastName": "EsnaAshari"
+ },
+ {
+ "StudentNumber": 7,
+ "FirstName": "Alireza",
+ "LastName": "Ziaei"
+ },
+ {
+ "StudentNumber": 8,
+ "FirstName": "Mohammad Hossein",
+ "LastName": "Ghesarieh"
+ }
+]
diff --git a/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/OperatorProviderTest.cs b/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/OperatorProviderTest.cs
new file mode 100644
index 0000000..5104d08
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/OperatorProviderTest.cs
@@ -0,0 +1,44 @@
+using FluentAssertions;
+using SimpleCalculator.Business.Abstraction;
+using SimpleCalculator.Business.Enums;
+using SimpleCalculator.Business.OperatorBusiness;
+using SimpleCalculator.Business.OperatorBusiness.Operators;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SimpleCalculator.Business.Tests.OperatorBusiness
+{
+ public class OperatorProviderTest
+ {
+ [Theory]
+ [InlineData(OperatorEnum.sum, typeof(SumOperator))]
+ [InlineData(OperatorEnum.sub, typeof(SubOperator))]
+ [InlineData(OperatorEnum.multiply, typeof(MultiplyOperator))]
+ [InlineData(OperatorEnum.division, typeof(DivisionOperator))]
+ public void GetOperator_ReturnsCorrectOperator(OperatorEnum operatorType, Type expectedOperatorType)
+ {
+ // Arrange
+ IOperatorProvider operatorProvider = new OperatorProvider();
+
+ // Act
+ IOperator result = operatorProvider.GetOperator(operatorType);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().BeOfType(expectedOperatorType);
+ }
+
+ [Fact]
+ public void GetOperator_ThrowsNotSupportedException_ForUnsupportedOperator()
+ {
+ // Arrange
+ IOperatorProvider operatorProvider = new OperatorProvider();
+
+ // Act & Assert
+ Assert.Throws(() => operatorProvider.GetOperator((OperatorEnum)8));
+ }
+ }
+}
diff --git a/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/DivisionTest.cs b/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/DivisionTest.cs
new file mode 100644
index 0000000..295a8e2
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/DivisionTest.cs
@@ -0,0 +1,44 @@
+using System;
+using FluentAssertions;
+using SimpleCalculator.Business.OperatorBusiness.Operators;
+
+namespace SimpleCalculator.Business.Tests.OperatorBusiness.Operators
+{
+ public class DivisionTest
+ {
+ [Theory]
+ [InlineData(2, 4, 0)]
+ [InlineData(28, 14, 2)]
+ [InlineData(-28, -14, 2)]
+ [InlineData(12, -2, -6)]
+ [InlineData(-14, 12, -1)]
+ [InlineData(0, -14, 0)]
+ public void Division_ShouldReutrnDivisionResult_WhenSecondNumberIsNotZero(int num1, int num2, int answer)
+ {
+ //Arrange
+ var divisionOperatot = new DivisionOperator();
+
+ //Act
+ int result = divisionOperatot.Calculate(num1, num2);
+
+ //Assert
+ result.Should().Be(answer);
+ }
+
+ [Fact]
+ public void Division_ShouldReutrnDivisionResult_WhenSecondNumberIsZero()
+ {
+ //Arrange
+ var divisionOperator = new DivisionOperator();
+ int num1 = 12;
+ int num2 = 0;
+
+ // Act
+ var action = () => divisionOperator.Calculate(num1, num2);
+
+ //Assert
+ action.Should().Throw();
+ }
+ }
+}
+
diff --git a/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/MultipyTest.cs b/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/MultipyTest.cs
new file mode 100644
index 0000000..bd0f42d
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/MultipyTest.cs
@@ -0,0 +1,31 @@
+using System;
+using FluentAssertions;
+using SimpleCalculator.Business.OperatorBusiness.Operators;
+
+namespace SimpleCalculator.Business.Tests.OperatorBusiness.Operators
+{
+
+ public class MultipyTest
+ {
+ [Theory]
+ [InlineData(2, 4, 8)]
+ [InlineData(12, -2, -24)]
+ [InlineData(-12, 14, -168)]
+ [InlineData(-12, -14, 168)]
+ [InlineData(0, -14, 0)]
+ [InlineData(12, 0, 0)]
+ [InlineData(0, 0, 0)]
+ public void Multiply_ShouldReutrnMultiplyResult_WhenEver(int num1, int num2, int answer)
+ {
+ //Arrange
+ var multipyOperatot = new MultiplyOperator();
+
+ //Act
+ int result = multipyOperatot.Calculate(num1, num2);
+
+ //Assert
+ result.Should().Be(answer);
+ }
+ }
+}
+
diff --git a/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/SubTest.cs b/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/SubTest.cs
new file mode 100644
index 0000000..e8ca5aa
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/SubTest.cs
@@ -0,0 +1,28 @@
+using FluentAssertions;
+using SimpleCalculator.Business.OperatorBusiness.Operators;
+
+namespace SimpleCalculator.Business.Tests.OperatorBusiness.Operators;
+
+public class SubTest
+{
+ [Theory]
+ [InlineData(2, 4, -2)]
+ [InlineData(12, -2, 14)]
+ [InlineData(-12, 14, -26)]
+ [InlineData(-12, -14, 2)]
+ [InlineData(0, -14, 14)]
+ [InlineData(12, 0, 12)]
+ [InlineData(0, 0, 0)]
+ public void Sub_ShouldReutrnSubResult_WhenEver(int num1, int num2, int answer)
+ {
+ //Arrange
+ var subOperatot = new SubOperator();
+
+ //Act
+ int result = subOperatot.Calculate(num1, num2);
+
+ //Assert
+ result.Should().Be(answer);
+ }
+}
+
diff --git a/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/SumTest.cs b/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/SumTest.cs
new file mode 100644
index 0000000..ff48ebe
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business.Tests/OperatorBusiness/Operators/SumTest.cs
@@ -0,0 +1,27 @@
+using FluentAssertions;
+using SimpleCalculator.Business.OperatorBusiness.Operators;
+
+namespace SimpleCalculator.Business.Tests.OperatorBusiness.Operators;
+
+public class SumTest
+{
+ [Theory]
+ [InlineData(2, 4, 6)]
+ [InlineData(12, -2, 10)]
+ [InlineData(-12, 14, 2)]
+ [InlineData(-12, -14, -26)]
+ [InlineData(0, -14, -14)]
+ [InlineData(12, 0, 12)]
+ [InlineData(0, 0, 0)]
+ public void Sum_ShouldReutrnSumResult_WhenEver(int num1, int num2, int answer)
+ {
+ //Arrange
+ var sumOperator = new SumOperator();
+
+ //Act
+ int result = sumOperator.Calculate(num1, num2);
+
+ //Assert
+ result.Should().Be(answer);
+ }
+}
diff --git a/phase04-test/SimpleCalculator.Business.Tests/SimpleCalculator.Business.Tests.csproj b/phase04-test/SimpleCalculator.Business.Tests/SimpleCalculator.Business.Tests.csproj
new file mode 100644
index 0000000..45259ff
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business.Tests/SimpleCalculator.Business.Tests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/phase04-test/SimpleCalculator.Business.Tests/SimpleCalculatorTest.cs b/phase04-test/SimpleCalculator.Business.Tests/SimpleCalculatorTest.cs
new file mode 100644
index 0000000..12a558b
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business.Tests/SimpleCalculatorTest.cs
@@ -0,0 +1,82 @@
+using FluentAssertions;
+using NSubstitute;
+using NSubstitute.ExceptionExtensions;
+using SimpleCalculator.Business.Abstraction;
+using SimpleCalculator.Business.Enums;
+using SimpleCalculator.Business.OperatorBusiness;
+using SimpleCalculator.Business.OperatorBusiness.Operators;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SimpleCalculator.Business.Tests
+{
+ public class SimpleCalculatorTest
+ {
+ private readonly ICalculator _sut;
+ private readonly IOperatorProvider _operatorProvider;
+
+ public SimpleCalculatorTest()
+ {
+ _operatorProvider = Substitute.For();
+ _sut = new SimpleCalculator(_operatorProvider);
+ }
+
+ [Fact]
+ public void Calculate_ShouldReturnOperatorResult_WhenEver()
+ {
+ // Arrange
+ var first = 2;
+ var second = 3;
+ var sumOperator = OperatorEnum.sum;
+ var expected = 123;
+
+ var mockedOperator = Substitute.For();
+ _operatorProvider.GetOperator(sumOperator).Returns(mockedOperator);
+
+ mockedOperator.Calculate(first, second).Returns(expected);
+
+ // Act
+ var actual = _sut.Calculate(first, second, sumOperator);
+
+ // Assert
+ actual.Should().Be(expected);
+ }
+
+ [Fact]
+ public void Calculate_ShouldThrowOperatorsException_WhenOperatorThrowsException()
+ {
+ // Arrange
+ var first = 2;
+ var second = 3;
+ var sumOperator = OperatorEnum.sum;
+
+ var mockedOperator = Substitute.For();
+ _operatorProvider.GetOperator(sumOperator).Returns(mockedOperator);
+
+ mockedOperator.Calculate(first, second).Throws();
+
+ // Act
+ var action = () => _sut.Calculate(first, second, sumOperator);
+
+ // Assert
+ action.Should().Throw();
+ }
+
+ public class OperatorException : Exception
+ {
+
+ }
+
+ //[Theory]
+ //[InlineData(2, 3, OperatorEnum._)]
+ //public void CalculatorTestInvalidOperator(int first, int second, OperatorEnum operatorEnum)
+ //{
+ // Calculator calculator = new();
+
+ // Assert.Throws(() => calculator.Calculate(first, second, operatorEnum);
+ //}
+ }
+}
diff --git a/phase04-test/SimpleCalculator.Business.Tests/Usings.cs b/phase04-test/SimpleCalculator.Business.Tests/Usings.cs
new file mode 100644
index 0000000..9df1d42
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business.Tests/Usings.cs
@@ -0,0 +1 @@
+global using Xunit;
diff --git a/phase04-test/SimpleCalculator.Business/Abstraction/ICalculator.cs b/phase04-test/SimpleCalculator.Business/Abstraction/ICalculator.cs
new file mode 100644
index 0000000..8ed10c4
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business/Abstraction/ICalculator.cs
@@ -0,0 +1,9 @@
+using SimpleCalculator.Business.Enums;
+
+namespace SimpleCalculator.Business.Abstraction
+{
+ public interface ICalculator
+ {
+ int Calculate(int first, int second, OperatorEnum operatorType);
+ }
+}
\ No newline at end of file
diff --git a/phase04-test/SimpleCalculator.Business/Abstraction/IOperator.cs b/phase04-test/SimpleCalculator.Business/Abstraction/IOperator.cs
new file mode 100644
index 0000000..faee7a0
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business/Abstraction/IOperator.cs
@@ -0,0 +1,7 @@
+namespace SimpleCalculator.Business.Abstraction
+{
+ public interface IOperator
+ {
+ int Calculate(int first, int second);
+ }
+}
\ No newline at end of file
diff --git a/phase04-test/SimpleCalculator.Business/Abstraction/IOperatorProvider.cs b/phase04-test/SimpleCalculator.Business/Abstraction/IOperatorProvider.cs
new file mode 100644
index 0000000..3f26650
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business/Abstraction/IOperatorProvider.cs
@@ -0,0 +1,9 @@
+using SimpleCalculator.Business.Enums;
+
+namespace SimpleCalculator.Business.Abstraction
+{
+ public interface IOperatorProvider
+ {
+ IOperator GetOperator(OperatorEnum operatorType);
+ }
+}
\ No newline at end of file
diff --git a/phase04-test/SimpleCalculator.Business/Enums/OperatorEnum.cs b/phase04-test/SimpleCalculator.Business/Enums/OperatorEnum.cs
new file mode 100644
index 0000000..c7b0722
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business/Enums/OperatorEnum.cs
@@ -0,0 +1,10 @@
+namespace SimpleCalculator.Business.Enums
+{
+ public enum OperatorEnum
+ {
+ sum,
+ sub,
+ multiply,
+ division
+ }
+}
\ No newline at end of file
diff --git a/phase04-test/SimpleCalculator.Business/OperatorBusiness/OperatorProvider.cs b/phase04-test/SimpleCalculator.Business/OperatorBusiness/OperatorProvider.cs
new file mode 100644
index 0000000..b867a65
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business/OperatorBusiness/OperatorProvider.cs
@@ -0,0 +1,21 @@
+using SimpleCalculator.Business.Abstraction;
+using SimpleCalculator.Business.Enums;
+using SimpleCalculator.Business.OperatorBusiness.Operators;
+
+namespace SimpleCalculator.Business.OperatorBusiness
+{
+ public class OperatorProvider : IOperatorProvider
+ {
+ public IOperator GetOperator(OperatorEnum operatorType)
+ {
+ return operatorType switch
+ {
+ OperatorEnum.sum => new SumOperator(),
+ OperatorEnum.sub => new SubOperator(),
+ OperatorEnum.multiply => new MultiplyOperator(),
+ OperatorEnum.division => new DivisionOperator(),
+ _ => throw new NotSupportedException(),
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/DivisionOperator.cs b/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/DivisionOperator.cs
new file mode 100644
index 0000000..0f0895b
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/DivisionOperator.cs
@@ -0,0 +1,16 @@
+using SimpleCalculator.Business.Abstraction;
+
+namespace SimpleCalculator.Business.OperatorBusiness.Operators
+{
+ public class DivisionOperator : IOperator
+ {
+ public int Calculate(int first, int second)
+ {
+ if (second == 0)
+ {
+ throw new DivideByZeroException();
+ }
+ return first / second;
+ }
+ }
+}
diff --git a/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/MultiplyOperator.cs b/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/MultiplyOperator.cs
new file mode 100644
index 0000000..2273476
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/MultiplyOperator.cs
@@ -0,0 +1,12 @@
+using SimpleCalculator.Business.Abstraction;
+
+namespace SimpleCalculator.Business.OperatorBusiness.Operators
+{
+ public class MultiplyOperator : IOperator
+ {
+ public int Calculate(int first, int second)
+ {
+ return first * second;
+ }
+ }
+}
diff --git a/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/SubOperator.cs b/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/SubOperator.cs
new file mode 100644
index 0000000..299e8e8
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/SubOperator.cs
@@ -0,0 +1,12 @@
+using SimpleCalculator.Business.Abstraction;
+
+namespace SimpleCalculator.Business.OperatorBusiness.Operators
+{
+ public class SubOperator : IOperator
+ {
+ public int Calculate(int first, int second)
+ {
+ return first - second;
+ }
+ }
+}
diff --git a/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/SumOperator.cs b/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/SumOperator.cs
new file mode 100644
index 0000000..0be0a8a
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business/OperatorBusiness/Operators/SumOperator.cs
@@ -0,0 +1,12 @@
+using SimpleCalculator.Business.Abstraction;
+
+namespace SimpleCalculator.Business.OperatorBusiness.Operators
+{
+ public class SumOperator : IOperator
+ {
+ public int Calculate(int first, int second)
+ {
+ return first + second;
+ }
+ }
+}
diff --git a/phase04-test/SimpleCalculator.Business/SimpleCalculator.Business.csproj b/phase04-test/SimpleCalculator.Business/SimpleCalculator.Business.csproj
new file mode 100644
index 0000000..132c02c
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business/SimpleCalculator.Business.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
diff --git a/phase04-test/SimpleCalculator.Business/SimpleCalculator.cs b/phase04-test/SimpleCalculator.Business/SimpleCalculator.cs
new file mode 100644
index 0000000..d159921
--- /dev/null
+++ b/phase04-test/SimpleCalculator.Business/SimpleCalculator.cs
@@ -0,0 +1,22 @@
+using SimpleCalculator.Business.Abstraction;
+using SimpleCalculator.Business.Enums;
+using SimpleCalculator.Business.OperatorBusiness;
+
+namespace SimpleCalculator.Business
+{
+ public class SimpleCalculator : ICalculator
+ {
+ private readonly IOperatorProvider _operatorProvider;
+
+ public SimpleCalculator(IOperatorProvider operatorProvider)
+ {
+ _operatorProvider = operatorProvider ?? throw new ArgumentNullException(nameof(operatorProvider));
+ }
+
+ public int Calculate(int first, int second, OperatorEnum operatorType)
+ {
+ var @operator = _operatorProvider.GetOperator(operatorType);
+ return @operator.Calculate(first, second);
+ }
+ }
+}
\ No newline at end of file
diff --git a/phase04-test/SimpleCalculator.sln b/phase04-test/SimpleCalculator.sln
new file mode 100644
index 0000000..c66ea3c
--- /dev/null
+++ b/phase04-test/SimpleCalculator.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32616.157
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCalculator.Console", "SimpleCalculator\SimpleCalculator.Console.csproj", "{8014D1FB-2AD5-4594-9CD5-6C5F4D81E0C3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCalculator.Business", "SimpleCalculator.Business\SimpleCalculator.Business.csproj", "{0909EA1D-C7F6-4FD9-84AA-DFA59FF46AF3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCalculator.Business.Tests", "SimpleCalculator.Business.Tests\SimpleCalculator.Business.Tests.csproj", "{2EC3269F-A338-454C-89A1-582469D7DF85}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8014D1FB-2AD5-4594-9CD5-6C5F4D81E0C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8014D1FB-2AD5-4594-9CD5-6C5F4D81E0C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8014D1FB-2AD5-4594-9CD5-6C5F4D81E0C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8014D1FB-2AD5-4594-9CD5-6C5F4D81E0C3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0909EA1D-C7F6-4FD9-84AA-DFA59FF46AF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0909EA1D-C7F6-4FD9-84AA-DFA59FF46AF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0909EA1D-C7F6-4FD9-84AA-DFA59FF46AF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0909EA1D-C7F6-4FD9-84AA-DFA59FF46AF3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2EC3269F-A338-454C-89A1-582469D7DF85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2EC3269F-A338-454C-89A1-582469D7DF85}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2EC3269F-A338-454C-89A1-582469D7DF85}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2EC3269F-A338-454C-89A1-582469D7DF85}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {BE8A8582-4913-44BF-A6B7-7F9E9361AB03}
+ EndGlobalSection
+EndGlobal
diff --git a/phase04-test/SimpleCalculator/Program.cs b/phase04-test/SimpleCalculator/Program.cs
new file mode 100644
index 0000000..e690da3
--- /dev/null
+++ b/phase04-test/SimpleCalculator/Program.cs
@@ -0,0 +1,8 @@
+using SimpleCalculator.Business;
+using SimpleCalculator.Business.OperatorBusiness;
+using SimpleCalculator.ConsoleApp;
+
+using Calculator = SimpleCalculator.Business.SimpleCalculator;
+
+new UiManager(new Calculator(new OperatorProvider()))
+ .StartUI();
\ No newline at end of file
diff --git a/phase04-test/SimpleCalculator/SimpleCalculator.Console.csproj b/phase04-test/SimpleCalculator/SimpleCalculator.Console.csproj
new file mode 100644
index 0000000..3112058
--- /dev/null
+++ b/phase04-test/SimpleCalculator/SimpleCalculator.Console.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/phase04-test/SimpleCalculator/UiManager.cs b/phase04-test/SimpleCalculator/UiManager.cs
new file mode 100644
index 0000000..a7a96a6
--- /dev/null
+++ b/phase04-test/SimpleCalculator/UiManager.cs
@@ -0,0 +1,78 @@
+using SimpleCalculator.Business;
+using SimpleCalculator.Business.Enums;
+
+namespace SimpleCalculator.ConsoleApp
+{
+ internal class UiManager
+ {
+ private static readonly Dictionary s_operatorSigns = new()
+ {
+ {"+", OperatorEnum.sum },
+ {"-", OperatorEnum.sub },
+ {"*", OperatorEnum.multiply },
+ {"/", OperatorEnum.division }
+ };
+
+ private readonly Business.SimpleCalculator _calculator;
+
+ public UiManager(Business.SimpleCalculator calculator)
+ {
+ _calculator = calculator;
+ }
+
+ public void StartUI()
+ {
+ SayHi();
+ var operatorType = GetOperator();
+ var firstOperand = GetOperand("first");
+ var secondOperand = GetOperand("second");
+ Calculate(operatorType, firstOperand, secondOperand);
+ }
+
+ private static string? GetNumberString(string name)
+ {
+ Console.WriteLine($"Write a non-decimal number for '{name} operand:");
+ return Console.ReadLine();
+ }
+
+ private static int GetOperand(string name)
+ {
+ var numberString = GetNumberString(name);
+ while (!int.TryParse(numberString, out _))
+ {
+ Console.WriteLine($"Cannot parse given number '{numberString}'");
+ numberString = GetNumberString(name);
+ }
+ return int.Parse(numberString);
+ }
+
+ private static OperatorEnum GetOperator()
+ {
+ var operatorSign = GetOperatorSign();
+ while (!s_operatorSigns.ContainsKey(operatorSign))
+ {
+ Console.WriteLine($"Given operator '{operatorSign}' is not valid!");
+ operatorSign = GetOperatorSign();
+ }
+ return s_operatorSigns[operatorSign];
+ }
+
+ private static string GetOperatorSign()
+ {
+ Console.WriteLine($"Write operator sign ({string.Join(',', s_operatorSigns.Keys)}):");
+ return Console.ReadLine().Trim();
+ }
+
+ private static void SayHi()
+ {
+ Console.WriteLine("Hi user");
+ Console.WriteLine("How you doing?");
+ }
+
+ private void Calculate(OperatorEnum operatorType, int firstOperand, int secondOperand)
+ {
+ var result = _calculator.Calculate(firstOperand, secondOperand, operatorType);
+ Console.WriteLine($"{operatorType}({firstOperand}, {secondOperand}) = {result}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/DataProcessorTest.cs b/phase05-TDD/SampleLibrary/SampleLibrary.Test/DataProcessorTest.cs
new file mode 100644
index 0000000..6de4fab
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/DataProcessorTest.cs
@@ -0,0 +1,105 @@
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace SampleLibrary.Test
+{
+ public class DataProcessorTest
+ {
+ private DataProcessor _sut;
+
+ public DataProcessorTest()
+ {
+ _sut = new DataProcessor();
+ }
+
+ [Fact]
+ public void Process_ShouldReturnAListOfEntityStructureThatEachEntityIncludesAWordAndItsSourceName_WhenThereIsOnlyOneEntryInGivenData()
+ {
+ // Arrange
+ Dictionary data = new Dictionary
+ {
+ {"file1", "there is a word" }
+ };
+
+ List expected = new List();
+ EntityStructure e1 = new EntityStructure("there", new HashSet { "file1" });
+ EntityStructure e2 = new EntityStructure("is", new HashSet { "file1" });
+ EntityStructure e3 = new EntityStructure("a", new HashSet { "file1" });
+ EntityStructure e4 = new EntityStructure("word", new HashSet { "file1" });
+ expected.Add(e1);
+ expected.Add(e2);
+ expected.Add(e3);
+ expected.Add(e4);
+
+ // Act
+ var actual = _sut.Process(data);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Process_ShouldReturnAListOfEntityStructureThatEachEntityIncludesAWordAndItsSourceName_WhenThereAreMultipleEntriesInGivenData()
+ {
+ // Arrange
+ Dictionary data = new Dictionary
+ {
+ {"file1", "there is a word" },
+ {"file2", "another word" }
+ };
+
+ List expected = new List();
+ EntityStructure e1 = new EntityStructure("there", new HashSet { "file1" });
+ EntityStructure e2 = new EntityStructure("is", new HashSet { "file1" });
+ EntityStructure e3 = new EntityStructure("a", new HashSet { "file1" });
+ EntityStructure e4 = new EntityStructure("word", new HashSet { "file1" });
+ EntityStructure e5 = new EntityStructure("another", new HashSet { "file2" });
+ EntityStructure e6 = new EntityStructure("word", new HashSet { "file2" });
+
+ expected.Add(e1);
+ expected.Add(e2);
+ expected.Add(e3);
+ expected.Add(e4);
+ expected.Add(e5);
+ expected.Add(e6);
+
+ // Act
+ var actual = _sut.Process(data);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Process_ShouldThrowArgumentNullException_WhenThereIsNoData()
+ {
+ // Arrange
+
+ // Act
+ var action = () => _sut.Process(null);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be(null);
+ }
+
+ [Fact]
+ public void Process_ShouldReturnAnEmptyListOfEntityStructure_WhenThereIsNoEntryInData()
+ {
+ // Arrange
+ Dictionary data= new Dictionary();
+
+ List expected = new List();
+
+ // Act
+ var actual = _sut.Process(data);
+
+ // Assert
+ actual.Should().BeEquivalentTo(actual);
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/DataProviderTest.cs b/phase05-TDD/SampleLibrary/SampleLibrary.Test/DataProviderTest.cs
new file mode 100644
index 0000000..5e427a4
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/DataProviderTest.cs
@@ -0,0 +1,98 @@
+using FluentAssertions;
+using NSubstitute;
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace SampleLibrary.Test
+{
+ public class DataProviderTest
+ {
+ private readonly IDataProvider _sut;
+ private readonly ISourceReader _sourceReader;
+
+ public DataProviderTest()
+ {
+ _sourceReader = Substitute.For();
+ _sut = new DataProvider(_sourceReader);
+ }
+
+ [Fact]
+ public void GetAllDataFromSource_ShouldReturnADictionaryContainingSourceNameAndSourceData_WhenThereIsOnlyOneSourceInSourcePath()
+ {
+ // Arrange
+ var sourceDirectory = "some source";
+ var expected = new Dictionary
+ {
+ {"key1", "value1" }
+ };
+
+ _sourceReader.GetAllSourceNames(sourceDirectory).Returns(["key1"]);
+ _sourceReader.ReadSource("key1").Returns("value1");
+
+
+ // Act
+ var actual = _sut.GetAllDataFromSource(sourceDirectory);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void GetAllDataFromSource_ShouldReturnADictionaryOfAllSourceNamesAndSourceData_WhenThereIsMultipleSourcesAtSourceDirectory()
+ {
+ // Arrange
+ var sourceDirectory = "some source";
+ var expected = new Dictionary
+ {
+ {"key1", "value1" },
+ {"key2", "value2" },
+ {"key3", "value3" }
+ };
+
+ _sourceReader.GetAllSourceNames(sourceDirectory).Returns(expected.Keys);
+ _sourceReader.ReadSource("key1").Returns("value1");
+ _sourceReader.ReadSource("key2").Returns("value2");
+ _sourceReader.ReadSource("key3").Returns("value3");
+
+ // Act
+ var actual = _sut.GetAllDataFromSource(sourceDirectory);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void GetAllDataFromSource_ShouldThrowArgumentException_WhenSourceDirectoryIsNullOrEmpty(string sourceDirectory)
+ {
+ // Arrange
+
+
+ // Act
+ var action = () => _sut.GetAllDataFromSource(sourceDirectory);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("source");
+ }
+
+ [Fact]
+ public void GetAllDataFromSource_ShouldReturnEmptyDictionary_WhenThereIsNoSourceInSourceDirectory()
+ {
+ // Arrange
+ var sourceDirectory = "some source";
+ var expected = new Dictionary();
+
+ // Actt
+ var actual = _sut.GetAllDataFromSource(sourceDirectory);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexConstructorTest.cs b/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexConstructorTest.cs
new file mode 100644
index 0000000..db039b9
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexConstructorTest.cs
@@ -0,0 +1,108 @@
+using Castle.Components.DictionaryAdapter.Xml;
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Sdk;
+
+namespace SampleLibrary.Test
+{
+ public class InvertedIndexConstructorTest
+ {
+ private InvertedIndexConstructor _sut;
+
+ public InvertedIndexConstructorTest()
+ {
+ _sut = new InvertedIndexConstructor();
+ }
+
+ [Fact]
+ public void Build_ShouldReturnConstructedInvertedIndexBasedOnProcessedData_WhenThereIsOnlyOneEntryInProcessedData()
+ {
+ // Arrange
+ var processedData = new List();
+ var entry = new EntityStructure("word", new HashSet { "file1" });
+ processedData.Add(entry);
+
+ var expected = new InvertedIndex();
+ expected.Add(entry.word, entry.sourceNames);
+
+ // Act
+ var actual = _sut.Build(processedData);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Build_ShouldReturnConstructedInvertedIndexBasedOnProcessedData_WhenThereAreMultipleNotRepeatedEntriesInProcessedData()
+ {
+ // Arrange
+ var processedData = new List();
+ var entry1 = new EntityStructure("word", new HashSet { "file1" });
+ var entry2 = new EntityStructure("word2", new HashSet { "file2" });
+ processedData.Add(entry1);
+ processedData.Add(entry2);
+
+ var expected = new InvertedIndex();
+ expected.Add(entry1.word, entry1.sourceNames); ;
+ expected.Add(entry2.word, entry2.sourceNames); ;
+
+ // Act
+ var actual = _sut.Build(processedData);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Build_ShouldReturnConstructedInvertedIndexBasedOnProcessedData_WhenThereAreMultipleRepeatedEntriesInProcessedData()
+ {
+ // Arrange
+ var processedData = new List();
+ var entry1 = new EntityStructure("word", new HashSet { "file1" });
+ var entry2 = new EntityStructure("word", new HashSet { "file2" });
+ processedData.Add(entry1);
+ processedData.Add(entry2);
+
+ var expected = new InvertedIndex();
+ var entry3 = new EntityStructure("word", new HashSet { "file1", "file2" });
+ expected.Add(entry3.word, entry3.sourceNames);
+
+ // Act
+ var actual = _sut.Build(processedData);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Build_ShouldThrowArgumentExcetpion_WhenProcessedDataIsNull()
+ {
+ // Arragne
+
+ //Act
+ var action = () => _sut.Build(null);
+
+ //Assert
+ action.Should().Throw().Which.ParamName.Should().Be("processedData");
+ }
+
+ [Fact]
+ public void Build_ShouldReturnAnEmptyInvertedIndex_WhenProcessedDataIsEmpty()
+ {
+ // Arrange
+ List processedData = new List();
+ InvertedIndex expected = new InvertedIndex();
+
+ // Act
+ var actual = _sut.Build(processedData);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexNecessaryWordFinderTest.cs b/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexNecessaryWordFinderTest.cs
new file mode 100644
index 0000000..da10ff9
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexNecessaryWordFinderTest.cs
@@ -0,0 +1,130 @@
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace SampleLibrary.Test
+{
+ public class InvertedIndexNecessaryWordFinderTest
+ {
+ private InvertedIndexNecessaryWordFinder _sut;
+
+ public InvertedIndexNecessaryWordFinderTest()
+ {
+ _sut = new InvertedIndexNecessaryWordFinder();
+ }
+
+ [Fact]
+ public void Find_ShouldReturnNecessaryWordsSourceNames_WhenThereIsOnlyOneEntryInInvertedIndexAndWeHaveOneNecessaryWordToSearch()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+ invertedIndex.Add("word1", ["File1", "File2"]);
+
+ var expected = new HashSet { "File1", "File2" };
+
+ // Act
+ var actual = _sut.Find(invertedIndex, ["word1"]);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Find_ShouldReturnNecessaryWordsSourceNames_WhenThereAreMoreThanOneEntryInInvertedIndexAndWeHaveOneNecessaryWordToSearch()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+ invertedIndex.Add("word1", ["File1", "File2", "File3"]);
+ invertedIndex.Add("word2", ["File1", "File2", "File3"]);
+ invertedIndex.Add("word3", ["File1", "File2", "File3"]);
+
+ var expected = new HashSet { "File1", "File2", "File3" };
+
+ // Act
+ var actual = _sut.Find(invertedIndex, ["word1"]);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Find_ShouldReturnNecessaryWordsSourceNames_WhenThereAreMoreThanOneEntryInInvertedIndexAndWeHaveMoreThanOneNecessaryWordToSearchAndWordsHaveIntersects()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+ invertedIndex.Add("word1", ["File1", "File3", "File4"]);
+ invertedIndex.Add("word2", ["File1", "File2", "File3"]);
+ invertedIndex.Add("word3", ["File1", "File2", "File4"]);
+
+ var expected = new HashSet { "File1", "File4" };
+
+ // Act
+ var actual = _sut.Find(invertedIndex, ["word1", "word3"]);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Find_ShouldReturnNecessaryWordsSourceNames_WhenThereAreMoreThanOneEntryInInvertedIndexAndWeHaveMoreThanOneNecessaryWordToSearchAndWordsHaveNoIntersects()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+ invertedIndex.Add("word1", ["File1", "File3", "File4"]);
+ invertedIndex.Add("word2", ["File1", "File2", "File3"]);
+ invertedIndex.Add("word3", ["File5", "File6", "File7"]);
+
+ var expected = new HashSet { };
+
+ // Act
+ var actual = _sut.Find(invertedIndex, ["word1", "word3"]);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Find_ShouldReturnEmptyHashSet_WhenThereIsNoEntryInInvertedIndex()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+
+ var expected = new HashSet { };
+
+ // Act
+ var actual = _sut.Find(invertedIndex, ["word1", "word3"]);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Find_ShouldThrowException_WhenInvertedIndexIsNull()
+ {
+ // Assign
+
+ // Act
+ var action = () => _sut.Find(null, ["word1", "word3"]);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("source");
+ }
+
+ [Fact]
+ public void Find_ShouldThrowException_WhenWordsIsNull()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+
+ // Act
+ var action = () => _sut.Find(invertedIndex, null);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("words");
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexQuerySearchTest.cs b/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexQuerySearchTest.cs
new file mode 100644
index 0000000..0e2d082
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexQuerySearchTest.cs
@@ -0,0 +1,86 @@
+using FluentAssertions;
+using NSubstitute;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace SampleLibrary.Test
+{
+ public class InvertedIndexQuerySearchTest
+ {
+ private InvertedIndexQuerySearch _sut;
+
+ public InvertedIndexQuerySearchTest()
+ {
+ _sut = new InvertedIndexQuerySearch();
+ }
+
+ [Fact]
+ public void SearchQuery_ShouldReturnSearchResults_WhenInvertedIndexHasEntriesAndQueryIsValid()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+ invertedIndex.Add("NecessaryWord1", ["File1", "File2", "File3", "File4"]);
+ invertedIndex.Add("NecessaryWord2", ["File2", "File3", "File4"]);
+ invertedIndex.Add("OrWord1", ["File2"]);
+ invertedIndex.Add("OrWord2", ["File3"]);
+ invertedIndex.Add("OrWord3", ["File4"]);
+ invertedIndex.Add("OrWord4", ["File5"]);
+ invertedIndex.Add("ForbiddenWord1", ["File4"]);
+
+ QueryObj queryObj = new(["NecessaryWord1", "NecessaryWord2"], ["OrWord1", "OrWord2", "OrWord3", "OrWord4"], ["ForbiddenWord1"]);
+
+ var expected = new HashSet { "File2", "File3" };
+
+ // Act
+ var actual = _sut.SearchQeury(invertedIndex, queryObj);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+
+ }
+
+ [Fact]
+ public void SearchQuery_ShouldReturnEmpty_WhenIvertedIndexIsEmpty()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+ QueryObj queryObj = new(["get"], ["illness"], ["cough"]);
+
+ // Act
+ var actual = _sut.SearchQeury(invertedIndex, queryObj);
+
+ // Assert
+ actual.Should().BeEmpty();
+ }
+
+ [Fact]
+ public void SearchQuery_ShouldThrowException_WhenIvertedIndexIsNull()
+ {
+ // Assign
+ QueryObj queryObj = new(["get"], ["illness"], ["cough"]);
+
+ // Act
+ var action = () => _sut.SearchQeury(null, queryObj);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("invertedIndex");
+ }
+
+ [Fact]
+ public void SerachQuery_ShouldThrowException_WhenQueryIsNull()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+
+ // Act
+ var action = () => _sut.SearchQeury(invertedIndex, null);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("queryObj");
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexUnnecessaryWordFinderTest.cs b/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexUnnecessaryWordFinderTest.cs
new file mode 100644
index 0000000..6550e6e
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/InvertedIndexUnnecessaryWordFinderTest.cs
@@ -0,0 +1,112 @@
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace SampleLibrary.Test
+{
+ public class InvertedIndexUnnecessaryWordFinderTest
+ {
+ private InvertedIndexUnnecessaryWordFinder _sut;
+
+ public InvertedIndexUnnecessaryWordFinderTest()
+ {
+ _sut = new InvertedIndexUnnecessaryWordFinder();
+ }
+
+ [Fact]
+ public void Find_ShouldReturnUnnecessaryWordsSourceNames_WhenThereIsOnlyOneEntryInInvertedIndexAndWeHaveOneUnnecessaryWordToSearch()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+ invertedIndex.Add("word1", ["File1", "File2"]);
+
+ var expected = new HashSet { "File1", "File2" };
+
+ // Act
+ var actual = _sut.Find(invertedIndex, ["word1"]);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Find_ShouldReturnUnnecessaryWordsSourceNames_WhenThereAreMoreThanOneEntryInInvertedIndexAndWeHaveOneUnnecessaryWordToSearch()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+ invertedIndex.Add("word1", ["File1", "File2", "File3"]);
+ invertedIndex.Add("word2", ["File1", "File2", "File3"]);
+ invertedIndex.Add("word3", ["File1", "File2", "File3"]);
+
+ var expected = new HashSet { "File1", "File2", "File3" };
+
+ // Act
+ var actual = _sut.Find(invertedIndex, ["word1"]);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Find_ShouldReturnNecessaryWordsSourceNames_WhenThereAreMoreThanOneEntryInInvertedIndexAndWeHaveMoreThanOneUnnecessaryWordToSearchAndWords()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+ invertedIndex.Add("word1", ["File1", "File3", "File4"]);
+ invertedIndex.Add("word2", ["File1", "File2", "File3"]);
+ invertedIndex.Add("word3", ["File1", "File4", "File7"]);
+
+ var expected = new HashSet { "File1", "File3", "File4", "File7" };
+
+ // Act
+ var actual = _sut.Find(invertedIndex, ["word1", "word3"]);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Find_ShouldReturnEmptyHashSet_WhenThereIsNoEntryInInvertedIndex()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+
+ var expected = new HashSet { };
+
+ // Act
+ var actual = _sut.Find(invertedIndex, ["word1", "word3"]);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Find_ShouldThrowException_WhenInvertedIndexIsNull()
+ {
+ // Assign
+
+ // Act
+ var action = () => _sut.Find(null, ["word1", "word3"]);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("source");
+ }
+
+ [Fact]
+ public void Find_ShouldThrowException_WhenWordsIsNull()
+ {
+ // Assign
+ InvertedIndex invertedIndex = new();
+
+ // Act
+ var action = () => _sut.Find(invertedIndex, null);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("words");
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryInterpreterTest.cs b/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryInterpreterTest.cs
new file mode 100644
index 0000000..7b5d616
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryInterpreterTest.cs
@@ -0,0 +1,48 @@
+using FluentAssertions;
+using NSubstitute;
+using SampleLibrary.Abstraction;
+using Xunit;
+
+namespace SampleLibrary.Test;
+
+public class QueryInterpreterTest
+{
+ private QueryInterpreter _sut;
+
+ public QueryInterpreterTest()
+ {
+ _sut = new QueryInterpreter();
+ }
+
+ [Fact]
+ public void QueryInterpreter_ShouldReturnAQueryObjectBasedOnGivenQuery_WhenQueryIsValid()
+ {
+ // Arrange
+ var givenQuery = "get help +illness +disease -cough";
+ List necessaryWords = ["get", "help"];
+ List orWords = ["illness", "disease"];
+ List forbiddenWords = ["cough"];
+ QueryObj expected = new(necessaryWords, orWords, forbiddenWords);
+
+ // Act
+ QueryObj actual = _sut.Interpret(givenQuery);
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void Interpret_ShouldThrowArgumentException_WhenSplittedQueryIsNullOrEmpty(string query)
+ {
+ // Arrange
+
+ // Act
+ var action = () => _sut.Interpret(query);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("query");
+
+ }
+}
\ No newline at end of file
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryNecessaryWordCategorizerTest.cs b/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryNecessaryWordCategorizerTest.cs
new file mode 100644
index 0000000..f56dfd8
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryNecessaryWordCategorizerTest.cs
@@ -0,0 +1,49 @@
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace SampleLibrary.Test
+{
+ public class QueryNecessaryWordCategorizerTest
+ {
+ private QueryNecessaryWordCategorizer _sut;
+
+ public QueryNecessaryWordCategorizerTest()
+ {
+ _sut = new QueryNecessaryWordCategorizer();
+ }
+
+ [Theory]
+ [InlineData(new[] { "get", "+illness", "-cough" }, new[] { "get" })]
+ [InlineData(new string[] { }, new string[] { })]
+ [InlineData(new[] { "+illness", "-fever" }, new string[] { })]
+ [InlineData(new[] { "get" }, new[] { "get" })]
+ public void Categorize_ShouldReturnAllNecessaryWordsInsideSplittedQuery_WhenQueryIsValid(string[] splittedQuery, string[] expected)
+ {
+ // Arrange
+
+
+ // Act
+ var actual = _sut.Categorize(splittedQuery);
+
+ // Assert
+ actual.Should().Equal(expected);
+ }
+
+ [Fact]
+ public void Categorize_ShouldThrowArgumentException_WhenSplittedQueryIsNull()
+ {
+ // Arrange
+
+ // Act
+ var action = () => _sut.Categorize(null);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("splittedQuery");
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryUnnecessaryForbiddenWordCategorizerTest.cs b/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryUnnecessaryForbiddenWordCategorizerTest.cs
new file mode 100644
index 0000000..3fd535e
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryUnnecessaryForbiddenWordCategorizerTest.cs
@@ -0,0 +1,49 @@
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace SampleLibrary.Test
+{
+ public class QueryUnnecessaryForbiddenWordCategorizerTest
+ {
+ private QueryUnnecessaryWordCategorizer _sut;
+
+ public QueryUnnecessaryForbiddenWordCategorizerTest()
+ {
+ _sut = new QueryUnnecessaryWordCategorizer('-');
+ }
+
+ [Theory]
+ [InlineData(new[] { "get", "+illness", "-cough" }, new[] { "cough" })]
+ [InlineData(new string[] { }, new string[] { })]
+ [InlineData(new[] { "+illness", "fever" }, new string[] { })]
+ [InlineData(new[] { "-get" }, new[] { "get" })]
+ public void Categorize_ShouldReturnAllForbiddenWordsInsideSplittedqeury_WhenQueryIsValid(string[] splittedQeury, string[] expected)
+ {
+ // Arrange
+
+
+ // Act
+ var actual = _sut.Categorize(splittedQeury);
+
+ // Arrange
+ actual.Should().Equal(expected);
+ }
+
+ [Fact]
+ public void Categorize_ShouldThrowArgumentException_WhenSplittedQueryIsNull()
+ {
+ // Arrange
+
+ // Act
+ var action = () => _sut.Categorize(null);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("splittedQuery");
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryUnnecessaryOrWordCategorizerTest.cs b/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryUnnecessaryOrWordCategorizerTest.cs
new file mode 100644
index 0000000..70a44b2
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/QueryUnnecessaryOrWordCategorizerTest.cs
@@ -0,0 +1,49 @@
+using FluentAssertions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace SampleLibrary.Test
+{
+ public class QueryUnnecessaryOrWordCategorizerTest
+ {
+ private QueryUnnecessaryWordCategorizer _sut;
+
+ public QueryUnnecessaryOrWordCategorizerTest()
+ {
+ _sut = new QueryUnnecessaryWordCategorizer('+');
+ }
+
+ [Theory]
+ [InlineData(new[] { "get", "+illness", "-cough" }, new[] { "illness" })]
+ [InlineData(new string[] { }, new string[] { })]
+ [InlineData(new[] { "illness", "-cough" }, new string[] { })]
+ [InlineData(new[] { "+get" }, new[] { "get" })]
+ public void Categorize_ShouldReturnAllOrWordsInsideSplittedQuery_WhenQueryIsValid(string[] splittedQuery, string[] expected)
+ {
+ // Arrange
+
+
+ // Act
+ var actual = _sut.Categorize(splittedQuery);
+
+ // Assert
+ actual.Should().Equal(expected);
+ }
+
+ [Fact]
+ public void Categorize_ShouldThrowArgumentException_WhenSplittedQueryIsNull()
+ {
+ // Arrange
+
+ // Act
+ var action = () => _sut.Categorize(null);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("splittedQuery");
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/ResultPrinterTest.cs b/phase05-TDD/SampleLibrary/SampleLibrary.Test/ResultPrinterTest.cs
new file mode 100644
index 0000000..98fba81
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/ResultPrinterTest.cs
@@ -0,0 +1,69 @@
+using FluentAssertions;
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace SampleLibrary.Test
+{
+ public class ResultPrinterTest
+ {
+ private ResultPrinter _sut;
+ private StringWriter _writer;
+
+ public ResultPrinterTest()
+ {
+ _writer = new StringWriter();
+ _sut = new ResultPrinter(_writer);
+ }
+
+ [Fact]
+ public void Print_ShouldPrintResult_WhenResultIsNotEmptyOrNull()
+ {
+ // Arrange
+ HashSet result = new HashSet();
+ result.Add("res1");
+ result.Add("res2");
+
+ var expected = "Search result: \r\nres1\r\nres2\r\n";
+
+ // Act
+ _sut.Print(result);
+ var actual = _writer.ToString();
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Print_ShoulrPrintNothing_WhenResultIsEmpty()
+ {
+ // Arrange
+ HashSet result = new HashSet();
+
+ var expected = "Search result: \r\n";
+
+ // Act
+ _sut.Print(result);
+ var actual = _writer.ToString();
+
+ // Assert
+ actual.Should().BeEquivalentTo(expected);
+ }
+
+ [Fact]
+ public void Print_ShouldThrowArgumentExceptionWhenResultIsNull()
+ {
+ // Arrange
+
+ // Act
+ var action = () => _sut.Print(null);
+
+ // Assert
+ action.Should().Throw().Which.ParamName.Should().Be("result");
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.Test/SampleLibrary.Test.csproj b/phase05-TDD/SampleLibrary/SampleLibrary.Test/SampleLibrary.Test.csproj
new file mode 100644
index 0000000..f5c06b5
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.Test/SampleLibrary.Test.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary.sln b/phase05-TDD/SampleLibrary/SampleLibrary.sln
new file mode 100644
index 0000000..e80fad1
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleLibrary", "SampleLibrary\SampleLibrary.csproj", "{A7B9FF7C-1BB7-4C74-AA7E-29E34D2116D4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleLibrary.Test", "SampleLibrary.Test\SampleLibrary.Test.csproj", "{E73D1629-E575-461B-81E6-2C6B7BF77E2F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleLibrary.Executable", "SampleLibraryExecutable\SampleLibrary.Executable.csproj", "{4DE12EAE-F70B-4953-A9C5-776B2DBA99E6}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A7B9FF7C-1BB7-4C74-AA7E-29E34D2116D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A7B9FF7C-1BB7-4C74-AA7E-29E34D2116D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A7B9FF7C-1BB7-4C74-AA7E-29E34D2116D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A7B9FF7C-1BB7-4C74-AA7E-29E34D2116D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E73D1629-E575-461B-81E6-2C6B7BF77E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E73D1629-E575-461B-81E6-2C6B7BF77E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E73D1629-E575-461B-81E6-2C6B7BF77E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E73D1629-E575-461B-81E6-2C6B7BF77E2F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4DE12EAE-F70B-4953-A9C5-776B2DBA99E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4DE12EAE-F70B-4953-A9C5-776B2DBA99E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4DE12EAE-F70B-4953-A9C5-776B2DBA99E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4DE12EAE-F70B-4953-A9C5-776B2DBA99E6}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {D4689DC4-8FD9-40A8-BFA4-2CE47D5E62FE}
+ EndGlobalSection
+EndGlobal
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IDataProcessor.cs b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IDataProcessor.cs
new file mode 100644
index 0000000..1c1bb4a
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IDataProcessor.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary.Abstraction
+{
+ internal interface IDataProcessor
+ {
+ List Process(Dictionary data);
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IDataProvider.cs b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IDataProvider.cs
new file mode 100644
index 0000000..454aa91
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IDataProvider.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary.Abstraction
+{
+ public interface IDataProvider
+ {
+ Dictionary GetAllDataFromSource(string source);
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IInvertedIndexConstructor.cs b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IInvertedIndexConstructor.cs
new file mode 100644
index 0000000..81a94e6
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IInvertedIndexConstructor.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary.Abstraction
+{
+ internal interface IInvertedIndexConstructor
+ {
+ public InvertedIndex Build(List processedData);
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IInvertedIndexQuerySearch.cs b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IInvertedIndexQuerySearch.cs
new file mode 100644
index 0000000..2c71681
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IInvertedIndexQuerySearch.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary.Abstraction
+{
+ public interface IInvertedIndexQuerySearch
+ {
+ public HashSet SearchQeury(InvertedIndex invertedIndex, QueryObj queryObj);
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IQueryInterpreter.cs b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IQueryInterpreter.cs
new file mode 100644
index 0000000..15b6d85
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IQueryInterpreter.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary.Abstraction
+{
+ public interface IQueryInterpreter
+ {
+ QueryObj Interpret(string query);
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IQueryWordCategorizer.cs b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IQueryWordCategorizer.cs
new file mode 100644
index 0000000..6a48e26
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IQueryWordCategorizer.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary.Abstraction
+{
+ public interface IQueryWordCategorizer
+ {
+ public List Categorize(string[] splittedQuery);
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IResultPrinter.cs b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IResultPrinter.cs
new file mode 100644
index 0000000..eaf2703
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IResultPrinter.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary.Abstraction
+{
+ public interface IResultPrinter
+ {
+ void Print(HashSet result);
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/ISourceReader.cs b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/ISourceReader.cs
new file mode 100644
index 0000000..3aae696
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/ISourceReader.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary.Abstraction
+{
+ internal interface ISourceReader
+ {
+ IReadOnlyCollection GetAllSourceNames(string sourcePath);
+
+ string ReadSource(string sourcePath);
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IWordFinder.cs b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IWordFinder.cs
new file mode 100644
index 0000000..2926e7f
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/Abstraction/IWordFinder.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary.Abstraction
+{
+ public interface IWordFinder
+ {
+ public HashSet Find(T source, List words);
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/DataProcessor.cs b/phase05-TDD/SampleLibrary/SampleLibrary/DataProcessor.cs
new file mode 100644
index 0000000..41b99d4
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/DataProcessor.cs
@@ -0,0 +1,35 @@
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ internal class DataProcessor : IDataProcessor
+ {
+ public List Process(Dictionary data)
+ {
+ if (data is null)
+ {
+ throw new ArgumentException(nameof(data));
+ }
+
+ List result = new List();
+
+ foreach (string sourceName in data.Keys)
+ {
+ string[] splittedData = data[sourceName].Split(new char[] { ' ' });
+ foreach (string word in splittedData)
+ {
+ EntityStructure entity = new EntityStructure(word, new HashSet { sourceName });
+ result.Add(entity);
+ }
+
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/DataProvider.cs b/phase05-TDD/SampleLibrary/SampleLibrary/DataProvider.cs
new file mode 100644
index 0000000..6da7510
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/DataProvider.cs
@@ -0,0 +1,37 @@
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ internal class DataProvider : IDataProvider
+ {
+ private ISourceReader sourceReader;
+
+ public DataProvider(ISourceReader sourceReader)
+ {
+ this.sourceReader = sourceReader ?? throw new ArgumentNullException(nameof(sourceReader));
+ }
+
+ public Dictionary GetAllDataFromSource(string source)
+ {
+ if (string.IsNullOrWhiteSpace(source))
+ {
+ throw new ArgumentException($"'{nameof(source)}' cannot be null or whitespace.", nameof(source));
+ }
+
+ var result = new Dictionary();
+
+ foreach (var file in sourceReader.GetAllSourceNames(source))
+ {
+ result.Add(file, sourceReader.ReadSource(file));
+
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/EntityStructure.cs b/phase05-TDD/SampleLibrary/SampleLibrary/EntityStructure.cs
new file mode 100644
index 0000000..6989810
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/EntityStructure.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ internal class EntityStructure
+ {
+ public string word;
+ public HashSet sourceNames;
+
+ public EntityStructure(string givenWord, HashSet givenSourceNames)
+ {
+ word = givenWord;
+ sourceNames = givenSourceNames;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndex.cs b/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndex.cs
new file mode 100644
index 0000000..07ee9d3
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndex.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ public class InvertedIndex
+ {
+ public Dictionary> table;
+
+ public InvertedIndex()
+ {
+ table = [];
+ }
+
+ public void Add(string word, HashSet sourceNames)
+ {
+ table.Add(word, sourceNames);
+ }
+
+ public bool Contains(string word)
+ {
+ return table.ContainsKey(word);
+ }
+
+ public void AddSourceNames(string word, HashSet newSourceNames)
+ {
+ var res = new HashSet(table[word].Union(newSourceNames));
+ table[word] = res;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexConstructor.cs b/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexConstructor.cs
new file mode 100644
index 0000000..114bc7c
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexConstructor.cs
@@ -0,0 +1,38 @@
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.XPath;
+
+namespace SampleLibrary
+{
+ internal class InvertedIndexConstructor : IInvertedIndexConstructor
+ {
+ public InvertedIndex Build(List processedData)
+ {
+ if (processedData is null)
+ {
+ throw new ArgumentNullException(nameof(processedData));
+ }
+
+ InvertedIndex result = new InvertedIndex();
+
+ foreach (var entity in processedData)
+ {
+ if (result.Contains(entity.word))
+ {
+ result.AddSourceNames(entity.word, entity.sourceNames);
+ }
+ else
+ {
+ result.Add(entity.word, entity.sourceNames);
+ }
+
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexNecessaryWordFinder.cs b/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexNecessaryWordFinder.cs
new file mode 100644
index 0000000..dc74a32
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexNecessaryWordFinder.cs
@@ -0,0 +1,43 @@
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ internal class InvertedIndexNecessaryWordFinder : IWordFinder
+ {
+ public HashSet Find(InvertedIndex source, List words)
+ {
+ if (source is null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ if (words is null)
+ {
+ throw new ArgumentNullException(nameof(words));
+ }
+
+ var result = new HashSet();
+
+ foreach(var word in words)
+ {
+ var search_result = source.table.GetValueOrDefault(word, []);
+
+ if (result.Count == 0)
+ {
+ result.UnionWith(search_result);
+ }
+ else
+ {
+ result.IntersectWith(search_result);
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexQuerySearch.cs b/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexQuerySearch.cs
new file mode 100644
index 0000000..35276c5
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexQuerySearch.cs
@@ -0,0 +1,40 @@
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ internal class InvertedIndexQuerySearch : IInvertedIndexQuerySearch
+ {
+ public HashSet SearchQeury(InvertedIndex invertedIndex, QueryObj queryObj)
+ {
+ if (invertedIndex is null)
+ {
+ throw new ArgumentNullException(nameof(invertedIndex));
+ }
+
+ if (queryObj is null)
+ {
+ throw new ArgumentNullException(nameof(queryObj));
+ }
+
+ InvertedIndexNecessaryWordFinder necessaryWordFinder = new();
+
+ HashSet necessarySources = necessaryWordFinder.Find(invertedIndex, queryObj.necessaryWords);
+
+ InvertedIndexUnnecessaryWordFinder unnecessaryWordFinder = new();
+
+ HashSet orSources = unnecessaryWordFinder.Find(invertedIndex, queryObj.orWords);
+ HashSet forbiddenSources = unnecessaryWordFinder.Find(invertedIndex, queryObj.forbiddenWords);
+
+ var result = new HashSet(necessarySources.Intersect(orSources));
+ var finalResult = new HashSet(result.Except(forbiddenSources));
+
+ return finalResult;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexUnnecessaryWordFinder.cs b/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexUnnecessaryWordFinder.cs
new file mode 100644
index 0000000..b336ce0
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/InvertedIndexUnnecessaryWordFinder.cs
@@ -0,0 +1,34 @@
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ internal class InvertedIndexUnnecessaryWordFinder : IWordFinder
+ {
+ public HashSet Find(InvertedIndex source, List words)
+ {
+ if (source is null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ if (words is null)
+ {
+ throw new ArgumentNullException(nameof(words));
+ }
+
+ var result = new HashSet();
+
+ foreach (var word in words)
+ {
+ result.UnionWith(source.table.GetValueOrDefault(word, []));
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/Properties/FileName.cs b/phase05-TDD/SampleLibrary/SampleLibrary/Properties/FileName.cs
new file mode 100644
index 0000000..bb49838
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/Properties/FileName.cs
@@ -0,0 +1,5 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("SampleLibrary.Test")]
+[assembly: InternalsVisibleTo("SampleLibrary.Executable")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
\ No newline at end of file
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/QueryInterpreter.cs b/phase05-TDD/SampleLibrary/SampleLibrary/QueryInterpreter.cs
new file mode 100644
index 0000000..5c637c7
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/QueryInterpreter.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SampleLibrary.Abstraction;
+
+namespace SampleLibrary
+{
+ public class QueryInterpreter : IQueryInterpreter
+ {
+ public QueryObj Interpret(string query)
+ {
+ if (string.IsNullOrEmpty(query))
+ {
+ throw new ArgumentException($"'{nameof(query)}' cannot be null or empty.", nameof(query));
+ }
+
+ string[] splittedQuery = query.Split(' ');
+
+ QueryNecessaryWordCategorizer necessaryCategorizer = new();
+ List necessaryWords = necessaryCategorizer.Categorize(splittedQuery);
+
+ QueryUnnecessaryWordCategorizer orWordCategorizer = new('+');
+ List orWords = orWordCategorizer.Categorize(splittedQuery);
+
+ QueryUnnecessaryWordCategorizer forbiddenCategorizer = new('-');
+ List forbiddenWords = forbiddenCategorizer.Categorize(splittedQuery);
+
+ QueryObj result = new(necessaryWords, orWords, forbiddenWords);
+
+ return result;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/QueryNecessaryWordCategorizer.cs b/phase05-TDD/SampleLibrary/SampleLibrary/QueryNecessaryWordCategorizer.cs
new file mode 100644
index 0000000..e6f346b
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/QueryNecessaryWordCategorizer.cs
@@ -0,0 +1,32 @@
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ public class QueryNecessaryWordCategorizer : IQueryWordCategorizer
+ {
+ public List Categorize(string[] splittedQuery)
+ {
+ if (splittedQuery is null)
+ {
+ throw new ArgumentNullException(nameof(splittedQuery));
+ }
+
+ List result = [];
+
+ foreach (string word in splittedQuery)
+ {
+ if (!word.StartsWith('+') && !word.StartsWith('-'))
+ {
+ result.Add(word);
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/QueryObj.cs b/phase05-TDD/SampleLibrary/SampleLibrary/QueryObj.cs
new file mode 100644
index 0000000..b5556af
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/QueryObj.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ public class QueryObj
+ {
+ public List necessaryWords;
+ public List orWords;
+ public List forbiddenWords;
+
+ public QueryObj(List givenNecessaryWords, List givenOrWords, List givenForbiddenWords)
+ {
+ this.necessaryWords = givenNecessaryWords;
+ this.orWords = givenOrWords;
+ this.forbiddenWords = givenForbiddenWords;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/QueryUnnecessaryWordCategorizer.cs b/phase05-TDD/SampleLibrary/SampleLibrary/QueryUnnecessaryWordCategorizer.cs
new file mode 100644
index 0000000..e15cad8
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/QueryUnnecessaryWordCategorizer.cs
@@ -0,0 +1,39 @@
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ public class QueryUnnecessaryWordCategorizer : IQueryWordCategorizer
+ {
+ private readonly char _delimiter;
+
+ public QueryUnnecessaryWordCategorizer(char _delimiter)
+ {
+ this._delimiter = _delimiter;
+ }
+
+ public List Categorize(string[] splittedQuery)
+ {
+ if (splittedQuery is null)
+ {
+ throw new ArgumentNullException(nameof(splittedQuery));
+ }
+
+ List result = [];
+
+ foreach (string word in splittedQuery)
+ {
+ if(word.StartsWith(_delimiter))
+ {
+ result.Add(word.Remove(0, 1));
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/ResultPrinter.cs b/phase05-TDD/SampleLibrary/SampleLibrary/ResultPrinter.cs
new file mode 100644
index 0000000..9eee8d4
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/ResultPrinter.cs
@@ -0,0 +1,34 @@
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ internal class ResultPrinter : IResultPrinter
+ {
+ private StringWriter _outputWriter;
+
+ public ResultPrinter(StringWriter outputWriter)
+ {
+ _outputWriter = outputWriter ?? throw new ArgumentNullException(nameof(outputWriter));
+ }
+
+ public void Print(HashSet result)
+ {
+ if (result is null)
+ {
+ throw new ArgumentNullException(nameof(result));
+ }
+
+ _outputWriter.WriteLine("Search result: ");
+
+ foreach(string entry in result)
+ {
+ _outputWriter.WriteLine(entry);
+ }
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/SampleLibrary.csproj b/phase05-TDD/SampleLibrary/SampleLibrary/SampleLibrary.csproj
new file mode 100644
index 0000000..fa71b7a
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/SampleLibrary.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/phase05-TDD/SampleLibrary/SampleLibrary/SourceReader.cs b/phase05-TDD/SampleLibrary/SampleLibrary/SourceReader.cs
new file mode 100644
index 0000000..8d791fd
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibrary/SourceReader.cs
@@ -0,0 +1,54 @@
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ public class SourceReader : ISourceReader
+ {
+ public IReadOnlyCollection GetAllSourceNames(string sourcePath)
+ {
+ List sources = [];
+
+ try
+ {
+ DirectoryInfo directoryInfo = new DirectoryInfo(sourcePath);
+
+ sources =
+ [
+ .. Directory.GetFiles(sourcePath, "*", SearchOption.TopDirectoryOnly),
+ ];
+
+
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message);
+ }
+
+ var readOnlyList = new ReadOnlyCollection(sources);
+
+ return readOnlyList;
+ }
+
+ public string ReadSource(string sourcePath)
+ {
+ string text = "";
+
+ try
+ {
+ text = File.ReadAllText(sourcePath);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Exception: " + e.Message);
+ }
+
+ return text;
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibraryExecutable/Program.cs b/phase05-TDD/SampleLibrary/SampleLibraryExecutable/Program.cs
new file mode 100644
index 0000000..5f3033d
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibraryExecutable/Program.cs
@@ -0,0 +1,46 @@
+using SampleLibrary.Abstraction;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Resources;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SampleLibrary
+{
+ public class Program
+ {
+ public static void Main()
+ {
+ const string sourceDirectory = "C:\\Users\\mhda1\\Documents\\VSCode\\Fall1402-TwoWeek-InternShip-Backend\\EnglishData\\";
+
+ SourceReader sourceReader = new();
+ DataProvider dataProvider = new(sourceReader);
+
+ Dictionary data = dataProvider.GetAllDataFromSource(sourceDirectory);
+
+ DataProcessor dataProcessor = new();
+ List processedData = dataProcessor.Process(data);
+
+ InvertedIndexConstructor invertedIndexConstructor = new();
+
+ InvertedIndex invertedIndex = invertedIndexConstructor.Build(processedData);
+
+ const string givenQuery = "get help +illness +disease -cough";
+
+ QueryInterpreter queryInterpreter = new();
+
+ QueryObj query = queryInterpreter.Interpret(givenQuery);
+
+ InvertedIndexQuerySearch querySearch = new InvertedIndexQuerySearch();
+
+ var searchResult = querySearch.SearchQeury(invertedIndex, query);
+
+ foreach(var res in searchResult)
+ {
+ Console.WriteLine(res);
+ }
+ }
+ }
+}
diff --git a/phase05-TDD/SampleLibrary/SampleLibraryExecutable/SampleLibrary.Executable.csproj b/phase05-TDD/SampleLibrary/SampleLibraryExecutable/SampleLibrary.Executable.csproj
new file mode 100644
index 0000000..435612a
--- /dev/null
+++ b/phase05-TDD/SampleLibrary/SampleLibraryExecutable/SampleLibrary.Executable.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+