Pull to refresh

Comments 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]]
Оно абсолютно нечитаемо. Это практически обфусцированный код.
UFO just landed and posted this here
этот вариант точно не хуже вереницы if/foreach,
по мне это ясное выражение
Здесь нет никаких магических чисел, r[0][0] это первый элемент первого массива результата предыдущих применений этого коллбэка. Это довольно очевидно.

Или вы предлагаете ввести константу FIRST_ARRAY_ELEMENT?

Например, присвоить это значение переменной с понятным именем

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

Этот «кусок кода» — содержимое функции передаваемой в reduceRight. Функция, передаваемая в другую функцию для последующего вызова называется callback, если вы не в курсе.

И да, я также считаю, что код трудночитаемый, но совсем не из-за r[0][0], а из-за вложенного тренарника и использования && для ветвления; вложенные условия человеку всегда тяжело анализировать, особенно если записать их в одно выражение. В добавок первое условие r.length (или, если более явно r.length != 0) выполняется только в первый раз, и можно было передать этот элемент вторым аргументом в reduceRight, а из массива его исключить. Когда читаешь код, обычно пытаешься мысленно его выполнить, а лишние условия только сбивают с толку.
Коллбэк это по определению функция обратного вызова. В жаваскрипте выполняются асинхронно.
Лямбда, передаваемая в reduceRight не является коллбэком. Это не функция обратного вызова.
Обычно каша в голове и рождает как раз подобную кашу в коде.
Очень жаль, что у вас такая каша в голове. Коллбэк не имеет никакого отношения к асинхронщине.

Даже педивики говорит:
Callback (англ. call — вызов, англ. back — обратный) или фу́нкция обра́тного вы́зова в программировании — передача исполняемого кода в качестве одного из параметров другого кода.


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

Наверно все-таки первый элемент, имеющий нулевой индекс.
Его назначение не следует из формы.
По такому коду сразу видно «командного игрока».
Я, конечно, JavaScript-ер, и не боюсь компактного кода, но идея использовать в выражениях методы с побочкой — на мой взгляд, так себе.
Согласен. Оно трудночитаемо и сходу непонятно, что хотел сказать автор. Особенно весело такое дебажить. Иногда лучше не экономить, а честно написать императивный код. Работать будет также, а читать в сто раз проще (верно и обратное, но для других случаев).

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

>скорее всего это будет императивный код в стиле си
Я начинал с Алгола-60, так что си вряд ли ;) Но в целом мысль здравая, хотябы потому, что какой-то один стиль надо же выбрать, даже если знаешь и умеешь в разных.

У всех наверное по разному, у меня вот на собеседованиях чисто сишный стиль выходит. Такое ощущение, что стресс все знание абстракций сдувает

UFO just landed and posted this here
При большом количестве прогонов (>= тому, что в тесте) обычный random там дает первую-вторую цифру после запятой. Так что видимо да, надо подправлять его на ходу.
Чем то напоминает микроменеджмент, если честно.
Вы считаете что замена 10 строк с foreach и if на 5 строк с array map/reduce и тернарниками дадут какой-то значимый профит качеству крупного проекта и невозможны для понимания тем кто привык к первому варианту?

Я полагаю, что тенденция к написанию 50 строк там, где можно обойтись 25 строками, в перспективе действительно способна навредить проекту.

Зависит от читаемости строк. Это ж не one-line-code challenge. Коротко хорошо только тогда, когда оно также легко читается.
1.
Я полагаю, что тенденция к написанию 50 строк там, где можно обойтись 25 строками, в перспективе действительно способна навредить проекту.

