Loading...
j.petrov_90 avatar j.petrov_90 373 Точки

Привет, колега,

Изключително съм радостен, че се престрашаваш да пишеш във форума.
Човек, който не се притеснява да признае, че не се сеща за решение - явно го е грижа да го научи.
Все пак, обаче, извини ме но ще потърпиш малко градивна критика.
И не, не съм "хейтър" :)

Въпроса, които си задал е неуместен по ред причини:
1) Домашното е все още в срок, т.е. ако някой сподели "готовото" решение - всички ще го копират без да си размърдат сивото вещество.

2) Казваш, че имаш решение, но не можеш да го разбереш.
Откъде имаш това решение? Някой ти го е дал ли? Явно не си го написал сам, щом някой ти го е дал.
Това би трябвало да те навежда на мисълта, че ще се случи същото това нещо, което съм написал в точка 1, ако сега някой беше "изпляскал" едно решението тук.

3) Въпроси тип - "6та задача на 7мото домашно ми и трудна - помощ" не са препоръчителни. 
Защо? Защото или не си помислил грам по задачата, или си помислил, но по някаква причина не искаш да го споделиш.
Разбери, че подхода към всяка една задача е: да я разбиеш на малки съставни части.
За всяка едно от тези части вече можеш или да потърсиш най-спокойно информация в интернет или да зададеш въпрос тук във форума.
По този начин ще може да ти се помогне в детайли точно за конкретния проблем, за който изпитваш затруднение.

Поздрави

 

1
pesosz avatar pesosz 4 Точки

Исках просто някой да обясни стъпка по стъпка как я е решил, просто аз не се сетих за вариант. Погледнах в на един колега решението, но не го схванах и се надявах някой добър човек да качи неговото решение с обяснение кое, как и защо е направил. Сори, не знаех, че е против правилата, да питам за решение на някоя задача, все пак сме тук за да се научим.

0
27/02/2019 03:46:37
j.petrov_90 avatar j.petrov_90 373 Точки

Привет,

Грешно си ме разбрал.
Никъде не съм казал, че е против правилата да питаш за решение на някой задача - даже напротив.

Споделих, че не е правилно да се поства цялото решение на задачата докато домашното е още в срок, защото хората ще го copy-paste-нат без да помислят грам.

Без да се заяждам - продължавам да твърдя, че на такъв въпрос няма да получиш смислен отговор.
Защо? Защото не задаваш конкретен въпрос!
Кажи ми: 
- "не разбирам как работи еди си кой конструктор"
- "не разбирам как работи << оператора"
- "не разбирам ред 78 от програмата какво прави" - и копирай тук проблемния ред.
Това са конкретни въпроси, на които мога да ти изпиша за всеки от тях по 50 реда обяснение така, че да ти стане максимално точно.

Когато обаче някой зададе въпрос тип "обяснете ми 5та задача" - кое по напред да ти отговоря по задачата?
В нея има 120 неща. За всяко нещо ли да пиша по 50 реда? Някой според теб ще си отдели ли 2 часа от живота си да пише такова огромно обяснение?
ОК, няма проблем - ще ги отдели и ще го напише. След това ти ще прегледаш какво е написал. Ще прочетеш 116 от тези неща и ще ги пропуснеш, защото вече ги знаеш.
Върху останалите 4 ще се съсредоточиш, защото само тях не знаеш.

Това е едното нещо.
Другото е, че като някой пост-не решението тук - ти го гледаш, съгласяваш се, разбираш го и всичко е 6 точки ... до момента, в който не започнеш да решаваш следващата задача и пак не забиеш на същите тези неща?
Защо ще забиеш на тях? Не защото не си ги разбрал - напротив, а защото докато на човек не му мине нещо през ръцете докато го учи - трудна работа да стане. (да си изцапаш ръцете както се казва)

Като преподавател това е моето мнение:
При така задазен въпроса, ако ти постна решена задачата - ще ти направя "мечешка" услуга.

