-
-
Save askrynnikov/b984564cf7b8f4d8e4c32fbee95ceddd to your computer and use it in GitHub Desktop.
require 'date' | |
require 'csv' | |
require 'ostruct' | |
def print_movies(movies) | |
movies.each { |movie| puts "#{movie[:title]} (#{movie[:date]}; #{movie[:genres].join("/")}) - #{movie[:duration]} min" } | |
end | |
def parse_date(date) | |
# добавляем месяц если отсутствует | |
date += "-01" if date.split("-").size == 1 | |
# добавляем число месяца если отсутствует | |
date += "-01" if date.split("-").size == 2 | |
Date.strptime(date, '%Y-%m-%d') | |
end | |
def post_process(movie) | |
movie.year = movie.year.to_i | |
movie.date = parse_date(movie.date) | |
movie.genres = movie.genres.split(",") | |
movie.duration = movie.duration.to_i | |
movie.stars = movie.stars.split(",") | |
movie | |
end | |
file_name = ARGV[0] || "movies.txt" | |
abort "Ошибка! Файл #{file_name} не найден." unless File.file?("./#{file_name}") | |
FIELDS = [:link, :title, :year, :country, :date, :genres, :duration, :rate, :director, :stars] | |
movies = CSV.read(file_name, { :col_sep => '|' }) | |
.map { |movie| post_process(OpenStruct.new(FIELDS.zip(movie).to_h))} | |
puts "\n5 самых длинных фильмов" | |
print_movies(movies | |
.sort_by(&:duration) | |
.last(5) | |
.reverse | |
) | |
puts "\n10 комедий (первые по дате выхода)" | |
print_movies(movies | |
.select { |m| m.genres.include?("Comedy") } | |
.sort_by(&:date) | |
.first(10) | |
) | |
puts "\nсписок всех режиссёров" | |
puts(movies | |
.map(&:director) | |
.uniq | |
.sort_by { |m| m.split(" ").last} | |
) | |
puts "\nколичество фильмов, снятых не в США" | |
puts(movies | |
.count { |m| m.country != "USA" } | |
) | |
puts "\nколичество фильмов по месяцам" | |
movies | |
.group_by { |m| m.date.month } | |
.sort | |
.each {|key, value| puts "#{Date::MONTHNAMES[key]}: #{value.size}" } | |
puts "\nколичество фильмов по месяцам" | |
movies | |
.reduce(Hash.new { 0 }) { |stats, m| stats[m.date.month] += 1; stats } | |
.sort | |
.each {|key, value| puts "#{Date::MONTHNAMES[key]}: #{value}" } | |
puts "\nколичество фильмов по месяцам" | |
movies | |
.each_with_object(Hash.new { 0 }) { |m, stats| stats[m.date.month] += 1 } | |
.sort | |
.each {|key, value| puts "#{Date::MONTHNAMES[key]}: #{value}" } | |
good_movie = ["Matrix", "Mask", "Dislike"]
bad_movie = ["Titanic", "Warcraft", "Mechanic"]
movie = ARGV[0]
conclusion =
if good_movie.include?(movie)
"#{movie} is a good movie"
elsif bad_movie.include?(movie)
"#{movie} is a bad movie"
else
"Haven't seen #{movie} yet"
end
puts conclusion
Вопросы в конце задания - для самопроверки?
Как выполнить программу, если в названии фильма несколько слов, например "The Matrix"?
Какая проблема имелась ввиду:
- среди нескольких слов только одно значимое
- разделителей может быть несколько
- разный регистр букв
?
Вопросы для самопроверки, да :) Проблема имелась в виду — название фильма из нескольких слов.
Кстати, так ок, но можно было оставить case
: case ... when *good_movies
(*
— оператор «распаковки» массива, когда у нас есть массив каких-то значений, а надо его куда-то передать как много отдельных аргументов)
Распаковка массива - да, более красиво получается. Спасибо.
:) Все равно не понятно, что нужно было сделать что-бы решить "проблему нескольких слов"?
Все равно не понятно, что нужно было сделать что-бы решить "проблему нескольких слов"?
Это вопрос с подвохом! Он не на руби-трюки, а на знание командной строки :) Правильный ответ: ruby movies.rb "The Matrix"
(кавычки делают его одним аргументом).
Теперь про текущее задание:
- в строках 10-11 лушче использовать map
- и объединить в общую структуру со строками 5-8
- 27-28 тоже можно объединить в одну конструкцию, вот так:
movies
.select {... }
.each do
# ...
end
на знание командной строки
теперь понятно :)
использовать map ... и объединить
Как то так? .map ... .map ... .map
27-28 тоже можно объединить в одну конструкцию, вот так:
Вау! Красиво.
Прекрасно, едем дальше :) Но в следующем задании мы все эти конструкции ещё подсократим!
Неплохо!
- стр. 20-21: теперь ещё немножко упрощаем: если использовать
File.readlines
сразу, а неopen
, то можно всё это в целом привести в состояниеmovies = несколько выражений, читающих и разбирающих файл
- 24-33: хорошо бы вынести ключи в константу, а здесь собирать хеш с помощью
.zip
- 28: лучше метод более информативно назвать, типа
parse_date
. а то по этой строчке не угадаешь, что он собирается сделать- и, кстати, то же касается метода
print
: в руби он есть и так, а метод который «печатает фильмы», лучше назвать как-то коммуникативнее, типаprint_movies
- и, кстати, то же касается метода
- 30: когда нужен именно первый элемент, более идеоматично (и в длинных выражениях лучше смотрится) обозначить это с помощью
.first
. но, кстати, эти сложности и не нужны:"126 min".to_i
тоже работает ;) - 40: (и далее) sort_by удобнее и читабельнее
Парсинг какой-то некрасивый получился.
Все в отдельный метод перенести?
Так, во-первых вот это не очень рубёво:
movies = []
...map { movies << ... }
Хочется всё же так:
movies = File.readlines(...).map(...).map{...}
Во-вторых,
Парсинг какой-то некрасивый получился.
Все в отдельный метод перенести?
Угу. Хорошо бы что-нибудь типа post_process(FIELDS.zip(line.split('|')).to_h)
что ли
Ага! (Хотя в post_process
может быть имело бы смысл использовать метод Hash#merge
, но сильно уж короче бы не стало).
Тут появляется интересное упрощение (хотя у тебя такой компактный код, что оно мало где пригодится): .sort_by { |m| m.duration}
(т.е. когда ровно один метод без аргументов вызывается внутри блока) можно упростить до .sort_by(&:duration)
. Вот объяснение: https://ruby-doc.org/core-2.2.0/Symbol.html#method-i-to_proc
Кроме того, для последнего задания group_by
или reduce
(что первым придёт в голову как использовать) даст куда более красивый и читабельный код.
Да, прикольно. Хотя кажется уже на грани краткости и здравого смысла (по крайней мере для начинающих) :)
Искал, но ничего здравого у меня не выходит. Как эта конструкция должна выглядеть?
Да, прикольно. Хотя кажется уже на грани краткости и здравого смысла (по крайней мере для начинающих) :)
Привыкнешь :) На самом деле, до появления этой конструкции, когда в тысячный раз пишешь array_of_strings.sort_by { |s| s.length }
, ужасно хотелось кого-то стукнуть за этот повторяющийся бессмысленный s
, поэтому возможность написать sort_by(&:length)
(при этом не магическая, а вполне читаемая, оператор &
в этой роли нам будет часто встречаться, а :length
— просто символ) — казалась подарком небес!
Искал, но ничего здравого у меня не выходит. Как эта конструкция должна выглядеть?
movies.group_by { |m| m.date.month }
и погляди что дальше будет.
когда в тысячный раз пишешь
array_of_strings.sort_by { |s| s.length }
для встроенных методов супер, а в этой конкретной задаче оставил бы как реализовано в прошлом задании - с использованием символов как названий полей. Экстремизм - всё методы, как-то не по душе :) Под капотом почти любой программы обнаружим реляционную СУБД, поэтому разбиение на [:name] - это поле, а .overdraft - бизнес логика, мне кажется уместным :) Каково мнение сообщества по этому вопросу?
Почему length
а не size
? :)
movies.group_by { |m| m.date.month }
и погляди что дальше будет.
:) отвергнул этот вариант по причине "из пушки по воробьям" при данной постановке задачи: "в каком сколько ... снято"
Да, получилось читабельней.
Как reduce
здесь можно использовать?
Экстремизм - всё методы, как-то не по душе :) Под капотом почти любой программы обнаружим реляционную СУБД, поэтому разбиение на [:name] - это поле, а .overdraft - бизнес логика, мне кажется уместным :)
Не, у нас всё и всегда объекты, более или менее. Собственно, в большинстве случаев с СУБД работают через ORM (object-relational mappers), которые превращают строки таблицы в объекты. В этом тоже есть некоторый смысл: user.full_name
может быть как полем БД, так и методом склеившим поля first_name
и last_name
(хотя отношение к ORM довольно сложное, иногда считают что оно больше проблем создаёт чем удобства)
Как reduce здесь можно использовать?
Вот так:
movies.reduce(Hash.new { 0 }) { |stats, m| stats[m.month] += 1; stats }
# хотя each_with_object тут удобнее:
movies.each_with_object(Hash.new { 0 }) { |m, stats| stats[m.month] += 1 }
Да, с each_with_object
красота :)
👍
Теперь доделываем усложнение и идём дальше. Но, кстати, более идиоматично вот так:
(так виднее, что вся цель
case
— вычислитьconclusion
)