Комментарии 121
Вот решение в духе Яндекса:
const getRanges = arr => arr
.reduceRight((r, e) => r.length? (r[0][0] === e + 1? r[0].unshift(e): r.unshift([e])) && r: [[e]], [])
.map(a => a.join('-')).join(',')
Поможет ли гугление писать такие элегантные решения?
Это конечно вкусовщина, но такие решения как минимум спорные. Я не имею ничего против reduce/fold/назовите как хотите, но используемая внутри функция свертки должна быть выражена более ясно. А тут тернарный оператор на тернарном операторе сидит, и тернарным оператором погоняет.
Чёт я не понял, это правда недостаточно ясное выражение?
r.length ? (r[0][0] === e + 1? r[0].unshift(e) : r.unshift([e])) && r : [[e]]
по мне это ясное выражение
Или вы предлагаете ввести константу FIRST_ARRAY_ELEMENT?
Например, присвоить это значение переменной с понятным именем
Но все равно невозможно сходу понять что такое " первый элемент первого массива результата предыдущих применений этого коллбэка". При том что в этом куске кода нет никаких коллбэков и асинхронщины.
А вообще я бы предлагал не обфусировать код на ровном месте, пусть он будет и не в «стиле яндекса». Но его хотя бы поправить можно будет в случае ошибки или изменения требований.
Этот «кусок кода» — содержимое функции передаваемой в reduceRight. Функция, передаваемая в другую функцию для последующего вызова называется callback, если вы не в курсе.
И да, я также считаю, что код трудночитаемый, но совсем не из-за
r[0][0]
, а из-за вложенного тренарника и использования &&
для ветвления; вложенные условия человеку всегда тяжело анализировать, особенно если записать их в одно выражение. В добавок первое условие r.length
(или, если более явно r.length != 0
) выполняется только в первый раз, и можно было передать этот элемент вторым аргументом в reduceRight
, а из массива его исключить. Когда читаешь код, обычно пытаешься мысленно его выполнить, а лишние условия только сбивают с толку.Лямбда, передаваемая в reduceRight не является коллбэком. Это не функция обратного вызова.
Обычно каша в голове и рождает как раз подобную кашу в коде.
В документации первый аргумент reduceRight называется не иначе как callback: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight
Даже педивики говорит:
Callback (англ. call — вызов, англ. back — обратный) или фу́нкция обра́тного вы́зова в программировании — передача исполняемого кода в качестве одного из параметров другого кода.
Что мы делаем? Передаем исполняемый код (эту лямбду) в качестве параметра в другой код (в reduceRight). Подходит под определение выше?
Наверно все таки не первый элемент, а нулевой.
Наверно все-таки первый элемент, имеющий нулевой индекс.
Да, правда.
Я еще скажу, что на собеседовании стресс, поэтому все знания синтаксиса кроме самых дубовых вылетают. Так что скорее всего первый вариант будет похож по кодостилю на то, с чего ты начал программировать.
То есть скорее всего это будет императивный код в стиле си
Я начинал с Алгола-60, так что си вряд ли ;) Но в целом мысль здравая, хотябы потому, что какой-то один стиль надо же выбрать, даже если знаешь и умеешь в разных.
Вы считаете что замена 10 строк с foreach и if на 5 строк с array map/reduce и тернарниками дадут какой-то значимый профит качеству крупного проекта и невозможны для понимания тем кто привык к первому варианту?
Я полагаю, что тенденция к написанию 50 строк там, где можно обойтись 25 строками, в перспективе действительно способна навредить проекту.
Я полагаю, что тенденция к написанию 50 строк там, где можно обойтись 25 строками, в перспективе действительно способна навредить проекту.
Мне кажется если у вас в одной функции 25 строк такого месива из тернарных операторов(или if`ов), уже что-то не так с декомпозицией и качеством кода, а цикломатическая сложность функции где-то за пределами досягаемости.
Распространение знаний в команде через код ревью или парное программирование не решают проблему? Чтобы заменить foreach на array_map нужен какой-то серьёзный теоретический и математический бэкграунд?
2.
Но вот с тезисом о практической бесполезности яндексовских заданий я не соглашусь. Даже рутинная разработка нет-нет, да поставит перед тобой задачу, у которой есть несколько решений
Мне казалось что для проверки умения решить практические задачи следует спрашивать про практические задачи и примеры брать из них же, а «Необходимо реализовать функцию getRanges, которая возвращает следующие результаты» слабо напоминает таковую, и слабо корреллирует с упомянутым вами «умением хорошо решать задачи».
В ваших примерах по сути одни и те же действия реализованные через разные языковые конструкции на том же уровне абстракции, изоляция сего куска кода в функцию с названием которое обозначает что она должна делать и, при необходимости, юнит-тестами показывающими как ей пользоваться закрывает вопрос с читаемостью и расширяемостью данного куска кода.
Пока что остаётся ощущение что про map/reduce спрашивается потому что интервьюеру так понравилось и он решил непременно блеснуть своими умениями перед глупыми кандидатами.
Ну то есть, в целом вы скорее правы, чем нет, но конкретный пример — ну так себе.
Статья совы-менеджера.
— Не предлагал писать код на бумаге, но просил писать в code.yandex-team.ru. Это многопользовательский онлайн-редактор кода без возможности его выполнения. Возможно, есть и другой, боле удобный вариант, но искать и было, и осталось лень;
Есть еще colabedit, когда я пытался пройти собеседование в Яндекс(весна 2017) — использовали его.
В частности, за решение задач «в духе Яндекса». Люблю и уважаю ФП, но однобуквенные переменные, тернарка в тернарке это за гранью. Можно ведь сделать проще.
К тому же, используется
Array.prototype.unshift
, что, как бы, «неправославно» в рамках функциональной парадигмы, которой вы пытаетесь следовать.Ваше решение с промисами (там, где gif), так же под вопросом. Во первых, переменная
user
там не нужна. category
можно оставить как шорткат к data.category
. Первые два запроса можно выполнить паралельно, что уменьшит время ответа.public addTicket(data: IAddTicketData): Promise<number> {
const fromEmail = data.fromEmail;
const category = data.category;
return Promise.all([
fromEmail ? this.getUserByEmail(fromEmail) : undefined,
category === 'INCIDENT' && this.isCategorizableType(data.type),
])
.then(([user, isCategorizable]) => this.twig.render('Assyst/Views/add.twig', {
from: fromEmail,
text: data.text,
category: isCategorizable ? 'INC_RFC' : category,
user,
}))
.then(xml => this.query('events', 'post', xml))
.then(response => new Promise((resolve, reject) => {
xml2js.parseString(response, (err, result) => {
const error = err || result.exception;
if (error) {
reject(error);
} else {
resolve(result.event.id - 5000000);
}
});
}));
}
const getRanges = arr => arr
.reduceRight((r, e) => r.length ? (r[0][0] === e + 1 ? r[0].unshift(e): r.unshift([e])) && r: [[e]], [])
.map(a => `${a[0]}-${a[a.length-1]}`).join(',')
Но это очень хорошо показывает всю ценность такого стиля написания кода, да. Автор статьи топит за крутость, качество, и единообразие решений, и постит в статью ошибочный код, потому что тупо не видит, что он ошибочный — да и едва ли это кто-то «на глазок» увидит, кроме совсем уж упоровшихся.
const getRanges = arr => arr
.reduceRight((r, e) => r.length ? (r[0][0] === e + 1 ? r[0].unshift(e): r.unshift([e])) && r: [[e]], [])
.map(a => a[0] + (a.length > 1 ? '-'+a[a.length - 1]: '')).join(',')
Надеюсь, достаточно нечитабельно написал, чтоб соответствовать.
getRanges([1,2,3,6]) => "1-3,6-6"
Что в очередной раз доказывает, что меньшее количество точек с запятой не являются гарантией меньшего количества багов.
На самом деле, есть и неплохие (но неполные) упражнения (в частности, игры со счётчиками и setTimeout, которые показывают понимание Event Loop и области видимости), но мне откровенно жаль, что 30+ человек попали к некомпетентному интервьюверу и я не понимаю, как крупная продуктовая компания могла такое допустить.
Option Explicit
Function GetRanges(ByRef arr() As Variant, Optional _
ByVal hyphen As String = "-") As String
Dim span(1 To 3) As Variant, element As Variant
If Not (Not Not arr) > 0 Then Exit Function
For Each element In arr
If element - span(3) > 1 Then
SpanSeparation GetRanges, span
ElseIf element - span(3) = 1 And IsEmpty(span(1)) Then
span(1) = span(3)
span(2) = hyphen
End If
span(3) = element
Next element
SpanSeparation GetRanges, span
End Function
Sub SpanSeparation(ByRef band As String, ByRef span() As Variant)
If Len(band) > 0 Then band = band & ","
band = band & Join(span, "")
Erase span
End Sub
Но вот с тезисом о практической бесполезности яндексовских заданий я не соглашусь. Даже рутинная разработка нет-нет, да поставит перед тобой задачу, у которой есть несколько решений.Может быть, я тупой, объясните пожалуйста, где и зачем на практике может пригодится функция getRanges() в первом примере? И чем плохо решить ее «в лоб» (через for/forEach + if)?
Одна из задач нашего технического интервью:
let n = 0
while (++n < 5) {
setTimeout(() => console.log(n), 10 + n)
}
// Что будет выведено в консоль?
Дальше читать не стал :)
Правильный ответ на такую задачу — «ничего», не надо так писать.
В общем, задача конечно та еще, но с другой стороны, а вы бы что предложили?
На мой взгляд, это задачка на понимание концепции. То есть, заданная как повод для рассуждений на тему, что и в каком порядке будет в таком коде происходить — почему бы и нет? В устном общении, и как тема для обсуждения.
Ну и кстати — такой ответ как у вас, я бы вполне принял, но дальше бы попросил объяснить, почему так не стоит.
Вопросы:
1. Что в нём плохо?
2. Что в нём хорошо?
3. Объясните что он делает
Вопрос №1 очень важен по любой теме, ответ на него даст вам понять кто перед вами, профессионал или не совсем, профессионал всегда знает минусы и ограничения того в чём он профессионал.
Мне не нравятся оба приведенных решения для getRanges — по моему мнению, они оба нечитаемая лапша, так как с первого взгляда трудно понять логику выполнения кода. В них идет злоупотребление функциями вроде map(), которые тут не нужны и служат лишь цели показать знание ES6, хотя в задаче этого не требуется. Вместо того, чтобы сделать решение самым простым способом, вы пишете код, который надо долго разбирать, который делает какие-то хитрые преобразования и на который будут плеваться все, кому придется его читать.
Если в Яндексе или у вас так пишут, то я сочувстсвую вашим разработчикам.
Я хочу сделать другое решение — максимально простое и последовательное, которое будет легко прочесть и понять другим разработчикам. И которое не будет демонстрировать знание функций вроде map/reduce, хотя я их знаю, хотя я и не помню список аргументов reduce без заглядывания в MDN, хотя я не вижу тут проблемы. Логика функции проста:
- сортировка значений по возрастанию
- группировка в массив диапазонов
- форматирование и склеивание диапазонов
У меня получается такой код, я использую ES3 для макс. совместимости, так как в задаче ничего про версию языка не сказано.
/**
* Принимает на вход массив значений вида [1, 2, 3, 5, 7, 8] и возвращает
* массив диапазонов вроде "1-3,5,7-8".
*
* @param {int[]} values
* @return {string}
*/
function getRanges(values) {
// Массив диапазонов вида "2-5" или "7"
var ranges = [];
function addRange(start, end) {
if (end == start) {
ranges.push(start);
} else {
ranges.push(start + "-" + end);
}
}
// Делаем копию массива и сортируем ее
values = values.slice();
values.sort();
if (!values.length) {
return "";
}
var start = values[0], end = values[0];
for (var i = 1; i < values.length; i++) {
var value = values[i];
if (value == end - 1) {
end = value;
continue;
}
addRange(start, end);
start = end = value;
}
// Добавляем последний диапазон, который мы не добавили внутри цикла
addRange(start, end);
return ranges.join(",");
}
Ну и раз уж речь зашла про задания с собеседований, вот задача, которая мне нравится, хотя возможно, она и не очень полезная. Объясните, почему в некоторых реализациях JS (например, в Firefox и Chrome) следующий код выдает такие результаты:
0.1 + 0.1 === 0.2 // true
0.1 + 0.1 + 0.1 === 0.3 // false
0.1 + 0.1 + 0.1 + 0.1 === 0.4 // true
Если вы смогли объяснить, то расскажите, каким математическим условиям должны соответствовать числа, чтобы их можно было представить в этих реализациях JS точно и без ошибок. Если не можете, то идите учить теорию, а не по собеседованиям ходите.
Если вы смогли объяснить, то расскажите, каким математическим условиям должны соответствовать числа, чтобы их можно было представить в этих реализациях JS точно и без ошибок. Если не можете, то идите учить теорию, а не по собеседованиям ходите.
Вам точно нужен тот, кто заучил всю основную проблематику IEEE 754 на память? Или это такой же обыденный выпендрёж, как и у автора статьи в духе «мне это нравится, значит все, кого я собеседую, должны в это мочь»?
Вы хотите подвести какую-то глубокую теорию под то, что в двоичной системе не все десятичные числа точно представимы?
Я вижу, вы знаете особенности двоичной системы. Тогда вам наверно не составит труда объяснить, какие из этих чисел: 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6 точно представимы и почему.
Спасибо, я вам перезвоню, хотя у меня нет вашего номера телефона и я не стану звонить незнакомому человеку просто так.
Если вы знаете представление числа в IEEE754, вы без труда ответите на вопрос или самостоятельно додумаетесь. Я тоже знаю про периодические дроби, но хотел узнать, способен ли собеседуемый разобраться, какие именно дроби будут периодическими. Я такую задачу ни у кого не спрашивал, так как кандидаты не могли нормально решить и более простые, но решил запостить на Хабр, может кто сможет решить или кому будет интересно поломать голову. При этом добавил немного провокационную концовку про обучение, в надежде подогреть интерес.
Задача, как мы видим, проверяет понимание IEEE754, знает ли человек эту систему представления чисел или лишь что-то краем уха слышал.
И должен уточнить свою задачу, выше я писал "в некоторых реализациях JS", но я тут глянул стандарт ECMA-262 и там недвусмысленно сказано, что во всех реализациях JS должна использоваться IEEE754.
Если рассуждать "остальное человек может найти", то нет смысла проводить собеседования. Нет, смысл в том, что мы задаем кандидатам похожие вопросы и задачи и по ответам определяем, кто сильнее. И у меня есть ощущение, что человек, который на собеседовании ничего не может решить, но готов погуглить, будет справляться с работой не так хорошо, как тот, который решает задачи. Хотя, как я писал выше, на практике приходится спрашивать более простые вопросы и задачи.
И у меня есть ощущение, что человек, который на собеседовании ничего не может решить, но готов погуглить, будет справляться с работой не так хорошо, как тот, который решает задачи.
Если ваши задачи состоят в том, что кто-то спрашивает, какие числа в IEEE 754 будут периодическими, а вы должны максимально быстро и полно ответить — тогда ваш тезис безусловно верен. Вопрос только в том, действительно ли у вас такие задачи.
Я много думал над тем, какие задачи надо придумывать. Они должны быть лаконичными в формулировке, понятными на слух. Реальные рабочие задачи давать трудно, так как чтобы их понять, надо знать особенности разрабатываемого ПО, а это быстро не рассказать.
Потому на собеседовании, на мой взгляд, больше подходят теоретические задачи по разным темам вроде PHP, SQL, JS и так далее.
Реальные рабочие задачи давать трудно, так как чтобы их понять, надо знать особенности разрабатываемого ПО, а это быстро не рассказать.
То есть, вы спрашиваете у людей какие-то вопросы, в равной степени не относящиеся к вашим реальным задачам, и на основании их ответов определяете, насколько они хорошо будут работать над реальными задачами?
Скажите, а коэффициент корелляции вы уже посчитали, или это всё проходит по разряду «я так вижу»?
Э, ну как сказать. Я вообще не знаю, что такое IEEE754, но при этом понимаю, что двоичное представление отличается от десятичного. И имею периодический же опыт JS начиная с 1996 года. Ну т.е. более 20 лет, плюс-минус. И на подобные вопросы я как правило отвечаю на собеседованиях примерно «как Энштейн» — это можно посмотреть в справочнике (и обычно я знаю, в каком именно).
Ну то есть, я ничего плохого не хочу сказать про ваши методы собеседования, так как не знаю, кто и зачем вам нужен. Просто при таком подходе вы очевидно потеряете часть адекватных людей. Опять же — вы кого-то потеряете почти при любом подходе, если вы не гугль, и к вам не стоит длинная очередь отличных кандидатов. Это не недостаток метода — это его ограничение.
Очевидно же, что при правильно выбранном представлении точно представимы все.
Я к тому, что NodeJS давно поддерживает async/await. А для более старых версий, код всегда можно прогнать через babel и получить поддержку. А если у вас typescript, так в конфиге указали, какая поддержка ES вам нужна, и радуетесь.
Соглашусь, понимание должно быть. Но вот что бы промисы были в приоритете — для меня звучит странно.
PS Я знаю как работают промисы, даже когда-то делал полифил для IE, что бы получить поддержку в ослике. Но когда появился async/await это же как глоток свежего воздуха.
Как вы собрались пользоваться async/await, не понимая промисов? Это ведь конструкции для работы с ними. Как вы сделаете ожидание разрешения 3 промисов, как вы поймете, корректно ли обрабатывается отказ одного из них?
Еще раз повторю — код на промисах и код на async/await — это два разных вида написания кода. И второй вид, более простой и читабельный. Если человек написал выполнение кода с async/await, с моей точки зрения это только плюс.
способных писать крутой код
Код, конечно, крутой, вот только он в 10 раз медленнее, если писать «в лоб» с циклами
(6711 мсек против 463 мсек)
<script>
function generateTest()
{
var test = [];
for (var i = 0; i < 10000000; i++)
if (Math.random() < 0.8)
test.push(i);
return test;
}
const getRanges = arr => arr
.reduceRight((r, e) => r.length ? (r[0][0] === e + 1 ? r[0].unshift(e): r.unshift([e])) && r: [[e]], [])
.map(a => a[0] + (a.length > 1 ? '-'+a[a.length - 1]: '')).join(',')
function getRanges2(arr)
{
var ranges = [];
var start = 0;
var size = arr.length;
while (start < size)
for (var i = start + 1; i <= size; i++)
{
if (i == size || arr[i] !== arr[i - 1] + 1)
{
ranges.push(i == start + 1 ? '' + arr[i - 1] : '' + arr[start] + '-' + arr[i - 1]);
start = i;
break;
}
}
return ranges.join(',');
}
window.onload = function ()
{
var a = generateTest();
var t0 = performance.now()
var tmp1 = getRanges(a);
var t1 = performance.now()
var tmp2 = getRanges2(a);
var t2 = performance.now()
alert('' + (t1 - t0) + ', ' + (t2 - t1) + '');
if (tmp1 !== tmp2)
alert('error');
}
</script>
Promise.resolve().then(() => fetch(url))
Вместо этого можно просто написать fetch(url), нет?
/* Необходимо реализовать функцию getRanges, которая возвращает следующие результаты: */ getRanges([0, 1, 2, 3, 4, 7, 8, 10]) // "0-4,7-8,10" getRanges([4,7,10]) // "4,7,10" getRanges([2, 3, 8, 9]) // "2-3,8-9"
function getRanges(arr) {
if (arr.length === 3) return "4,7,10"
else if (arr.length === 4) return "2-3,8-9"
else if (arr.length === 8) return "0-4,7-8,10"
return false;
}
Заметим в скобках, даже можно методологическое обоснование подогнать.
Кстати, не знал до этого, есть ли в js switch. С этим языком я почти незнаком. Знаю только что if есть везде
Знаю только что if есть вездеВ Assembler — нет. Ну или в Brainfuck. :]
Точно так же про это решение подумал. Каков вопрос — таков ответ. Угадывать формулу ряда по конечному набору данных — это к математике, в раздел неразрешимых задач.
Разве не будет отправлено 6 запросов? Кажется, имелось ввиду:function fetchUrl(url, attempt = 5) { return Promise.resolve() .then(() => fetch(url)) .catch(() => attempt-- ? fetchUrl(url, attempt) : Promise.reject('Fetch failed after 5 attempts')) }'error'
--attempt.
Решал похожую задачу на Scala когда выполнял задачки с курса, там я решил сделать моноид ренжей:
object Monoid {
sealed trait Ordered // это АДТ в стиле scala, всего 3 варианта
case class Range(a: Int, b: Int) extends Ordered
case object Unordered extends Ordered
case object Mempty extends Ordered
def orderMonoid: Monoid[Ordered] = new Monoid[Ordered] {
def zero: Ordered = Mempty
def op(a: Ordered, b: Ordered): Ordered = (a, b) match {
case (Range(la, ha), Range(lb, hb)) if ha <= lb => Range(la, hb)
case (Mempty, i) => i
case (i, Mempty) => i
case _ => Unordered
}
}
Объявляем энум, нас ренж либо пустой, либо неупорядочен, либо упорядоченный ренж a-b.
Ну и дальше в мой задаче нужно было определить, является ли список упорядоченным:
def isOrdered(v: Seq[Int]): Boolean = {
// превращаем каждый элемент списка в упорядоченный ренж из него же самого
// а потом делаем свертку плюсом моноида
foldMap(v, orderMonoid)(a => Range(a, a)) match {
case Unordered => false
case _ => true
}
}
Вроде все достаточно очевидно, по аналоги можно решить и эту задачу.
А однострочник из нечитаемых мутирующих функций назвать читаемым, к сожалению, не могу, как и "ФПшным".
К слову, и формулировка задачи странная:
/ Необходимо реализовать функцию getRanges, которая возвращает следующие результаты: /
Есть бесконечное множество функций которые будут выдавать такие результаты, вплоть до switch case из трех веток.
Продолжая играться с хаскеллем (уж очень он меня порадовал когда готовил последнюю статью), решил на нем сделать конкретно эту задачу:
getRanges :: [Int] -> [(Int, Int)]
getRanges = foldr go ([]) -- делаем reduce
where
go x t = case t of -- берем очередной элемент и аккумулятор
((l, r):as) | l - x == 1 -> ((x, r) : as) -- если там что-то есть и можем всунуть, то расширяем ренж текущим элементом
_ -> (x, x) : t -- иначе создаем новый интервал
main :: IO ()
main = print $ getRanges [0, 1, 2, 3, 4, 7, 8, 10]
Достаточно аккуратно. Чтобы сделать кастомный принт нужно будет немного кода дописать:
main = do
let ranges = getRanges [0, 1, 2, 3, 4, 7, 8, 10]
let format = fmap $ \x -> case x of
(a, b) | a == b -> $"{a}"
(a, b) -> $"{a}-{b}"
print $ format ranges
Чисто для популяризации Crystal — на нём реализация. Почти как Ruby, но не совсем.
alias LR = Tuple(Int32, Int32)
alias S = Tuple(Int32)
def getRanges(a : Array(Int32))
a.reduce([] of LR | S) { |total, cur|
total << (total.pop?.try { |x|
if cur - x.last <= 1
{ x.first }
else
total << x
Tuple.new
end
} || Tuple.new) + { cur }
}.map { |el| el.join('-') }.join(',')
end
А еще проще там задач нет? Скучно.
const getRanges = (numbers) => {
const normalized = numbers.slice().sort((a, b) => a - b)
let latest = null
return normalized.reduce((ranges, n) => {
const i = latest !== n - 1 ? ranges.push([]) : ranges.length
latest = n
ranges[i - 1].push(n)
return ranges
}, []).map((range) => {
return range.length === 1 ? range[0] : `${range[0]}-${range[range.length - 1]}`
}).toString()
}
Ну и чисто по фану, то же самое, только пожатое до мама не горюй ) Напишите кто-нибудь короче:
const getRanges = (n,l)=>''+n.slice().sort((a,b)=>a-b).reduce((r,n)=>(r[(l!==n-1?r.push([]):r.length)-1].push(l=n),r),[]).map(r=>r.length===1?r[0]:`${r[0]}-${r[r.length-1]}`)
Ну, так а в чем проблема то? Дубли что-ли?
Ну, так а в чем проблема то? Дубли что-ли?
Фиксится одной строкой в редьюсере, в самом начале:
if (latest === n) return ranges
Полный код:
const getRanges = (numbers) => {
const normalized = numbers.slice().sort((a, b) => a - b)
let latest = null
return normalized.reduce((ranges, n) => {
if (latest === n) return ranges
const i = latest !== n - 1 ? ranges.push([]) : ranges.length
latest = n
ranges[i - 1].push(n)
return ranges
}, []).map((range) => {
return range.length === 1 ? range[0] : `${range[0]}-${range[range.length - 1]}`
}).toString()
}
Можно еще на 5 знаков короче:
const getRanges = (n,l,x='length')=>''+n.slice().sort((a,b)=>a-b).reduce((r,n)=>(r[(l!==n-1?r.push([]):r[x])-1].push(l=n),r),[]).map(r=>r[x]===1?r[0]:r[0]+'-'+r[r[x]-1])
Возможно, они знают что-то, чего не знаем мы.
Скорее — они могут себе позволить не самые эффективные и даже странные стратегии найма, так как с доходами у них всё хорошо. Доходами от продуктов, написанных далеко не олимпиадниками-PhD с красными дипломами и черными поясами по написанию кода на бумажке, надо заметить.
Сам я ни разу не решал эту задачу до написания этой заметки
— у меня у одного вызывает диссонанс сочетание этих двух абзацев?
public static String getRanges(List<Integer> l) {
// Begin ranges & End ranges (br & er)
List<Integer> br = new ArrayList<>();
List<Integer> er = new ArrayList<>();
l.stream().reduce(l.get(0)-2, (a, b) -> {
if (Math.abs(a-b) > 1 && a < b) {
er.add(a);
br.add(b);
}
return b;
});
er.remove(0);
er.add(l.get(l.size()-1));
// Zip & join
return IntStream
.range(0, Math.min(br.size(), er.size()))
.mapToObj(i -> (br.get(i) != er.get(i))
? br.get(i) + "-" + er.get(i)
: br.get(i) + "")
.collect(Collectors.joining(", "));
}
Пс. метод полагает, что вход отсортирован
Да, что бы быстро читать код, он должен быть минимальной длинны. Но это только один из критериев. Еще более важный критерий, на мой взгляд, — сложность чтения и понимания кода.
Вот яркий пример того: ваша имплементация getRanges содержит ошибку, «ручная декомпиляция», дебаг и правка этого места займет много времени. А кроме того, даже зная все это, — вы каждый раз тут споткнетесь взглядом и потратите лишние 5-10 сек, а если вы не часто так пишете — то и до нескольких минут…
Не надо из себя строить обфускатор и минимайзер — за вас это легко сделают плагины.
В любом случае надо перебрать массив 1 раз, «синтаксический сахар» не сделает код быстрее.
private static string GetRanges(int[] ints) {
//Не ясно условие задачи, надо ли комбинировать всё или только то что находится рядом.
//ints = ints.Distinct().OrderBy(x => x).ToArray();
StringBuilder result = new StringBuilder();
for (int index = 0; index < ints.Length; index++) {
int endOfRange = FindEndOfRange(ints, index);
if (endOfRange == index) {
result.Append(ints[index]).Append(',');
} else {
result.Append(ints[index]).Append('-').Append(ints[endOfRange]).Append(',');
index = endOfRange;
}
}
if (result.Length > 0) {
result.Remove(result.Length - 1, 1);
}
return result.ToString();
}
private static int FindEndOfRange(int[] ints, int searchIndex) {
for (int index = searchIndex + 1; index < ints.Length; index++) {
if (ints[index] != ints[index - 1] + 1) {
return index - 1;
}
}
return ints.Length - 1;
}
Как Яндекс научил меня собеседовать программистов