Завършвам с един известен цитат:
Дай на човек риба и ще го нахраниш за един ден. Научи го да лови риба и ще си го нахранил за цял живот.

Поздрави

1
pesosz avatar pesosz 4 Точки

Добре, ще се постарая за в бъдеще въпросите ми да са по точно конкретни проблеми. Та за същата задача имам няколко неясни неща, като почнем от:
1 - SortingMain (единият от готовите skeleton файлове към задачата), на ред  49 ето линк към скелета:         https://judge.softuni.bg/Contests/Practice/DownloadResource/4019 

	else if (type == 's') {
		Parser<Song> p(std::cin, stopLine);
		Song s;
		typedef std::set<Song, Reverse<Song, LessThan<Song> > > Set;
		Set songs;
		while (p.readNext(s)) {
			songs.insert(s);
		}
		printContainer<Set>(songs.begin(), songs.end());

Не разбирам какво точно правят тези 2 реда: 

        typedef std::set<Song, Reverse<Song, LessThan<Song> > > Set;
        Set songs;

Това което предполагам, че правят е, typedef дава име на std::set<Song, Reverse<Song, LessThan<Song> > , като го кръщава Set и след това, предполагам, че винаги като извикаме Set, ще се извика това дългото обяснение зад него, но не схващам на долния ред какво се случва.. тоест свикнал съм като създадем например вектор и точно след дефиницията vector<int> Име, да кръстим вектора по някакъв начин, а в тази ситуация се получава някакво двойно кръщаване и ме обърква малко. Също така, самото разписване на: <Song, Reverse<Song, LessThan<Song>, не ми е ясно какво точно прави, може би не схващам std::set какво точно прави, но ми е странно написано и не мога да си го преведа(и преди някой да ме изхейти, да гугълнах за std::set, но явно не знам какво точно да потърся) 

2. Следващото което не ми е ясно е във файла Parser.h, а ето и кода:

 

#include <utility>
#include <string>
#include <sstream>

template<class T>
class Parser {
  std::istream& in;
  std::string stopLine;
public:
  Parser(std::istream& in, std::string& stopLine) : in(in), stopLine(std::move(stopLine)) { }

  bool readNext(T& element) {
    std::string line;
    if (std::getline(this->in, line) && line != this->stopLine) {
      std::istringstream is(line);
      is >> element;
      return true;
    }
    return false;
  }
};

и по-точно: Parser(std::istream& in, std::string& stopLine) : in(in), stopLine(std::move(stopLine)) { } ... нямам никаква идея, какво точно прави това (std::move(stopLine)) и защо е използвано в конструктора.
Също така не мога да разбера от къде се вкарват нови елементи, защото по това което чета от Main-а и Parser.h , имаме:

if (std::getline(this->in, line) && line != this->stopLine) ,  до колкото разбирам кода, в проверката чрез getline(this->in, line) проверяваме дали има още string-ове в стрийма, като в същото време записваме стрийма в line, след това като втора проверка, сравняваме текущия string, дали е равен със string-а stopLine след което надолу със стрийм единствено "extract-ваме" line-a в елемент от тип template, който връщаме обратно към main-a, заедно с flag дали да продължи да пълни set-a,  a в main-a единствено взимаме типа информация, който ще обработваме и след това символ за край на реда... Ето част от кода в мейн-а:

int main() {
	char type;
	std::cin >> type;
	std::cin.ignore();
	std::string stopLine;
	std::getline(std::cin, stopLine);

	if (type == 'i') {
		Parser<int> p(std::cin, stopLine);
		int n;
		typedef std::set<int, Reverse<int, LessThan<int> > > Set;
		Set numbers;
		while (p.readNext(n)) {
			numbers.insert(n);
		}..................

 

 

 

Следващото, което не ми е ясно е във файла Comparators.h, ето и кода:

 

#include <string>
#include <iostream>
#include "Song.h"

template<typename T>
struct LessThan {
  const bool operator()(const T& lhs, const T& rhs) const {
    return lhs < rhs;
  };
};

template<typename T, typename Comparator>
struct Reverse {
  const bool operator()(const T& lhs, const T& rhs) const {
    Comparator comparator;
    return !comparator(lhs, rhs);
  }
};

 

Първо, какви са тези скоби(с червеното) и за какво служат:  const bool operator()(const T& lhs, const T& rhs) const {  някакъв вид operator overloading ли е това? Също така не разбирам начина по който се извикват тези функции, защото в мейн-а се извикват със следния синтаксис: <Song, Reverse<Song, LessThan<Song> , а да не говорим за Reverse структурата.. нея изобщо не я разбирам какво прави и за какво се създава елемент от тип template и след това се връща обратното на comparator(lhs, rhs) , това не е ли равносилно с това да напиша return !double(int 5.5,int 10.2) ... Какво трябва да очаквам да ми върне това? 


Другото което не ми беше ясно, но сега, като го помислих по-обстойно мисля че се сетих защо е... във файла Song.h, от скелета, не схващах защо ни трябва да overload-ваме оператора < , но после се сетих, че се използва в Comparator-а.
 

bool operator<(const Song& thiz, const Song& other) {
	return thiz.getLengthSeconds() < other.getLengthSeconds();
}


И последното: PrintUtils.h

 

#include <iostream>
#include <sstream>
#include <vector>
#include <set>

template<typename Template>
void printContainer(typename Template::iterator begin, typename Template::iterator end) {
  while (begin != end) {
    std::cout << *begin << " ";
    ++begin;
  }
  std::cout << std::endl;
}


Това, мисля че го разбирам... от мейн-а ако не се лъжа подаваме printContainer<Set>(songs.begin(), songs.end()); . което мисля че взима от set-a пойнтър сеочещ към началото и пойнтър сочещ към края на set-a... това което не ми е напълно ясно е, защо слагаме четирите точки.. тоест до сега съм го правил механично, но не като цяло не съм го разбирал защо е така.. Според моята теория е, защото самият template class има като поле iterator, защото попринцип когато викаме 4те точки мисля, че достъпвахме фукнция или конструктор, или нещо от даден клас, но това което ми е странно е че го извикваме така: iterator begin, без скоби () , след begin... като цяло просто ми стана интересно как точно е написан итератора за да го използваме по този начин.

П.С. Знам че 90% от въпросите зададени тук са доста глупави и като цяло сме ги преглеждали на лекциите, но мога да гарантирам, че до сега съм гледал всяка лекция по 2 пъти поне и съм опитвал да пиша всички домашни, като повечето ме отказваха до тук, като стигна 5та задача.. някои почти ме отказаха на няколко пъти и на 4та задача... но като цяло усещам че ми се натрупват много малки неща които не са ми ясни и с всяка следваща лекция става все по-голямо незнанието ми.. Надявам се ако някой намери време да отговори на тези мои въпроси, че ще е достатъчно да наваксам нещата които не са ми ясни до тук. Благодаря, предварително!

2
j.petrov_90 avatar j.petrov_90 373 Точки

Евала!
Ето това исках да чуя от теб! Адмирации!
Забеляза ли как още докато задаваше въпросите - успя да си изясниш част от тях? :)
... и не се притеснявай ... никой няма да те "хейти". Както ти сам каза тук сме да се научим.
Забележи темата колко преглеждания има ... хората гледат форума, но ги е страх/срам да питат.
Живот и здраве като ти се изяснят въпросите - ще си дръпнал ли напред? Ще си дръпнал.
Дето се казва "haters are gonna hate", а мен ме боли фара :)

Отговарям ти в номерацията, която ти си задал:
1) 
- Правилно си разбрал какво значи "typedef". Това е все едно ние да си направим наш тип данни.
typedef int myInt
След това мога да направя променлива myInt num = 5; (това ще бъде равносилно на int num = 5;).
Това най-често се ползва като имаме дълъг тип данни за да не го пишем целия всеки път - typedef-ваме си го с друго име.
Например typedef std::vector<std::vector<int>> Matrix
След това просто си пишем Matrix matrix;

- Следва обяснението за std::set<> (същото важи и за std::multiset, std::map, std::multimap).
Това са структури от данни, които държат данните си винаги сортирани.
За да държат данните си сортирани тези структури имат нужда от способ за "сравнение" на тези типове данни.
Когато работим с прости данни (например int, float, double) - този способ ни идва на готово, защото компилаторът знае как да сравни тези типове.
Така можем най-спокойно да кажем std::set<int>

Не можем да кажем std::set<Song>, защото трябва да сме дали на set::set-а способ за сравнение на 2 обекта от тип "Song".

Този способ може да е (говоря за кой и да е тип - не конкретно за "Song"):
    > operator<(const T& left, const T& right) { //конкретна имплементация };
    > външна функция, която да подадеш на контейнера, която да сравнява 2 обекта от типа на контейнера;
    > lambda фунция (същото като горното, само че написана на място). Нещо като [](const T& left, const T& right) { //конкретна имплементация }
    > Functor (или C++ структура или class, който в себе си да има предефиниран оператора кръгли скоби "bool operator()(const T& left, const T& right){ //конкретна имплементация }")

- Относно объркването ти за std::vector<int> и printContainer<Set>(songs.begin(), songs.end());
Нормално е да си объркан, спокойно :)
Объркването идва от там, че с std::vector<int> създаваш template-тен клас вектор от тип "int", а с printContainer<Set>(songs.begin(), songs.end()) не създаваш обект.
Защо? Защото "printContainer" e функция, а не е обект.

В лекцията говорихме как template-тите можем да ги ползваме и само за отделни функции (без да са част от класа).
В лекцията това беше примера със "calcPercentage", ако не се лъжа.
Т.е. "printContainer" функцията ти трябва да изглежда нещо от сорта на:
template<typename Container>
void printContainer(const typename Container::iterator & begin, const typename Container::iterator & end) { //конкретна имплементация }

2) 
- std::move() e функция, която още не сме учили - ще я споменем по-нататък в курса.
Накратко какво прави -> cast-ва обекта към "специален тип", който можем да "преместим"
т.е. с кода this->stopLine(std::move(stopLine)) бихме "преместили" ресурсите от обекта stopLine в обекта this->stopLine
Приеми, че кода this->stopLine(stopLine) ще свърши повече от чудесна работа :)

- Кодът "if (std::getline(this->in, line) && line != this->stopLine)" си го разбрал прекрасно и нямам какво да допълня. 6 Точки :)