Мне кажется если у вас в одной функции 25 строк такого месива из тернарных операторов(или if`ов), уже что-то не так с декомпозицией и качеством кода, а цикломатическая сложность функции где-то за пределами досягаемости.

Распространение знаний в команде через код ревью или парное программирование не решают проблему? Чтобы заменить foreach на array_map нужен какой-то серьёзный теоретический и математический бэкграунд?

2.
Но вот с тезисом о практической бесполезности яндексовских заданий я не соглашусь. Даже рутинная разработка нет-нет, да поставит перед тобой задачу, у которой есть несколько решений

Мне казалось что для проверки умения решить практические задачи следует спрашивать про практические задачи и примеры брать из них же, а «Необходимо реализовать функцию getRanges, которая возвращает следующие результаты» слабо напоминает таковую, и слабо корреллирует с упомянутым вами «умением хорошо решать задачи».

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

Пока что остаётся ощущение что про map/reduce спрашивается потому что интервьюеру так понравилось и он решил непременно блеснуть своими умениями перед глупыми кандидатами.
UFO just landed and posted this here
Лишь то читается легко,
Что написано с трудом.
Что в час написано,
То в час и позабыто.

А.С., он самый, наш, Пушкин
Вообще-то, полезность скажем fold/reduce состоит в том, что оно более явно, чем циклы, выражает намерение автора. Т.е. мы берем массив, и схлопываем его при помощи функции. Но в данном случае этого не происходит, в частности потому, что ваша функция свертки довольно таки нетривиальная, и ее собственные намерения вообще не ясны, пока не вчитаешься, и не поймешь назначение верхнего уровня, так сказать.

Ну то есть, в целом вы скорее правы, чем нет, но конкретный пример — ну так себе.
Минификация и так сожмет код до минимума. А в исходниках он должен быть понятным, т.к. код читается больше чем пишется.
Статья совы-менеджера.
— Не предлагал писать код на бумаге, но просил писать в code.yandex-team.ru. Это многопользовательский онлайн-редактор кода без возможности его выполнения. Возможно, есть и другой, боле удобный вариант, но искать и было, и осталось лень;

Есть еще colabedit, когда я пытался пройти собеседование в Яндекс(весна 2017) — использовали его.
Если чисто для JS, то премиум версия Сodepen фаворит без вариантов.
А кто именно в данном случае должен платить за CodePen? Все пользователи или только создатель комнаты?
Какой кошмар. Спасибо, что пролили свет на интервью в Яндексе.

В частности, за решение задач «в духе Яндекса». Люблю и уважаю ФП, но однобуквенные переменные, тернарка в тернарке это за гранью. Можно ведь сделать проще.

К тому же, используется 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);
      }
    });
  }));
}
UFO just landed and posted this here
Потому что надо
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(',')

Но это очень хорошо показывает всю ценность такого стиля написания кода, да. Автор статьи топит за крутость, качество, и единообразие решений, и постит в статью ошибочный код, потому что тупо не видит, что он ошибочный — да и едва ли это кто-то «на глазок» увидит, кроме совсем уж упоровшихся.
Так лучше, но тест [4,7,10] не проходит.
Хорошо-хорошо, пусть будет так
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(',')

Надеюсь, достаточно нечитабельно написал, чтоб соответствовать.
UFO just landed and posted this here
Так и не будет — из того, что про постановку задачи видно, это невалидные входные данные. Валидные — отсортированные по возрастанию натуральные числа без повторов.
И это тоже неправильный вариант :)

getRanges([1,2,3,6]) => "1-3,6-6"

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

На самом деле, есть и неплохие (но неполные) упражнения (в частности, игры со счётчиками и setTimeout, которые показывают понимание Event Loop и области видимости), но мне откровенно жаль, что 30+ человек попали к некомпетентному интервьюверу и я не понимаю, как крупная продуктовая компания могла такое допустить.
Пример с setTimeout — это «проверка нулевого уровня», нечто такое, что позволяет отфильтровать людей, которые и правда что-то делали от людей, которые setTimeout разве что в книжках видели, да и то мельком. В этом смысле это нормальное упражнение, лично я считаю, что как минимум одно такое (а лучше побольше) должно быть на каждом собеседовании как самый очевидный тест для отсева самозванцев.
Хорошая тема для холиваров: нужно ли преподавать basic в школах?
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)?
Задачка превратить набор точек или набор пересекающихся отрезков в набор непересекающихся отрезков попадается на практике. Мне она периодически встречается в работе, например при анализе каких-нить данных, чтобы понять, где дырки.
Второй вопрос забыли. Потому что да, самое эффективное решение тут — тупо в лоб однократным проходом. А хипстерский yandex-way из статьи ковыряется намного больше.
По второму вопросу я возражений не имею, отметил лишь, что задачка не с потолка.
понял, спасибо. Если несложно, хотел бы увидеть какую-то конкретную задачу. Если несложно. Я не придираюсь, действительно интересно. Я в области анализа данных не силен вообще, было бы интересно.
Можно например поискать интервалы простоя чего-нибудь. Процессора или билд-агента. Можно вычислять свободное время группы людей, имея их расписания с пересекающимися занятыми интервалами и т.п.
Одна из задач нашего технического интервью:

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 лет, плюс-минус. И на подобные вопросы я как правило отвечаю на собеседованиях примерно «как Энштейн» — это можно посмотреть в справочнике (и обычно я знаю, в каком именно).

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

Очевидно же, что при правильно выбранном представлении точно представимы все.

Зачем Вы выходите из контекста? Речь в этой ветке идёт сугубо об IEEE754

Затем, что контекст — штука плавающая, и ее полезно задавать явно.

Ну, знаете, «в некоторых реализациях JS», как мне кажется, достаточно хорошо указывает обсуждаемый формат представления дробных чисел.
Ваша функция работает неверно
Уже занёс руку поставить плюсик, мол, вот адекватный интевьюер. А потом — опа! — те же таймауты, только сбоку
Скажите, а почему на беке именно важны промисы. Вот прям так, что бы на них писать код.

Я к тому, что NodeJS давно поддерживает async/await. А для более старых версий, код всегда можно прогнать через babel и получить поддержку. А если у вас typescript, так в конфиге указали, какая поддержка ES вам нужна, и радуетесь.

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

PS Я знаю как работают промисы, даже когда-то делал полифил для IE, что бы получить поддержку в ослике. Но когда появился async/await это же как глоток свежего воздуха.

Как вы собрались пользоваться async/await, не понимая промисов? Это ведь конструкции для работы с ними. Как вы сделаете ожидание разрешения 3 промисов, как вы поймете, корректно ли обрабатывается отказ одного из них?

Я где-то выше написал, что непонимаю промисов и как они работают? Или я не знаю, что asyn/await это всего лишь сахар?

Еще раз повторю — код на промисах и код на 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), нет?
UFO just landed and posted this here
Подозреваю вопрос и предполагает развитие беседы в стиле уточнения граничных условий интервьюируемым, либо же дополнительные вопросы от интервьюера по развитию и доработке кода после решения задачи только для указанных изначально кейсов.
300iq решение (не воспринимайте серьезно, просто шутка)
/* Необходимо реализовать функцию 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;
}

Заметим в скобках, даже можно методологическое обоснование подогнать.

Почему то вспомнил как где то читал что даже оверрайд хешкода на захардкоженное значение можно оправдать))
решение неправильное. нужно было использовать switch
:DD
Кстати, не знал до этого, есть ли в js switch. С этим языком я почти незнаком. Знаю только что if есть везде
Знаю только что if есть везде
В Assembler — нет. Ну или в Brainfuck. :]
От ассемблера зависит. Как директива препроцессора наверное почти везде есть. На многих их можно реализовать на макросах. В форт-ассемблерах почти всегда есть высокоуровневые конструкции из форта.
UFO just landed and posted this here
UFO just landed and posted this here

Точно так же про это решение подумал. Каков вопрос — таков ответ. Угадывать формулу ряда по конечному набору данных — это к математике, в раздел неразрешимых задач.

function fetchUrl(url, attempt = 5) {
  return Promise.resolve()
    .then(() => fetch(url))
    .catch(() => attempt-- ? fetchUrl(url, attempt) : Promise.reject('Fetch failed after 5 attempts'))
}'error'
Разве не будет отправлено 6 запросов? Кажется, имелось ввиду:
--attempt
.
UFO just landed and posted this here

Решал похожую задачу на 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
Ну это все-таки немного неспортивно, в оригинале был ES, а там система типов слегка так слабее чем у скалы и хаскеля. Но как направление куда двигаться — почему бы и нет?
UFO just landed and posted this here
UFO just landed and posted this here

Чисто для популяризации 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]}`)
UFO just landed and posted this here

