Статьи

Ruby Golf: результаты!

Еще в ноябре я написал статью о Ruby Golf . В конце статьи я поставил перед читателями 5 лунок, чтобы они могли завершить работу с использованием минимального числа байтов. Был потрясающий ответ, который видел много впечатляющих «выстрелов».

Крайний срок прошел в конце 2011 года, и с тех пор я просматривал все заявки, чтобы найти победителей в каждой лунке. Было несколько отличных решений, которые показали некоторые проницательные способы использования Ruby. Впрочем, справедливо сказать, что большинство из них на первый взгляд выглядело как ошеломляющая тряпка… хотя я думаю, что это нормально для курса в код-гольфе!

Помимо того, что я использовал этот пост для объявления победителей, я подумал, что было бы полезно ознакомиться с некоторыми приемами, которые использовались в каждой из победных работ. Так что, если вы чувствуете, что застряли в грубой части кода или в бункере ментального песка, тогда читайте дальше — здесь есть чему поучиться. Fore!

Примерное отверстие

Как часть статьи, я представил это как пример Ruby Golf:

Напишите метод, который бы нашел сумму всех кратных данного числа до заданного значения. Например, сумма (5,24) будет вычислять сумму всех кратных от 5 до 24 (то есть 5 + 10 + 15 + 20). Вот мой метод:

def sum(n,t)
  n*(1..t/n).to_a.inject(&:+)
end

Само собой разумеется, что мой гандикап, безусловно, мог бы быть лучше, и было множество улучшений, на которые читатели были только заинтересованы, чтобы указать.

Кайл Патнэм заметил, что метод to_ainject

 def sum(n,t)
  n*(1..t/n).inject(&:+)
end

Рейн Хенрикс также отметил, что вам не нужен символ & в Ruby 1.9, и вы также можете использовать метод Reduce вместо Inject (он имеет тот же набор букв, но, возможно, имеет большее значение):

 def sum(n,t)
  n*(1..t/n).reduce(:+)
end

Но затем Кайл Дин пробил удивительную дыру в 1, применив немного элементарной математики. Уже есть формула для суммирования чисел, которую можно использовать вместо итерации по диапазону:

 def sum(n,t)
  k=t/n;n*k*(k+1)/2
end

Это отличный пример отказа от причудливых методов и непонятных трюков, и вместо этого просто полагается на добрую старомодную математику, чтобы выполнить свою работу.

Отверстие 1: Fizz Buzz

Для заданного числа функция возвращает «Fizz», если оно кратно 3, «Buzz», если оно кратно 5, и «FizzBuzz», если оно кратно 15. Если число не кратно 3 или 5 затем число возвращается в виде строки.

Это была яростно оспариваемая дыра, но Марцин Манк поехал прямо по фарватеру с этим усилием, которое весит 41 байт.

 def fizzbuzz n
    n%3<1&&f=:Fizz;n%5&lt;1?"#{f}Buzz":"#{f||n}"
  end