3)
const bool operator()(const T& lhs, const T& rhs) const - обяснявам какво са "празните () скоби"
Както сам си предположил -> това е най-обикновен operator overloading. Кой е операторът? Ами оператор кръгли скоби - ().
Ако имаме 
strut Car {
    void operator()(){
        std::cout << "mambo number 5" << std::endl;
    }
}

Можем след това да си създадем обект от тип кола
Car mercedes; //тук просто ще се извика default-ния конструктор, който е празен за структурата ни Car
mercedes(); //тук ще извикаме оператора кръгли скоби на обекта от тип Car и това ще отпечата "mambo number 5" на конзолата.

4)
Относно Reverse<Song, Reverse<Song, LessThan<Song>
Това е така наречения Functor в C++ (или обект, който ни играе ролята на някаква функция).
Решавахме тавава задача на упражненията - можеш да прегледаш записа за да ти стане по-ясно (беше 3та задача).
Като подадем тази структура (или както се нарича функтор) - можем за сме сигурни, че ще се извика неговия оператор кръгли скоби /operator()/=
Какво трябва да ти върне това? Каквото ти връща оператора кръгли скоби /това си е най-обикновенна функция/.
В случая на задачата се връща bool.
Този bool служи на std::set-а да "сравнява" своите елементи (т.е. дали трябва да размести 2 обекта или не - така, че те винаги да бъдат в сортиран ред)

