Skip to content

Instantly share code, notes, and snippets.

@askrynnikov
Last active October 20, 2016 05:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save askrynnikov/b984564cf7b8f4d8e4c32fbee95ceddd to your computer and use it in GitHub Desktop.
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}" }
@zverok
Copy link

zverok commented Oct 15, 2016

👍

Теперь доделываем усложнение и идём дальше. Но, кстати, более идиоматично вот так:

conclusion = case movie
  when good_movie
    "#{movie} is a good movie"
  when bad_movie
    "#{movie} is a bad movie"
  else
    "Haven't seen #{movie} yet"
  end

(так виднее, что вся цель case — вычислить conclusion)

@askrynnikov
Copy link
Author

askrynnikov commented Oct 16, 2016

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"?

Какая проблема имелась ввиду:

  1. среди нескольких слов только одно значимое
  2. разделителей может быть несколько
  3. разный регистр букв

?

@zverok
Copy link

zverok commented Oct 16, 2016

Вопросы для самопроверки, да :) Проблема имелась в виду — название фильма из нескольких слов.

Кстати, так ок, но можно было оставить case: case ... when *good_movies (* — оператор «распаковки» массива, когда у нас есть массив каких-то значений, а надо его куда-то передать как много отдельных аргументов)

@askrynnikov
Copy link
Author

Распаковка массива - да, более красиво получается. Спасибо.
:) Все равно не понятно, что нужно было сделать что-бы решить "проблему нескольких слов"?

@zverok
Copy link

zverok commented Oct 16, 2016

Все равно не понятно, что нужно было сделать что-бы решить "проблему нескольких слов"?

Это вопрос с подвохом! Он не на руби-трюки, а на знание командной строки :) Правильный ответ: ruby movies.rb "The Matrix" (кавычки делают его одним аргументом).

Теперь про текущее задание:

  • в строках 10-11 лушче использовать map
  • и объединить в общую структуру со строками 5-8
  • 27-28 тоже можно объединить в одну конструкцию, вот так:
movies
  .select {... }
  .each do
    # ...
  end

@askrynnikov
Copy link
Author

на знание командной строки

теперь понятно :)

использовать map ... и объединить

Как то так? .map ... .map ... .map

27-28 тоже можно объединить в одну конструкцию, вот так:

Вау! Красиво.

@zverok
Copy link

zverok commented Oct 16, 2016

Прекрасно, едем дальше :) Но в следующем задании мы все эти конструкции ещё подсократим!

@zverok
Copy link

zverok commented Oct 17, 2016

Неплохо!

  • стр. 20-21: теперь ещё немножко упрощаем: если использовать File.readlines сразу, а не open, то можно всё это в целом привести в состояние movies = несколько выражений, читающих и разбирающих файл
  • 24-33: хорошо бы вынести ключи в константу, а здесь собирать хеш с помощью .zip
  • 28: лучше метод более информативно назвать, типа parse_date. а то по этой строчке не угадаешь, что он собирается сделать
    • и, кстати, то же касается метода print: в руби он есть и так, а метод который «печатает фильмы», лучше назвать как-то коммуникативнее, типа print_movies
  • 30: когда нужен именно первый элемент, более идеоматично (и в длинных выражениях лучше смотрится) обозначить это с помощью .first. но, кстати, эти сложности и не нужны: "126 min".to_i тоже работает ;)
  • 40: (и далее) sort_by удобнее и читабельнее

@askrynnikov
Copy link
Author

Парсинг какой-то некрасивый получился.
Все в отдельный метод перенести?

@zverok
Copy link

zverok commented Oct 18, 2016

Так, во-первых вот это не очень рубёво:

movies = []
...map { movies << ... }

Хочется всё же так:

movies = File.readlines(...).map(...).map{...}

Во-вторых,

Парсинг какой-то некрасивый получился.
Все в отдельный метод перенести?

Угу. Хорошо бы что-нибудь типа post_process(FIELDS.zip(line.split('|')).to_h) что ли

@zverok
Copy link

zverok commented Oct 18, 2016

Ага! (Хотя в post_process может быть имело бы смысл использовать метод Hash#merge, но сильно уж короче бы не стало).

@zverok
Copy link

zverok commented Oct 18, 2016

Тут появляется интересное упрощение (хотя у тебя такой компактный код, что оно мало где пригодится): .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 (что первым придёт в голову как использовать) даст куда более красивый и читабельный код.

@askrynnikov
Copy link
Author

Да, прикольно. Хотя кажется уже на грани краткости и здравого смысла (по крайней мере для начинающих) :)

Искал, но ничего здравого у меня не выходит. Как эта конструкция должна выглядеть?

@zverok
Copy link

zverok commented Oct 18, 2016

Да, прикольно. Хотя кажется уже на грани краткости и здравого смысла (по крайней мере для начинающих) :)

Привыкнешь :) На самом деле, до появления этой конструкции, когда в тысячный раз пишешь array_of_strings.sort_by { |s| s.length }, ужасно хотелось кого-то стукнуть за этот повторяющийся бессмысленный s, поэтому возможность написать sort_by(&:length) (при этом не магическая, а вполне читаемая, оператор & в этой роли нам будет часто встречаться, а :length — просто символ) — казалась подарком небес!

Искал, но ничего здравого у меня не выходит. Как эта конструкция должна выглядеть?

movies.group_by { |m| m.date.month } и погляди что дальше будет.

@askrynnikov
Copy link
Author

askrynnikov commented Oct 19, 2016

когда в тысячный раз пишешь array_of_strings.sort_by { |s| s.length }

для встроенных методов супер, а в этой конкретной задаче оставил бы как реализовано в прошлом задании - с использованием символов как названий полей. Экстремизм - всё методы, как-то не по душе :) Под капотом почти любой программы обнаружим реляционную СУБД, поэтому разбиение на [:name] - это поле, а .overdraft - бизнес логика, мне кажется уместным :) Каково мнение сообщества по этому вопросу?

Почему length а не size ? :)

movies.group_by { |m| m.date.month } и погляди что дальше будет.

:) отвергнул этот вариант по причине "из пушки по воробьям" при данной постановке задачи: "в каком сколько ... снято"

Да, получилось читабельней.

Как reduce здесь можно использовать?

@zverok
Copy link

zverok commented Oct 19, 2016

Экстремизм - всё методы, как-то не по душе :) Под капотом почти любой программы обнаружим реляционную СУБД, поэтому разбиение на [: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 }

@askrynnikov
Copy link
Author

Да, с each_with_object красота :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment