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 + + + + + + +