5)
Защо във функцията
template<typename Template>
void printContainer(typename Template::iterator begin, typename Template::iterator end) {
  while (begin != end) {
    std::cout << *begin << " ";
    ++begin;
  }
  std::cout << std::endl;
}

пишем Template::iterator?
В Лекцията учихме, че за да достъпим типа, който ни седи под името на template-а (в случая Template) - трябва да напишем Template::
т.е. пишейки Template::iterator ние не знаем, че в нашия тип има променлива от тип iterator, а обратното.
Ние казваме: искам всеки кандидат, който ще изпозлва моята функция да има в себе си поле "iterator".
Затова ако викнем нашата функция на std::vector<нещоСи> vec;
printContainer<std::vector<нещоСи>>(vec.begin(), vec.end()) - тя ще сработи, защото std::vector<> си има iterator;
Всеки контейнер в стандартната библиотека STL има iterator.

Не можем обаче да извикаме функцията върху наш клас. Например класа Car от по-рано.
printContainer<Car>() - няма да сработи. 
Компилаторът ще ти каже - класа ти няма iterator и няма да сработи. Също така няма функции .begin() и .end()
Можем да го накараме да работи, но трябва да направим нашия клас Car да поддържа iterator и да му напишем фунции .begin() и .end()

6)
защо във функцията printContainer iterator.begin() и .end() ги извикваме със скоби а вътре във фунuцията нямат скоби?
Защото .begin() и .end() са функции. Функции в C++ се викат като се напише името на функцията последвато с кръгли скоби (в тях поставяме аргументите, на фунцията, ако има такива).
Вътре вече във фунцията void printContainer(typename Template::iterator begin, typename Template::iterator end) използваме обекти от пит iterator.