Эта запись использует оператор по модулю, чтобы проверить, является ли n кратным 3. Если это так, то n% 3 фактически будет 0, но поскольку мы имеем дело только с целыми числами, вы можете сохранить байт, протестировав, если он меньше 1 ( n%3<1n%3==0

Первый оператор использует оператор && для установки f =: Fizz (обратите внимание также на использование символа здесь, поскольку он использует на один байт меньше строки), если n% 3 <1 оценивается как true (то есть, если n кратно из 3). Если n%3<1f

После ; , что означает конец строки кода, затем выполняется аналогичный тест, чтобы увидеть, кратно ли n n. Затем троичный оператор используется для отображения одного результата, если это правда, и другого результата, если он ложен. Если n кратно 5, возвращается следующая строка:

 "#{f}Buzz"

Что в этом умного, так это то, что для вставки значения f перед словом Buzz используется интерполяция строк. Если f было установлено в: Fizz (т.е. если n также было кратно 3 и, следовательно, кратно 15), то оно автоматически преобразуется в строку и вставляется перед словом Buzz. Если f не было установлено (т. Е. Если n не кратно 3, но все еще кратно 5), то #{f}

Если n не кратно 5, возвращается следующая строка:

 "#{f||n}"

Это умное использование || Оператор, чтобы дать два возможных варианта. Если f было установлено, то будет отображаться «Fizz» (в виде строки, а не символа, потому что снова используется интерполяция). Вторая часть условия будет достигнута только в том случае, если первая часть вернет false (т. Е. F не была установлена), а затем отобразит введенное число (опять же, в виде строки из-за интерполяции).

Отличное решение, основанное на вступлении Сайруса. Это может быть побито?

Отверстие 2: Цезарь Шифр

Внедрить шифр Цезаря

Пример:

 caesar("hello",3) => "khoor"

Вы также должны быть в состоянии произвести отрицательные сдвиги.

Я люблю коды и шифры, поэтому особенно интересовался решением этой проблемы. Лучшее вошло в крошечные 29 байтов, любезно предоставленные Джошом Чиком :

 def caesar(s,n)
    s.gsub(/./){|c|(c.ord+n).chr}
  end

Это был удивительно чистый и эффективный метод, который просто использовал метод gsub/./ Недостатки Ruby указывают, что для добавления в строку вы должны сначала использовать метод ordchr

Отверстие 3: Камень, Бумага, Ножницы Игра

Напишите простой метод, который «играет» в эту игру, где игрок вводит свой «ход» в качестве аргумента метода. Если игрок вводит неверную опцию, то результат должен быть «проиграть». Компьютер должен выбирать свой ход наугад. Выходные данные дают «перемещение» компьютера и результат в виде строки, разделенной запятыми.

Это была моя любимая дыра и также было больше записей. Победа в конкурсе — вещь прекрасная, и я не могу понять, как J – L сконцентрировали вездесущую детскую игру в ничтожные 84 персонажа:

  Def Play (I)
   m =% w (Каменные ножницы); m 

[/ c] +?, +% w (ничья Win Lose) [((m.index (i) || c-1) -c)% 3]
конец

Он начинается с использования сокращенной записи создания массива из трех «ходов» и присвоения его переменной. Назначение обычно является чем-то, чего следует избегать при программировании гольфа, так как оно тратит пару байтов. Но в этом случае это необходимо, так как массив будет использоваться снова позже.

Затем следующий оператор просто создает строку, которая будет возвращена методом. Он начинается с выбора перемещения компьютеров из массива ходов (m) случайным образом. «Ход» компьютеров вычисляется с использованием метода rand Это означает, что c может быть использован снова позже:

  м 

Далее запятая соединяется со строкой. Запятая создается с помощью ярлыка ?,','

 + ?,

Последний из всех результатов игры - выигрыш проигрыш или ничья - конкатенируется до конца. Результат определяется следующим битом кода:

 +% w (Draw Win Lose) [((m.index (i) || c-1) -c)% 3]

m.index(i) Если это не «Камень», «Бумага» или «Ножницы», тогда он заменяется на 1 меньше, чем перемещение компьютеров (которое было задано как c ранее в методе). Это гарантирует, что игрок проиграет, как вы скоро увидите. Далее следует небольшой умный алгоритм, который вычисляет, выиграл ли ты, выиграл или проиграл, используя арифметику по модулю 3. Если вы вычтете индекс перемещения компьютеров из индекса вашего собственного хода, то ответ будет разным в зависимости от результата игры:

 eg 
You play Rock (index = 0). Computer plays Rock(index = 0). This is a draw:
0 - 0 = 0 = 0 mod 3
You play Rock (index = 0). Computer plays Scissors(index = 2). This is a win:
0 - 2 = - 2 = 1 mod 3
You play Rock (index = 0). Computer plays Paper(index = 1). This is a loss:
0 - 1 = - 1 = 2 mod 3

Результат зависит от результата игры (выигрыш, проигрыш или ничья). Эти результаты одинаковы для других 6 возможных результатов (попробуйте) и означает, что если вы построите массив как% w (ничья, выигрыш проиграл), то индекс совпадет с результатом вычитания ходов.

Отверстие 4: Счетчик струн

Напишите метод, который при задании строки и подстроки возвращает количество раз, когда подстрока встречается в этой строке (без учета регистра).

Это оказалось относительно простым решением, которое было довольно легко решить с помощью метода сканирования. Марцин Манк был первым, кто использовал этот метод, но допустил ошибку для школьника, используя метод длины вместо размера, который на 2 байта короче. Кристиан Гюнтер был быстр, чтобы указать на это, и поэтому он выигрывает приз за это решение в 20 байтов:

 def count(s,x) 
    s.scan(/#{x}/i).size
  end

Этот метод работает путем изменения требуемой подстроки в регулярное выражение с использованием интерполяции. Каждый вхождение строки затем помещается в массив, который возвращается методом сканирования. Чтобы узнать, сколько раз строка появилась, нужно просто определить размер этого массива, чтобы метод size мог быть связан до конца.

Отверстие 5: функция свингеров

Напишите функцию, которая заменяет «складывание ключей». Аргумент функции - это массив массивов, которые содержат два объекта. Функция возвращает новый массив, в котором пары объектов были перепутаны. Объект не должен заканчиваться своим оригинальным «партнером».

Пример:

 swingers([["Homer","Marge"],["Micky","Minnie"],["Fred","Wilma"],["Peter","Lois"],["George","Judy"]])
=> [["Homer","Wilma"],["Micky","Lois"],["Fred","Judy"],["Peter","Marge"],["George","Minnie"]]

У Джоша Чика была самая низкая запись с 30 байтами:

 def swingers(s)
      f,l=s.transpose;f.zip l.rotate
    end

Это решение использует массовое присваивание, чтобы присвоить результат транспонирования массива пар двум переменным x и y. То, что это эффективно делает, помещает всех мужчин в x и всех женщин в y. Затем функция zip используется для объединения двух массивов назад, но метод rotate сначала применяется к массиву y (для женщин). Это эффективно перемещает каждую женщину в одном месте. Это означает, что первый мужчина попадает в ряд вместе со следующей женщиной.

Это соответствует указанному требованию, хотя существовало неявное требование, чтобы смешивание было случайным. Это легко достигается применением метода shuffle На самом деле это было сделано на раннем этапе Реми, который получил приз за эту дыру:

 def swingers(a)
  x,y=a.shuffle.transpose;x.zip y.rotate
end

В целом

После такого тяжелого соревнования я рад объявить абсолютного победителя с самым низким общим счетом за все пять лунок ... и победителем необычного Ruby Blazer ... является ... Джош Чик! Поздравляем чемпиона RubySource Masters 2011 года!

Если вы были одним из вышеупомянутых индивидуальных победителей, то вас ждет книга Sitepoint. Вам просто нужно связаться, оставив комментарий ниже. Поздравляем всех победителей, а также всех, кто принял участие. Помните, что это процесс поиска решения, которое помогает улучшить ваши навыки кодирования и, как и в реальном гольфе, практика совершенствуется. Я, конечно, многому научился на Ruby, прочитав все решения. Было восхитительно видеть множество различных подходов, используемых для достижения одного и того же решения. Мне также понравился тот факт, что ряд записей построен на предыдущих попытках - эквивалент того, чтобы идти по одному и тому же маршруту к грин, а затем потопить длинный удар, чтобы выиграть дыру! Но это лучший ответ? Мы никогда не можем быть на 100% уверены, что решение настолько мало, насколько это возможно, поэтому кто-нибудь может победить их? Вы узнали какие-нибудь интересные новые трюки с Ruby из этих записей или у вас есть другие, которые не были упомянуты? Дайте нам знать об этом в комментариях.

Но самое лучшее в Ruby Golf - это то, что в конце изнурительного раунда программирования вы все равно можете совершить путешествие к 19-й лунке в конце дня!