Ну, так а в чем проблема то? Дубли что-ли?

Ну, так а в чем проблема то? Дубли что-ли?
Фиксится одной строкой в редьюсере, в самом начале:


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()
}
UFO just landed and posted this here
UFO just landed and posted this here

Можно еще на 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])
Самое грустное в этих собеседованиях, что интервьюеры не пытаются создать подходящую дружескую атмосферу, прежде чем переходить к таким задачкам, над которыми надо поломать голову. Задачку можно брать любую, но чтобы это помогало оценить уровень человека — нужно на интервью именно парным программированием заниматься, а не наблюдать со стороны как кандидат мучается.
UFO just landed and posted this here
UFO just landed and posted this here
Возможно, они знают что-то, чего не знаем мы.

Скорее — они могут себе позволить не самые эффективные и даже странные стратегии найма, так как с доходами у них всё хорошо. Доходами от продуктов, написанных далеко не олимпиадниками-PhD с красными дипломами и черными поясами по написанию кода на бумажке, надо заметить.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

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

И попросил избавиться от ключевых слов async/await. С тех пор это задание стало первым и, в половине случаев, последним на собеседовании — его реально часто заваливают.

Сам я ни разу не решал эту задачу до написания этой заметки


— у меня у одного вызывает диссонанс сочетание этих двух абзацев?
Разрешите влезть со своим решением на жабе :D

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(", "));
}

Пс. метод полагает, что вход отсортирован

Долго пытался понять, что такое 1.stream(). Не даром во всех книжках пишут, что испольовать l, I и о в качестве имен переменных — плохая идея.

Код должен быть легко и быстро читаем. Это обеспечивает легкость и небольшие временные затраты на поддержку. Кроме того код может быть передан на поддержку менее клалифицированной команде.
Да, что бы быстро читать код, он должен быть минимальной длинны. Но это только один из критериев. Еще более важный критерий, на мой взгляд, — сложность чтения и понимания кода.

Вот яркий пример того: ваша имплементация getRanges содержит ошибку, «ручная декомпиляция», дебаг и правка этого места займет много времени. А кроме того, даже зная все это, — вы каждый раз тут споткнетесь взглядом и потратите лишние 5-10 сек, а если вы не часто так пишете — то и до нескольких минут…
Не надо из себя строить обфускатор и минимайзер — за вас это легко сделают плагины.
В любом случае надо перебрать массив 1 раз, «синтаксический сахар» не сделает код быстрее.

Как бы я сделал на шарпе и разбиением 2 на легко читаемых метода
        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;
        }

Sign up to leave a comment.

Articles