Давам пример за да стане по-ясно:
Ако имахме масив от 4 коли Car cars[4];
Напишем си 2 функции: 
Car * begin() {
    return &cars[0]
}

Car * end() {
    return &cars[4]
}

Ако нашата фунция беше printCars(Car * begin, Car * end) { //конкретна имплементация }
Щяхме да извикаме функцията като printCars(begin(), end());

 
Надявам се, да съм бил изчерпателен.
Продължавай напред и не губи мотивация!
За пореден път повтарям - курса е труден и домашните са трудни!
Това не е курс тип - дай ми Х лева - ето ти сертификат.
Искам този курс да го завърват успешно само хората, които се трудят и желаят нещата да се случат.
Предпочитам да ви сваля "розовите очила" и да ви науча отколкото просто да имате един сертификат, а в главата ви да няма никой :) 

По-здрави :)

1
Teodor_xd avatar Teodor_xd 0 Точки

Рядко чета форуми, но тук се задържах защото тая задача ми изпи нервичките.
Минавам само да благодаря j.petrov_90 за изчерпателните и поощрителни отговори!
Освен на pesosz, помагат и на други!

Относно задачата имах представа горе-долу как да я реша и преглеждайки нещата дотук стигам до извода че съм на прав път, само че ето например не знаех що е functor и се обърквах тотално с шаблоните...

Трябва да си призная, че в началото е гадно да трябва сам да си търсиш всичко, но минавайки през този период става по-интересно.

Радвам се, че попаднах на такава ползотворна дискусия!

Поздрави!

 

0
Можем ли да използваме бисквитки?
Ние използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Можете да се съгласите с всички или част от тях.
Назад
Функционални
Използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Използваме „сесийни“ бисквитки, за да Ви идентифицираме временно. Те се пазят само по време на активната употреба на услугите ни. След излизане от приложението, затваряне на браузъра или мобилното устройство, данните се трият. Използваме бисквитки, за да предоставим опцията „Запомни Ме“, която Ви позволява да използвате нашите услуги без да предоставяте потребителско име и парола. Допълнително е възможно да използваме бисквитки за да съхраняваме различни малки настройки, като избор на езика, позиции на менюта и персонализирано съдържание. Използваме бисквитки и за измерване на маркетинговите ни усилия.
Рекламни
Използваме бисквитки, за да измерваме маркетинг ефективността ни, броене на посещения, както и за проследяването дали дадено електронно писмо е било отворено.