![Hash Collision](https://quera.org/qbox/view/beB7xMQS8D/car-hash-collision.jpg)
محمدرضا علاقهی زیادی به بهینه بودن برنامهها دارد. او که در زمینهی الگوریتم بسیار خفن است، میداند که استفاده از *hash* برای نگهداری دادهها در برخی مواقع نه تنها کمکی به افزایش پرفورمنس نمیکند، بلکه پرفورمنس برنامه را کاهش میدهد. نیما که حرف محمدرضا را قبول ندارد، از او درخواست کدی کرده است تا میزان *hash collision* ها را ببیند. محمدرضا نیز این کار را به شما محول کرده است.
در این سؤال، یک `HashSet` یا `HashMap` به شما داده میشود. شما باید تعداد یکتای *hash code* های مقادیر موجود در `HashSet` یا کلیدهای موجود در `HashMap` را محاسبه کرده و برگردانید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/50200/download_problem_initial_project/169413/) دانلود کنید. ساختار فایلهای پروژه بهصورت زیر است:
```
hash-collision-checker
├── HashCollisionChecker.java
└── HashCollisionCheckerSampleTest.java
```
## کلاس `HashCollisionChecker`
این کلاس شامل دو متد زیر است که باید آنها را پیادهسازی کنید:
### محاسبهی تعداد یکتای *hash code* های موجود در یک `HashSet`
```java
public static <T> int countOfUniqueHashCodes(HashSet<T> set)
```
این متد را طوری پیادهسازی کنید که با دریافت یک `HashSet`، تعداد یکتای *hash code* های مقادیر موجود در آن را برگرداند.
### محاسبهی تعداد یکتای *hash code* های موجود در یک `HashSet`
```java
public static <K, V> int countOfUniqueHashCodes(HashMap<K, V> map)
```
این متد را طوری پیادهسازی کنید که با دریافت یک `HashMap`، تعداد یکتای *hash code* های کلیدهای موجود در آن را برگرداند.
# مثال
با اجرای متد `main` موجود در کلاس `HashCollisionChecker`:
```java
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("c#c#c#c#c#c#bBc#c#c#c#bBc#");
set.add("abcd");
set.add("c#c#c#c#c#c#bBc#c#c#c#c#aa");
set.add("1234");
set.add("c#c#c#c#c#c#bBc#c#c#c#c#bB");
System.out.println(countOfUniqueHashCodes(set)); // 3
HashMap<String, Integer> map = new HashMap<>();
map.put("c#c#c#c#c#c#c#aaaaaaaabBbB", 14);
map.put("c#c#c#c#c#c#c#aaaaaaaac#c#", 12);
map.put("c#c#c#c#c#c#c#aaaaaaaac#cc", 16);
System.out.println(countOfUniqueHashCodes(map)); // 2
}
```
خروجی باید بهصورت زیر باشد:
```
3
2
```
# نکات
+ برای دریافت *hash code* آبجکتها، میتوانید از متد `hashCode` استفاده کنید.
+ تستهای نمونهی سؤال در کلاس `HashCollisionCheckerSampleTest` موجود هستند. با افزودن *JUnit* به *classpath* پروژه، میتوانید آنها را اجرا کنید.
# آنچه باید آپلود کنید
پس از پیادهسازی متدها، فایل `HashCollisionChecker.java` را آپلود کنید.
هششمار
*سلیب* که در خلوت خود در حال تفکری عمیق درباره اردرهای مختلف بود، به این فکر فرو رفت که متدهایی که پیادهسازی کرده چقدر طول میکشند و چقدر بهینه کد میزند. برای همین سراغ پیادهسازی متدی برای سنجش زمان موردنیاز برای اجرای هر متد خود رفت. در ادامه به توضیح این متد میپردازیم.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/50200/download_problem_initial_project/169412/) دانلود کنید.
ساختار فایلهای پروژه بهصورت زیر است:
```
execution-time
└── ExecutionTimeCalculator.java
```
شما باید متد `measureExecutionTime` موجود در کلاس `ExecutionTime` را مطابق با خواست سؤال پیادهسازی کنید. امضای این متد بهصورت زیر است:
```java ExecutionTimeCalculator.java java
import java.math.BigDecimal;
public class ExecutionTimeCalculator {
public static long functionToCall() {
long returnValue = 0;
for (long i = 1; i < 1000L * 1000 * 1000 * 10; i++) {
returnValue += i;
}
return returnValue;
}
public static BigDecimal measureExecutionTime(Runnable function) {
// TODO: Implement
return null;
}
public static void main(String[] args) {
System.out.println(measureExecutionTime(ExecutionTimeCalculator::functionToCall));
}
}
```
این متد باید `Runnable`ای که به آن ورودی داده میشود را اجرا کند و زمان مورد نیاز برای اجرای آن را **با واحد ثانیه** در متغیری از جنس `BigDecimal` **دقیقاً با ۵ رقم پس از اعشار** برگرداند. برای کاهش ارقام اعشار باید عدد را از پنجمین رقم در ممیز گرد کنید (برای این کار، میتوانید از `RoundingMode.HALF_UP` استفاده کنید).
# نکات
+ توجه داشته باشید که خروجی شما **باید دقیقاً شامل ۵ رقم پس از اعشار باشد**.
+ عدد محاسبهشده را باید گرد کنید.
+ زمان محاسبهشده باید **با واحد ثانیه باشد**.
+ متد `run` در شیء `Runnable` متد را اجرا میکند.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، فایل `ExecutionTimeCalculator.java` را آپلود کنید.
زمان اجرا
![Cache](https://quera.org/qbox/view/zX6b6YCwyp/cache.png)
طبق [آماری که گوگل در سال ۲۰۱۶ منتشر کرد](https://www.blog.google/products/admanager/increase-speed-of-your-mobile-site-wi/)، ٪۵۳ از بازدیدکنندگان، یک وبسایت را در صورتی که لود آن بیش از ۳ ثانیه طول بکشد رها میکنند! این آمار، ابوالفضل را به این فکر فرو برده که سرعت لود یک وبسایت به چه اندازه روی حس رضایت کاربران آن وبسایت تأثیر دارد. او که حالا هشتگ `#SpeedMatters` را در شبکههای اجتماعی ترند کرده، به این فکر افتاده که سرعت لود برنامهاش را با استفاده از *cache* بالا ببرد. او برای این کار نیاز به یک دولوپر دیگر دارد و از شما کمک خواسته است.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/50200/download_problem_initial_project/169414/) دانلود کنید. ساختار فایلهای پروژه بهصورت زیر است:
```
cache
├── models
│ ├── CacheData.java
│ ├── Comment.java
│ ├── ExportCacheDTO.java
│ └── Photo.java
├── services
│ ├── Cache.java
│ └── CacheItem.java
└── Application.java
```
شما باید کلاس `Cache` را طوری کامل کنید تا خواستههایی را که در ادامه مطرح میشود را برآورده کند.
ساختار اولیهی این کلاس بهصورت زیر است:
```java services/Cache.java java
package services;
import java.util.ArrayList;
import java.util.List;
public class Cache {
private static final int TTL = 2;
private List<CacheItem> data = new ArrayList<>();
public List<CacheItem> getAll() {
return data;
}
}
```
همانطور که مشخص است، *cache* ما قرار است آیتمهای مختلفی را توسط کلاس `CacheItem` درون خود نگه دارد. این آیتمها فعلاً شامل دو کلاس `Photo` و `Comment` میشوند. همچنین این آیتمها زمان مجازی دارند که توسط پراپرتی `TTL` (واحد روز) مشخص میشود. در اولین گام باید کلاس `CacheItem` را کامل کنید.
## اینترفیس `CacheData`
این اینترفیس، بیانگر دادههای موجود در *cache* است. متد زیر در این اینترفیس تعریف شده است:
```java
long getID();
```
کلاسهای `Comment` و `Photo` این اینترفیس را پیادهسازی میکنند.
## کلاس `CacheItem`
این کلاس بهصورت *generic* بوده و مشخصکنندهی آیتمهایی است که درون *cache* نگه داشته میشوند. این کلاس باید شامل دو پراپرتی `data` از نوع `T` (بهصورت *generic*) و `createdAt` از نوع `LocalDateTime` باشد.
از پراپرتی `createdAt` برای بررسی مجاز بودن یک آیتم *cache* استفاده میشود. زمان مجاز زنده بودن یک آیتم درون *cache* طبق مقدار پراپرتی `TTL` در کلاس `Cache` برابر با **۲ روز** است.
## کلاس `Cache`
همانطور که گفته شد، مهمترین بخش برنامه، این کلاس است. شما باید متدهایی را که در ادامه ذکر میشود را در این کلاس پیادهسازی کنید:
+ متد `add`: این متد که ورودی آن `CacheItem` است، باید بتواند یک آیتم را به *cache* اضافه کند. **دقت کنید** که آیتمی با دیتای تکراری نباید در *cache* وجود داشته باشد. در اینصورت، آیتم قبلی باید از لیست آیتمها حذف شده و آیتم جدید درج شود.
+ متد `remove`: این متد که ورودی آن `CacheItem` است، باید بتواند یک آیتم را از *cache* حذف کند.
دو متد بالا را باید بتوان به روش *method chaining* صدا زد. مثال:
```java
cache.add(item1).remove(item1).add(item2);
```
+ متد `clear`: این متد تمام آیتمهای درون *cache* را حذف میکند.
+ متد `getAll`: این متد تمام آیتمهای مجاز درون *cache* را در قالب لیستی از `CacheItem`ها برمیگرداند.
+ متد `findByID`: این متد یک پارامتر ورودی از نوع `long` دارد و باید لیستی از `CacheItem`های مجاز را در صورتی که پراپرتی `ID` آیتم درون *cache* با پارامتر ورودی برابر بود برگرداند.
+ متد `filter`: این متد دو امضا بهصورت زیر دارد:
+ اولین امضای این متد دو پارامتر ورودی دارد. اولین پارامتر، نام پراپرتی از نوع `String` است. دومین پارامتر، یک عبارت باقاعده (*Regex*) بهصورت `String` است. در این حالت، **تضمین میشود** که پراپرتیای با نام ذکرشده در همهی دادههای موجود در *cache* تعریف شده است.
+ دومین امضای این متد نیز مانند امضای اول است، فقط پارامتر نوع کلاس بهعنوان اولین پارامتر به این متد اضافه شده و مشخصکنندهی نوع کلاسی است که باید جستوجو فقط در دادههای از نوع کلاس واردشده صورت گیرد. در این حالت، **تضمین میشود** که پراپرتیای با نام ذکرشده در کلاس ذکرشده تعریف شده است.
کاری که این متد انجام میدهد آن است که باید در پراپرتی گفتهشده، عبارت باقاعده را جستوجو کند و لیستی از `CacheItem`های مجاز را برگرداند. به عنوان مثال فرض کنید دو آیتم زیر درون *cache* وجود دارند:
```
Photo{ID=1, name='tree.png', path='/production', size=10.0}
Photo{ID=2, name='weather.jpg', path='/production', size=10.0}
```
حال متد `filter` را بهصورت زیر صدا میزنیم و میخواهیم عکسهای با فرمت `png` را فیلتر کنیم:
```java Application.java java
cache.filter(Photo.class, "name", ".png")
```
خروجی باید بهصورت زیر باشد:
```
Photo{ID=1, name='tree.png', path='/production', size=10.0}
```
+ متد `export`: از این متد نیز قرار است برای بهدست آوردن آماری در رابطه با آیتمهای درون *cache* استفاده کنیم. خروجی این متد لیستی از `ExportCacheDTO`ها است. مثالی از خروجی این متد:
```
[
ExportCacheDTO{name='Photo', size=2},
ExportCacheDTO{name='Comment', size=3}
]
```
این خروجی به این معنی است که ۲ آیتم از نوع `Photo` و ۳ آیتم از نوع `Comment` درون *cache* وجود دارد.
# نکات
+ آیتمی با دیتای تکراری نباید در *cache* وجود داشته باشد.
+ اگر قرار است در بعضی از متدها آیتمی را برگردانید، آن آیتم حتماً باید مجاز باشد (منقضی نشده باشد).
+ ترتیب برگرداندن آیتمها باید مطابق با ترتیب اضافه شدن آنها به *cache* باشد.
+ شما تنها مجاز به اعمال تغییرات در فایلهای `services/Cache.java` و `services/CacheItem.java` هستید.
+ تستهای نمونهی برنامه در کلاس `CacheSampleTest` موجود هستند. میتوانید آنها را پس از افزودن *JUnit* به *classpath* در سیستم خود اجرا کنید.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، پوشهی `services` را زیپ کرده و آپلود کنید (خود پوشه نیز در فایل زیپ موجود باشد).
کَشمَش
![معدن جام](https://quera.org/qbox/view/nrr8q7mM6U/world-cup-crawler.png)
علی در شبکهای اجتماعی، کانالهایی برای فوتبالیها دارد. او میخواهد برنامهی زمانبندی بازیهای فوتبال پیش رو را در کانالهایش بهصورت خودکار ارسال کند. همچنین، میخواهد پیش از شروع هر بازی، یک پیام یادآوری بهصورت خودکار بفرستد. برای این کار، او باید اطلاعات بازیها را داشته باشد. از شما میخواهیم برنامهای بنویسید که این اطلاعات را از صفحات وب جمعآوری کند.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/50200/download_problem_initial_project/169411/) دانلود کنید. ساختار فایلهای پروژه بهصورت زیر است:
```
world-cup-crawler
├── www
│ ├── ahmad-bin-ali-stadium.html
│ ├── al-bayt-stadium.html
│ ├── al-janoub-stadium.html
│ ├── al-thumama-stadium.html
│ ├── education-city-stadium.html
│ ├── index.html
│ ├── khalifa-international-stadium.html
│ ├── lusail-iconic-stadium.html
│ └── stadium-974.html
├── Country.java
├── HttpHandler.java
├── Match.java
├── Server.java
├── Stadium.java
├── Team.java
├── WorldCupCrawler.java
└── WorldCupCrawlerSampleTest.java
```
## کلاس `Country`
هر نمونه از این کلاس، بیانگر یک کشور است. هر `Country` دارای یک `name` از نوع `String` بوده که بیانگر نام کشور است. این کلاس از قبل بهطور کامل پیادهسازی شده است.
## کلاس `Stadium`
هر نمونه از این کلاس، بیانگر یک استادیوم است. هر `Stadium` دارای ویژگیهای زیر است:
+ نام (`name`) از نوع `String`
+ کشور (`country`) از نوع `Country`
+ ظرفیت (`capacity`) از نوع `int`
این کلاس از قبل بهطور کامل پیادهسازی شده است.
## کلاس `Team`
هر نمونه از این کلاس، بیانگر یک تیم است. هر `Team` دارای یک `name` از نوع `String` بوده که بیانگر نام کشور است. این کلاس از قبل بهطور کامل پیادهسازی شده است.
## کلاس `Match`
هر نمونه از این کلاس، بیانگر یک مسابقه است. هر `Match` دارای ویژگیهای زیر است:
+ زمان بازی (`time`) از نوع `Date`
+ گروه (`group`) از نوع `String`
+ تیم میزبان (`team1`) از نوع `Team`
+ تیم میزبان (`team2`) از نوع `Team`
+ استادیوم (`stadium`) از نوع `Stadium`
این کلاس از قبل بهطور کامل پیادهسازی شده است.
## کلاس `WorldCupCrawler`
این کلاس، وظیفهی استخراج اطلاعات بازیها از صفحات وب را برعهده دارد. عملیات استخراج دادهها باید تا حد امکان با استفاده از چند *thread* انجام شود تا سرعت بیشتری داشته باشد. این کلاس در کانستراکتور خود یک آبجمت از نوع `URL` دریافت میکند که بیانگر آدرس صفحهی لیست بازیها است. نمونهای از صفحهی لیست بازیها در فایل `www/index.html` موجود است.
+ متد استاتیک `getStadium` این کلاس را طوری پیادهسازی کنید که با دریافت یک `URL`، اطلاعات یک استادیوم را از `URL` دادهشده استخراج کرده و در قالب یک آبجکت از نوع `Stadium` برگرداند. نمونهای از نحوهی فراخوانی این متد:
```java
WorldCupCrawler.getStadium(new URL("http://localhost/al-bayt-stadium.html"))
```
+ متد `getMatches` را طوری پیادهسازی کنید که با ارسال درخواست به آدرس واردشده در کانستراکتور، اطلاعات بازیها را در قالب یک `List` از `Match`ها برگرداند.
**نکته:** تستهای نمونهی سؤال در کلاس `WorldCupCrawlerSampleTest` موجود هستند. با افزودن *JUnit* به *classpath* پروژه، میتوانید آنها را اجرا کنید. کلاسهای `Server` و `HtttpHandler` نیز جزو کلاسهای کمکی برای تست هستند.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، فایل `WorldCupCrawler.java` را آپلود کنید.
جامکاوی
*سلیب* که از امنیت کوئرا به ستوه آمده بود، با *نیما* (اسطورهی امنیت در سطح بینالمللی) در اینباره صحبتی را شروع کرد. *نیما* به این نتیجه رسید که کوئرا نیاز به یک اقدام امنیتی در ورود به حساب کاربری دارد. پیشنهاد او تأیید از طریق *SMS* بود و تأکید اکیدی بر زمان انقضای آنها داشت. به نظر او هر کد تأیید باید فقط برای ۱۰ ثانیه معتبر میبودند و پس از گذشت بیش از ۱۰ ثانیه، کد دیگر مورد قبول نباشد. *سلیب* از آنسو تاکید بر ماندگاری دیتاها داشت. به نظر او نیاز بود تا تمامی اطلاعات را به فرمت زیر در یک فایل *CSV* نگهداری کند:
```csv sample-data.csv csv
Phone,Code,Expiration
```
در ستون اول شماره مورد نظر، ستون دوم کد ساخته شده برای شماره و در ستون سوم اعتبار کد را نمایش میدهد، در ادامه به بیان جزئیات بیشتر سؤال میپردازیم.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/50200/download_problem_initial_project/169410/) دانلود کنید.
ساختار فایلهای پروژه بهصورت زیر است:
```
SMSAuthentication
├── pom.xml
├── src
│ ├── main
│ │ └── java
│ │ └── org
│ │ └── quera
│ │ └── smsauthentication
│ │ └── <mark class="red" title="شما تنها مجاز به تغیر این فایل هستید.">SMSAuthentication.java</mark>
│ └── test
│ └── java
│ └── org
│ └── quera
│ └── smsauthentication
│ └── SMSAuthenticationSampleTest.java
└── test.csv
```
شما باید متدهای `generateCode`، `sendAuthenticationSMS`، `loadData`، `verifyCode` موجود در کلاس `SMSAuthentication` را مطابق با خواستههای سؤال پیادهسازی کنید. نمای کلی فایل مورد نظر به شکل زیر است:
```java SMSAuthentication.java java
package org.quera.smsauthentication;
import ...
public class SMSAuthentication {
public static String generateCode() {
// TODO: Implement
}
public static String sendAuthenticationSMS(String phone, String path) {
// TODO: Implement
}
public static List<String[]> loadData(String path) {
// TODO: Implement
}
public static boolean verifyCode(String code, String phone, String path) {
// TODO: Implement
}
public static void main(String[] args) {
System.out.println(sendAuthenticationSMS("09381358888", "test.csv"));
Scanner sc = new Scanner(System.in);
String code = sc.next();
System.out.println(verifyCode(code,"09381358888","test.csv"));
}
}
```
## متد `generateCode`
این متد باید یک رشته تصادفی به **طول ۵** بسازد و مقدار آن را برگرداند. در این رشته میتوانید از **فقط متشکل از ارقام انگلیسی** استفاده کنید. نمونهی اجرای متد در زیر آمده است:
```java Terminal terminal
>>> generateCode()
40142
>>> generateCode()
18272
>>> generateCode()
00168
```
## متد `sendAuthenticationSMS`
این متد یک رشته به عنوان شماره و رشتهای دیگر به عنوان آدرس فایلی که باید اطلاعات را در آن ذخیره کند، به عنوان ورودی دریافت میکند. خروجی این متد باید رشتهای به فرمت `JSON` باشد. در خروجی `JSON` مورد نظر باید کلید و مقدارهای زیر را داشته باشد:
* کلید `phone` برابر با مقدار شماره مورد نظر خواهد بود.
* کلید `code` برابر با کد ساخته شده برای شماره مورد نظر خواهد بود.
* کلید `exp` برابر با زمانی که کد مورد نظر منقضی میشود است، زمان در این سؤال به فرمت `"yyyy/MM/dd-HH:mm:ss"` استفاده میشود.
این متد باید این محتوا را درون فایل *CSV* موجود در آدرس `path` ذخیره کند. خروجی و رفتار خواسته شده در بخش مثال با جزئیات بیشتری نمایش داده شده است.
### مثال
```java Terminal terminal
>>> sendAuthenticationSMS("09381354444", "test.csv")
{"phone":"09381354444","code":"06505","exp":"2022/10/04-18:02:15"}
>>> sendAuthenticationSMS("09381354443", "test.csv")
{"phone":"09381354443","code":"26052","exp":"2022/10/04-18:07:39"}
>>> sendAuthenticationSMS("09381354444", "test.csv") // duplicate
{"phone":"09381354444","code":"00300","exp":"2022/10/04-19:02:48"}
```
محتوای چند خط آخر فایل `test.csv` پس از اجرای بالا برابر با زیر خواهد بود:
```csv test.csv csv
Phone,Code,Expiration
<mark class="red" title="محتواهای قبلی موجود در فایل">...</mark>
09381354444,06505,2022/10/04-18:02:15
09381354443,26052,2022/10/04-18:07:39
09381354444,00300,2022/10/04-19:02:48
```
## متد `loadData`
این متد یک رشته به عنوان آدرس فایلی که باید اطلاعات آن را بخواند، به عنوان ورودی دریافت میکند. خروجی این متد لیستی از آرایهای از `String`ها خواهد بود که برابر با مقادیر موجود در فایل *CSV* ورودی متد است.
### مثال
برای مثال فرض کنید محتوای فایل *CSV* بهصورت زیر باشد:
```csv test.csv csv
Phone,Code,Expiration
09381354444,06505,2022/10/04-18:02:15
09381354443,26052,2022/10/04-18:07:39
09381354444,00300,2022/10/04-19:02:48
```
اگر متد را بهصورت زیر فراخوانی کنیم
```java snippet java
List<String[]> allData = loadData("test.csv");
```
و بخواهیم خروجی متد را بهصورت زیر چاپ کنیم:
```java snippet java
for(String[] data : allData) {
for(String attr : data) {
System.out.print(attr + " ");
}
System.out.println();
}
```
خروجی بهصورت زیر خواهد بود
```
09381354444 06505 2022/10/04-18:02:15
09381354443 26052 2022/10/04-18:07:39
09381354444 00300 2022/10/04-19:02:48
```
**دقت کنید که خط اول** (یا همان `Phone,Code,Expiration`) **در خروجی متد وجود ندارد**.
## متد `verifyCode`
این متد یک رشته به عنوان کد، یک رشته به عنوان شماره و رشتهای دیگر به عنوان آدرس فایلی که باید اطلاعات را از آن بخواند، به عنوان ورودی دریافت میکند. خروجی این متد یک `boolean` است که در صورتی که کد با شماره مورد نظر تطابق داشت و **کد منقضی نشده بود،** باید مقدار `true` برگرداند و در غیر این صورت مقدار `false` برگرداند.
# نکات
+ شما در این سؤال مجاز به تغییر فایل `pom.xml` نیستید.
+ برای کار با `JSON`، تنها مجاز به استفاده از کتابخانه `opencsv` هستید.
+ برای کار با *CSV* ، تنها مجاز به استفاده از کتابخانه `jackson-databind` هستید.
+ به جز کتابخانههای موجود در `pom.xml`، تنها مجاز به استفاده از کتابخانههای استاندارد جاوا هستید.
+ هر کد تنها **ده ثانیه** زمان دارد.
+ **دقت کنید** که تاریخ و زمان انقضای کد را باید دقیقا برابر با ۱۰ ثانیه پس از تولید کد (یا فراخوانی متد `sendAuthenticationSMS`) قرار دهید.
+ شما مجاز به عوض کردن فرمت ذخیرهسازی اطلاعات در فایل *CSV* نیستید و باید دقیقا برابر با فرمت خواسته شده، اطلاعات را ذخیره سازی کنید.
+ داوری این سؤال، بیشتر از باقی مسائل طول میکشد.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، فایل تمامی پروژه را زیپ کنید و آن را آپلود کنید. فایل زیپ شما باید بهصورتی باشد که پس از خروج از حالت فشرده، پوشهی `src` مشاهده شود.
سرویس احراز هویت پیامکی
![Nginx](https://quera.org/qbox/view/tBWSXd1BII/nginx-logo.png)
اخیراً وبسایت شخصی نیما مورد حملات مشکوک قرار گرفته است. او در حال بررسی لاگهای وبسرور سایتش است تا دریابد این حملات از کدام سمت بودهاند، اما برای این کار از یک ویرایشگر متنی ساده استفاده میکند. او میخواهد بتواند روی لاگها فیلتر اعمال کند تا جستوجوی هدفمندی داشته باشد. از شما میخواهیم چنین برنامهای را برای او پیادهسازی کنید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/50200/download_problem_initial_project/169409/) دانلود کنید. ساختار فایلهای پروژه بهصورت زیر است:
```
nginx-log-parser
├── exceptions
│ ├── LineDoesNotMatchException.java
│ ├── PropertyNotFoundException.java
│ └── TransformerNotFoundException.java
├── Condition.java
├── Log.java
├── LogList.java
├── LogParser.java
├── LogParserSampleTest.java
└── LogPropertyTransformerRepository.java
```
## بستهی `exceptions`
این بسته شامل استثناهای موردنیاز برنامه است. همهی کلاسهای موجود در این بسته از کلاس `RuntimeException` ارثبری میکنند.
## کلاس `Log`
هر نمونه از این کلاس بیانگر لاگ یک درخواست است. هر لاگ شامل یک یا چند ویژگی است. این کلاس از قبل بهطور کامل پیادهسازی شده و شامل متدهای زیر است:
+ متد `setProperty(String name, String value)`: این متد با دریافت نام و مقدار یک ویژگی، آن ویژگی را به ویژگیهای لاگ اضافه میکند.
+ متد `getProperty(String name)`: این متد با دریافت نام یک ویژگی، مقدار آن ویژگی را برمیگرداند. در صورتی که ویژگیای با نام واردشده موجود نباشد، یک استثنا از نوع `PropertyNotFoundException` پرتاب میشود.
## کلاس `LogPropertyTransformerRepository`
بعضی از ویژگیهای لاگها (مانند کد وضعیت پاسخ و زمان ارسال درخواست) ماهیت عددی دارند. این کلاس، مجموعهای از `Function<String, Long>` را در خود نگهداری میکند تا با استفاده از آنها، بتوان مقدار عددی معادل بعضی از ویژگیهای لاگها را دریافت کرد. این کلاس از قبل بهطور کامل پیادهسازی شده و شامل متدهای زیر است:
+ متد `addTransformer(String propertyName, Function<String, Long> transformer)`: این متد با دریافت نام یک ویژگی و یک تابع برای تبدیل مقدار رشتهای ویژگی به مقدار عددی، آن تابع را در `Map` موجود در کلاس ذخیره میکند.
+ متد `getTransformer(String propertyName)`: این متد با دریافت نام یک ویژگی، تابع مبدل مقدار آن ویژگی به مقدار عددی را برمیگرداند. در صورتی که تابعی برای ویژگی موردنظر تعریف نشده باشد، یک استثنا از نوع `TransformerNotFoundException` پرتاب میشود.
## کلاس `Condition`
هر نمونه از این کلاس، بیانگر یک شرط (ساده یا مرکب) برای فیلتر کردن لاگها است. پراپرتیهای موردنیاز خود را در این کلاس تعریف کرده و متدهای زیر را در آن پیادهسازی کنید:
+ متد استاتیک `equal(String propertyName, String value)` را طوری پیادهسازی کنید که با دریافت نام و مقدار یک ویژگی، یک نمونه از کلاس `Condition` برگرداند که اطلاعات مربوط به شرط معادل بودن ویژگی با مقدار موردنظر در آن موجود باشد.
+ متد استاتیک `notEqual(String propertyName, String value)` را طوری پیادهسازی کنید که با دریافت نام و مقدار یک ویژگی، یک نمونه از کلاس `Condition` برگرداند که اطلاعات مربوط به شرط معادل نبودن ویژگی با مقدار موردنظر در آن موجود باشد.
+ متد استاتیک `in(String propertyName, String... values)` را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و تعدادی مقدار، یک نمونه از کلاس `Condition` برگرداند که اطلاعات مربوط به شرط معادل بودن ویژگی با حداقل یکی از مقادیر واردشده در آن موجود باشد.
+ متد استاتیک `in(String propertyName, String... values)` را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و تعدادی مقدار، یک نمونه از کلاس `Condition` برگرداند که اطلاعات مربوط به شرط معادل نبودن ویژگی با هیچ یک از مقادیر واردشده در آن موجود باشد.
+ متد استاتیک `lessThan(String propertyName, Long value)` را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاس `Condition` برگرداند که اطلاعات مربوط به شرط کوچکتر بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیادهسازی این متد، از کلاس `LogPropertyTransformerRepository` استفاده کنید.
+ متد استاتیک `lessOrEqual(String propertyName, Long value)` را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاس `Condition` برگرداند که اطلاعات مربوط به شرط کوچکتر یا مساوی بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیادهسازی این متد، از کلاس `LogPropertyTransformerRepository` استفاده کنید.
+ متد استاتیک `greaterThan(String propertyName, Long value)` را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاس `Condition` برگرداند که اطلاعات مربوط به شرط بزرگتر بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیادهسازی این متد، از کلاس `LogPropertyTransformerRepository` استفاده کنید.
+ متد استاتیک `greaterOrEqual(String propertyName, Long value)` را طوری پیادهسازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاس `Condition` برگرداند که اطلاعات مربوط به شرط بزرگتر یا مساوی بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیادهسازی این متد، از کلاس `LogPropertyTransformerRepository` استفاده کنید.
+ متد `and(Condition condition)` را طوری پیادهسازی کنید که با دریافت یک شرط، یک نمونه از کلاس `Condition` برگرداند که شرط آن برابر با *AND* منطقی شرط فعلی و شرط واردشده باشد.
+ متد `or(Condition condition)` را طوری پیادهسازی کنید که با دریافت یک شرط، یک نمونه از کلاس `Condition` برگرداند که شرط آن برابر با *OR* منطقی شرط فعلی و شرط واردشده باشد.
+ متد استاتیک `not(Condition condition)` را طوری پیادهسازی کنید که با دریافت یک شرط، یک نمونه از کلاس `Condition` برگرداند که شرط آن برابر با *NOT* منطقی شرط فعلی و شرط واردشده باشد.
+ متد `test(Log log)` را طوری پیادهسازی کنید که با دریافت یک لاگ، در صورتی که لاگ در شرایط فعلی صدق کند، مقدار `true` و در غیر اینصورت، مقدار `false` را برگرداند.
**نکته:** متدهای `and`، `or` و `not` را باید بهگونهای پیادهسازی کنید که نمونههای جدیدی از کلاس `Condition` ایجاد شوند. در واقع، نمونههای کلاس `Condition` باید *immutable* باشند.
## کلاس `LogList`
این کلاس، یک `List` از لاگها را در خود نگهداری میکند.
+ متد `all` این کلاس را طوری پیادهسازی کنید که یک `LogList` شامل یک `List` جدید که لاگهای موجود در آبجکت فعلی در آن قرار گرفتهاند ایجاد کرده و برگرداند.
+ متد `where` این کلاس را طوری پیادهسازی کنید که با دریافت یک `Condition`، یک `LogList` شامل یک `List` جدید که لاگهای موجود در آبجکت فعلی در آن قرار گرفتهاند و در شرط واردشده صدق میکنند ایجاد کرده و برگرداند. ترتیب لاگهای موجود در لیست را نباید تغییر دهید.
## کلاس `LogParser`
وظیفهی این کلاس، پارس کردن رشتهی لاگها است. لاگها یک *schema* مشخص دارند که در قالب یک رشته به کانستراکتور این کلاس داده میشود. *schema* شامل تعدادی متغیر نیز هست که با `$` آغاز میشوند. مثالی از یک *schema*:
```
$remote_addr - $remote_user [$time_local] "$request" $status $bytes_sent "$http_referer" "$http_user_agent"
```
در اینصورت، لاگهای پارسشده باید شامل ویژگیهای `remote_addr`، `remote_user` و... باشند.
نمونهای از یک لاگ که طبق فرمت *schema* بالا است:
```
127.0.0.1 - - [07/Jul/2018:17:37:28 +0200] "GET /7d32ce87648a4050faca.hot-update.json HTTP/1.1" 200 43 "http://test.local/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0"
```
+ متد `parseLog(String line)` این کلاس را طوری پیادهسازی کنید که با دریافت یک رشتهی یکخطی، یک `Log` متناظر با لاگ واردشده برگرداند. در صورتی که لاگ واردشده با *schema* دادهشده مطابقت نداشت، یک استثنا از نوع `LineDoesNotMatchException` پرتاب کنید. ترتیب ویژگیهای `Log` خروجی باید مطابق ترتیب متغیرهای موجود در *schema* باشد.
+ متد `parseLogs(String logs)` را طوری پیادهسازی کنید که با دریافت یک رشتهی یک یا چند خطی، یک `LogList` متناظر با لاگهای واردشده برگرداند. برای پیادهسازی این متد، از متد `parseLog(String line)` استفاده کنید.
+ متد `parseLogs(File file)` را طوری پیادهسازی کنید که با دریافت یک فایل، یک `LogList` متناظر با لاگهای موجود در آن برگرداند. برای پیادهسازی این متد، از متد `parseLogs(String logs)` استفاده کنید.
# مثال
با اجرای متد `main` موجود در کلاس `LogParser`:
```java
public static void main(String[] args) {
LogPropertyTransformerRepository.addTransformer("status", Long::parseLong);
String schema = "$remote_addr - $remote_user [$time_local] \"$request\" $status $bytes_sent \"$http_referer\" \"$http_user_agent\"";
LogParser parser = new LogParser(schema);
LogList logs = parser.parseLogs("127.0.0.1 - - [07/Jul/2018:17:37:28 +0200] \"GET /7d32ce87648a4050faca.hot-update.json HTTP/1.1\" 200 43 \"http://test.local/\" \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0\"");
System.out.println(logs.where(Condition.greaterOrEqual("status", 200L)));
}
```
خروجی باید بهصورت زیر باشد:
```
[Log[remote_addr=127.0.0.1, remote_user=-, time_local=07/Jul/2018:17:37:28 +0200, request=GET /7d32ce87648a4050faca.hot-update.json HTTP/1.1, status=200, bytes_sent=43, http_referer=http://test.local/, http_user_agent=Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0]]
```
**نکته:** تستهای نمونهی سؤال در کلاس `LogParserSampleTest` موجود هستند. با افزودن *JUnit* به *classpath* پروژه، میتوانید آنها را اجرا کنید.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، یک فایل زیپ آپلود کنید که وقتی آن را باز میکنیم، با فایلهای زیر مواجه شویم:
```
Condition.java
LogList.java
LogParser.java
```