От переводчика: опубликовали для вас статью Марии Перна (Maria Antonietta Perna), которая рассказывает о типовых задачах по JavaScript, чаще всего предлагаемых соискателям-разработчикам на собеседованиях. Статья будет полезна, в первую очередь, начинающим программистам. Ниже даны примеры решения задач, если вам кажется, что они не слишком хороши, и есть вариант получше — предлагайте альтернативу в комментариях.

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

Причем чаще всего большинство этих задач не имеют отношения к работе, которую будет выполнять соискатель, но решать их все равно нужно. Иногда приходится делать это на доске, без сверки с Google или любым другим источником. Да, ситуация постепенно меняется, и в некоторых компаниях от таких собеседований отказываются, но множество работодателей всё еще придерживается этой традиции. Эта статья посвящена разбору типовых JavaScript-задач, которые часто используются в качестве заданий для соискателей.

Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».

Skillbox рекомендует: Практический курс «Мобильный разработчик PRO».

Главное — тщательная подготовка к вашему собеседованию


Да, прежде чем начать разбирать задачи, давайте рассмотрим общие советы по подготовке к интервью.

Главное — готовиться заранее. Проверьте, насколько хорошо вы помните алгоритмы и структуры данных, и подтяните знания в тех областях, которые вам не слишком хорошо знакомы. Есть немало онлайн-платформ, которые помогут подготовиться к прохождению собеседований. Советуем GeeksforGeeks, Pramp, Interviewing.io и CodeSignal.

Стоит научиться проговаривать решение вслух. Желательно рассказывать соискателям о том, что вы делаете, а не просто писать на доске (или же набирать код в компьютере, тоже молча). Таким образом, если в коде вы допустите ошибку, но ход решения будет в общем правильным, можно увеличить свои шансы на успех.

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

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

Шаблонные задачи на JavaScript


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

Палиндром

Палиндром — слово, предложение или последовательность символов, которая абсолютно одинаково читается как в привычном направлении, так и в обратном. К примеру, “Anna” — это палиндром, а “table” и “John” — нет.

Постановка

Дана строка; нужно написать функцию, которая позволяет вернуть значение true, если строка является палиндромом, и false — если нет. При этом нужно учитывать пробелы и знаки препинания.

palindrome('racecar') === true
palindrome('table') === false


Разбираем задание

Основная идея здесь — перевернуть строку в обратном направлении. Если «реверсная» строка полностью идентична исходной, значит, мы получили палиндром и функция должна вернуть значение true. Если же нет — false.

Решение

Вот код, который позволяет решить палиндром.

const palindrome = str => {
  // turn the string to lowercase
  str = str.toLowerCase()
  // reverse input string and return the result of the
  // comparisong
  return str === str.split('').reverse().join('')
}

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

Второй шаг — реверс строки. Это сделать несложно: необходимо преобразовать ее в массив посредством метода .split() (библиотека String). Потом мы переворачиваем массив, используя .reverse() (библиотека Array). Последний этап — преобразование обратного массива в строку при помощи .join() (библиотека Array).

Теперь все, что нужно, — сравнить «обратную» строку с исходной, вернув результат true или false.

FizzBuzz

Одна из самых популярных на собеседованиях задач.

Постановка

Требуется написать функцию, выводящую в консоль числа от 1 до n, где n — это целое число, которая функция принимает в качестве параметра, с такими условиями:

  • вывод fizz вместо чисел, кратных 3;
  • вывод buzz вместо чисел, кратных 5;
  • вывод fizzbuzz вместо чисел, кратных как 3, так и 5.

Пример

Fizzbuzz(5)

Результат

// 1
// 2
// fizz
// 4
// buzz


Разбираем задание

Главное здесь — способ поиска кратных чисел с использованием JavaScript. Его можно реализовать при помощи оператора модуля или же остатка — %, который позволяет показать остаток при делении двух чисел. Если остаток 0, это означает, что первое число кратно второму.

12 % 5 // 2 -> 12 is not a multiple of 5
12 % 3 // 0 -> 12 is multiple of 3


Так, если разделить 12 на 5, получаем 2 с остатком 2. Если же разделить 12 на 3, то получаем 4 с остатком 0. В первом случае 12 не кратно 5, во втором — 12 кратно 3.

Решение

Оптимальным решением будет следующий код:

const fizzBuzz = num => {
  for(let i = 1; i <= num; i++) {
    // check if the number is a multiple of 3 and 5
    if(i % 3 === 0 && i % 5 === 0) {
      console.log('fizzbuzz')
    } // check if the number is a multiple of 3
      else if(i % 3 === 0) {
      console.log('fizz')
    } // check if the number is a multiple of 5
      else if(i % 5 === 0) {
      console.log('buzz')
    } else {
      console.log(i)
    }
  }
}

Функция выполняет нужные проверки с использованием условных операторов и выдает результат, необходимый пользователю. В задаче стоит уделить внимание порядку операторов if…else: начинать с двойного условия (&&) и заканчивать случаем, когда кратные числа найти не получилось. В результате мы охватываем все варианты.

Анаграмма

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

Постановка

Нужно написать функцию, которая проверяет, являются ли две строки анаграммами, причем регистр букв не имеет значения. Учитываются лишь символы; пробелы или знаки препинания в расчет не берутся.

anagram('finder', 'Friend') --> true
anagram('hello', 'bye') --> false


Разбираем задание

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

finder --> f: 1 friend --> f: 1
i: 1 r: 1
n: 1 i: 1
d: 1 e: 1
e: 1 n: 1
r: 1 d: 1


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

Есть и другие условия:

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

Решение

// helper function that builds the
// object to store the data
const buildCharObject = str => {
  const charObj = {}
  for(let char of str.replace(/[^\w]/g).toLowerCase()) {
    // if the object has already a key value pair
    // equal to the value being looped over,
    // increase the value by 1, otherwise add
    // the letter being looped over as key and 1 as its value
    charObj[char] = charObj[char] + 1 || 1
  }
  return charObj
}
 
// main function
const anagram = (strA, strB) => {
  // build the object that holds strA data
  const aCharObject = buildCharObject(strA)
  // build the object that holds strB data
  const bCharObject = buildCharObject(strB)
 
  // compare number of keys in the two objects
  // (anagrams must have the same number of letters)
  if(Object.keys(aCharObject).length !== Object.keys(bCharObject).length) {
    return false
  }
  // if both objects have the same number of keys
  // we can be sure that at least both strings
  // have the same number of characters
  // now we can compare the two objects to see if both
  // have the same letters in the same amount
  for(let char in aCharObject) {
    if(aCharObject[char] !== bCharObject[char]) {
      return false
    }
  }
  // if both the above checks succeed,
  // you have an anagram: return true
  return true
}

Обратите внимание на использование Object.keys() в сниппете выше. Этот метод возвращает массив, содержащий имена или ключи в таком же порядке, в каком они встречаются в объекте. В этом случае массив будет таким:

['f', 'i', 'n', 'd', 'e', 'r']

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

Поиск гласных

Достаточно простая задача, которая часто попадается на собеседованиях.

Постановка

Нужно написать функцию, принимающую строку в качестве аргумента и возвращающую количество гласных, которые содержатся в строке.
Гласными являются «a», «e», «i», «o», «u».

Пример:

findVowels('hello') // --> 2
findVowels('why') // --> 0


Решение

Вот самый простой вариант:

const findVowels = str => {
  let count = 0
  const vowels = ['a', 'e', 'i', 'o', 'u']
  for(let char of str.toLowerCase()) {
    if(vowels.includes(char)) {
      count++
    }
  }
  return count
}

Важно обратить внимание на использование метода .includes(). Он доступен и для строк, и для массивов. Его стоит применять для того, чтобы выявить, содержит ли массив определенное значение. Этот метод возвращает true, если массив содержит указанное значение, и false, если нет.

Есть и более краткое решение проблемы:

const findVowels = str => {
  const matched = str.match(/[aeiou]/gi)
  return matched ? matches.length : 0
}

Здесь задействуется метод .match(), который позволяет реализовать эффективный поиск. Если регулярное выражение как аргумент метода обнаружено внутри указанной строки, то возвращаемым значением становится массив совпадающих символов. Ну а если совпадений нет, то .match() возвращает null.

Фибоначчи

Классическая задача, которую можно встретить на собеседованиях самого разного уровня. Стоит напомнить, что последовательность Фибоначчи — это ряд чисел, где каждое последующее является суммой двух предыдущих. Так, первые десять чисел выглядят следующим образом: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34.

Постановка

Нужно написать функцию, которая возвращает n-ную запись в определенной последовательности, причем n — число, которое передается в качестве аргумента функции.

fibonacci(3) // --> 2

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

Решение

const fibonacci = num => {
  // store the Fibonacci sequence you're going
  // to generate inside an array and
  // initialize the array with the first two
  // numbers of the sequence
  const result = [0, 1]
 
  for(let i = 2; i <= num; i++) {
    // push the sum of the two numbers
    // preceding the position of i in the result array
    // at the end of the result array
    const prevNum1 = result[i - 1]
    const prevNum2 = result[i - 2]
    result.push(prevNum1 + prevNum2)
  }
  // return the last value in the result array
  return result[num]
}

В массиве результатов первые два числа содержатся в ряду, поскольку каждая запись в последовательности состоит из суммы двух предыдущих чисел. В самом начале двух чисел, которые можно взять для получения следующего числа нет, поэтому цикл не может сгенерировать их в автоматическом режиме. Но, как мы знаем, первые два числа — всегда 0 и 1. Поэтому инициализировать массив результатов можно вручную.

Что касается рекурсии, то здесь все проще и сложнее одновременно:

const fibonacci = num => {
  // if num is either 0 or 1 return num
  if(num < 2) {
    return num
  }
  // recursion here
  return fibonacci(num - 1) + fibonacci(num - 2)
}

Мы продолжаем вызывать fibonacci(), передавая все меньшие числа в качестве аргументов. Останавливаемся в случае, когда переданный аргумент равен 0 или 1.

Вывод


Скорее всего, вы уже сталкивались с какой-либо из указанных задач, если проходили собеседования на работу frontend- или JavaScript-разработчика (особенно если это уровень junior). Но если они вам и не попадались, то могут пригодиться в будущем — как минимум для общего развития.
Skillbox рекомендует:

Комментарии (35)


  1. catsmile
    26.03.2019 13:23

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

    str.toLowerCase().split('').filter(char => { return char.match(/\w/); }).sort().join('')


    1. ZoNT
      26.03.2019 16:39

      const convert = str => str.toLowerCase().match(/\w/g).sort().join('');
      const anagram = (strA, strB) => convert(strA) === convert(strB);


      1. Zavtramen
        26.03.2019 21:58

        На пустой строке выдаст ошибку, match не отработает, равно как и на строке с русскими буквами или знаками препинания, в общем на любой где \w нет.
        Мало того из строки вырежутся, к примеру, русские буквы, и строки 'abc' и 'abcабц' будут равны


        1. ZoNT
          27.03.2019 09:18

          Это проблемы постановки задачи, на это уже отвечали ниже: habr.com/ru/company/skillbox/blog/445360/?reply_to=19946684#comment_19944882


          1. Zavtramen
            27.03.2019 11:10

            Ваше решение падает на пустой строке, причем здесь постановка задачи?


            1. ZoNT
              27.03.2019 12:33

              const convert = str => str.toLowerCase().match(/\w/g).sort().join('');
              const anagram = (strA, strB) => strA !== '' && strB !== '' && convert(strA) === convert(strB);

              Так лучше?
              Следует также учесть, что анаграмма это «Слово или словосочетание, образованное путём перестановки букв, составляющих другое слово (или словосочетание).». То есть пустая строка не может быть анаграммой.


  1. edogs
    26.03.2019 13:29

    FizzBuzz — мы бы не делали лишних проверок.

    тынц
    Не так
    const fizzBuzz = num => {
      for(let i = 1; i <= num; i++) {
        // check if the number is a multiple of 3 and 5
        if(i % 3 === 0 && i % 5 === 0) {
          console.log('fizzbuzz')
        } // check if the number is a multiple of 3
          else if(i % 3 === 0) {
          console.log('fizz')
        } // check if the number is a multiple of 5
          else if(i % 5 === 0) {
          console.log('buzz')
        } else {
          console.log(i)
        }
      }
    }

    А так
    const fizzBuzz = num => {
      for(let i = 1; i <= num; i++) {
        if(i % 3 !== 0 || i % 5 !== 0) console.log(i);
       else {
        if(i % 3 === 0) console.log('fizz')
        if(i % 5 === 0) console.log('buzz')
      }
    }


    1. e_fail
      26.03.2019 14:09
      +1

      У вас в выводе fizzbuzz будет размазан на две строки?


      1. timon_aeg
        26.03.2019 14:26

        Тогда process.stdout.write


    1. Stepanow
      26.03.2019 15:07

      if(i % 3 !== 0 || i % 5 !== 0) console.log(i);

      А почему ИЛИ? У вас так никогда не будет fizz или buzz, только fizzbuzz, да и то на разных строках.
      Вот так правильно:
      const fizzBuzz = num => {
        for(let i = 1; i <= num; i++) {
          if(i % 3 !== 0 && i % 5 !== 0){ 
            console.log(i);
          } else {
            console.log((i % 3 === 0 ? 'fizz' : '') + (i % 5 === 0 ? 'buzz' : ''));
          }
        }
      };
      fizzBuzz(100);


  1. kahi4
    26.03.2019 13:57

    FizzBuzz

    Оптимальным решением будет следующий код:

    Ужасная конструкция с нулевой читаемостью. Я даже не буду придираться что совместную кратность можно было проверить как-то как i % (3 * 5), но вложенные if-else читать невозможно и стоит использовать continue.


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


    Вот код, который позволяет решить палиндром.

    5 копий строки ради задачи, которая решается всего с одной дополнительной переменной. Браво. (Хорошо, к нижнему регистру приводить нужно все равно).


    Второй шаг — реверс строки. Это сделать несложно: необходимо преобразовать ее в массив посредством метода .split() (библиотека String). Потом мы переворачиваем массив, используя .reverse() (библиотека Array). Последний этап — преобразование обратного массива в строку при помощи .join() (библиотека Array).

    Первый раз слышу, чтобы в js кто-то Array и String называл библиотеками, а не встроенными типами, ну да ладно.


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

    Всегда думал, что рекурсивное решение является "стандартным" и все всегда просят переписать на цикл (без использования массива. Я начинаю понимать почему весь веб тормозит), заодно спрашивают чем решение с рекурсией плохое.


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


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


    1. ArVaganov
      26.03.2019 16:49
      +1

      Но оказывается, что js (и много кто) не умеет в хвостовую рекурсию

      В общем-то может, но для этого должны выполняться определенные условия.
      Тут страница 62 и дальше.

      Там как раз Николас рассматривает факториал


  1. Sirion
    26.03.2019 14:16

    За такое рекурсивное нахождение чисел Фибоначчи лично я бы сразу развернул интервьюируемого на выход как злостного вредителя. И вообще статья жесть.


  1. e_fail
    26.03.2019 14:18
    +1

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


    const fibonacci = num => {
    // if num is either 0 or 1 return num
    if(num < 2) {
    return num
    }
    // recursion here
    return fibonacci(num — 1) + fibonacci(num — 2)
    }


    Аааа, ужас-ужас-ужас!!!

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


    1. Sirion
      26.03.2019 14:20
      +1

      Ну, быстрое возведение в степень матриц — это всё-таки уже немного перебор для JS)


  1. tuxi
    26.03.2019 14:24

    Анаграммы в реальном проекте не используем, поэтому предлагаем вот такую задачку (одну из 3)
    «написать на javascript (ECMAscript например не старше 5) код, который будет мониторить результаты любых ajax запросов расположенных на той же странице и в случае ошибок отправлять данные на сервер логов/статистики»
    В ответах мы смотрим на оформление кода, на попытки подключения сторонних либ и самое главное, наличие списка уточняющих вопросов :)


    1. Waxer
      26.03.2019 16:09

      Мне вот интересно как вы fetch будете мониторить?


      1. tuxi
        26.03.2019 17:36

        а ограничение версии ecma не больше 5-й не напрягает?


        1. Waxer
          27.03.2019 16:32

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


          1. tuxi
            27.03.2019 17:59

            Во-вторых как вам поможет, что кандидат знает или не знает как перекрыть метод send?
            до сих пор помогало :)
            Тестовое задание вполне себе покрывает часть наших нужд по теме фронтенда. Главное чтобы человек думать умел, остальному — научим/научится


  1. shaukote
    26.03.2019 14:25
    +1

    По поводу проверки на палиндром.

    Во-первых, странно, что вообще не упомянуто самое эффективное решение, оно не больно-то сложнее приведённого:

    function palindrome(str) {
        for (let i = 0; i < Math.floor(str.length / 2); i++) {
            if (str[i] !== str[str.length - i - 1]) {
                return false;
            }
        } 
    
        return true;
    }

    А во-вторых задачки на проверку на палиндром и вообще на переворот строки простые только до тех пор, пока в дело не вступает Unicode. %)

    Самый популярное решение работает корректно только для символов из BMP, для остальных разрушаются их суррогатные пары со всеми вытекающими последствиями:
    const reverse = str => str.split('').reverse().join('');
    // хабр не может в такие иероглифы ?\_(?)_/?
    reverse('A \u{2f804} A');  // 'A ?? A' 
    reverse('A \u{2f804} A') === 'A \u{2f804} A'; // false
    


    Это относительно несложно исправить, пользуясь тем, что при итератор по строке обходит code point'ы, а не code unit'ы:
    const reverse = str => [...str].reverse().join('');
    reverse('A \u{2f804} A');  // 'A ? A' — тут всё ок, мамой клянусь
    reverse('A \u{2f804} A') === 'A \u{2f804} A'; // true
    


    Однако даже это не спасает, когда в дело вступают графемные кластеры:
    const reverse = str => [...str].reverse().join('');
    const str = 'А \u{0415}\u{0308} А';  // нормальная такая Ё
    reverse(str);  // 'А ?Е А'
    reverse(str) === str; // true
    


    Универсального решения, работающего во всех случаях, я не знаю, увы.


    1. ArVaganov
      26.03.2019 16:32

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

      Задача может усложняться разными регистрами и пробелами, вроде «А роза упала на лапу азора».

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


    1. Aingis
      27.03.2019 12:02

      Есть же String.prototype.normalize().

      [...'А \u{0415}\u{0308} А'.normalize()].reverse().join`` // "А Ё А"


  1. shaukote
    26.03.2019 14:38

    итератор по строке обходит code point'ы, а не code unit'ы
    Это, кстати, приводит к некоторым любопытным последствиям:
    const str = 'A \u{2f804} A';
    [...str].length === str.length;  // false
    [...str].map(char => char.length);  // [1, 1, 2, 1, 1]
    
    


    1. Sirion
      26.03.2019 14:58

      Каждый раз, когда я начинаю думать, что знаю JS досконально, находится что-нибудь в этом роде и меня разубеждает)


  1. DeniSun
    26.03.2019 14:41

    palindrome('race car') // false

    Неплохо бы добавить
    str = str.toLowerCase().replace(/ /g,'');

    palindrome('А роза упала на лапу Азора') // true
    // есть еще вариант с пенсионным фондом
    palindrome('Оголи жопу пожилого'); // true


  1. Electrohedgehog
    26.03.2019 15:17

    Мда, эксперты…
    Код на проверку палиндрома не сработает, как писали выше, если будут пробелы и знаки препинания.
    Fizzbuzz решён прямо в тупую, никакого понимания вообще.
    Анаграммы… странноваты
    Гласные вообще решаются в одну строку, два действия(если нет дополнительных условий).
    Только Фибоначчи нормально.
    Если эта дама людей собеседует, то ей стоит слегка поднять уровень.


  1. keslo
    26.03.2019 15:25

    Внесу и я свою лепту в fizzbuzz :)

    function Fizzbuzz (num) {
      let response = '';
      if (!(num % 3)) response += 'fizz';
      if (!(num % 5)) response += 'buzz';
      return response || num;
    }
    


    1. Zavtramen
      26.03.2019 15:31

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

      function fizzbuzz(n) {
          var i, s; 
          for (i = 1; i <= n; i++) {
              s = '';
              if (i % 3 == 0) s = 'fizz';
              if (i % 5 == 0) s += 'buzz';
              console.log(s ? s : i);
          }
      }
      


      P.S. в задаче требуется написать функцию которая выводит от 1 до n в консоль. Ваша функция этого не делает, и еще со строчной обычно называют конструкторы, это если придираться ;)


      1. keslo
        26.03.2019 16:28

        Согласен с вами.


    1. Electrohedgehog
      26.03.2019 15:32

      Каноничный вариант.


  1. Cryvage
    26.03.2019 15:34

    В анаграмме у вас две ошибки в одной строке:

    for(let char of str.replace(/[^\w]/g).toLowerCase()) { ...
    


    Во-первых, у метода replace должно быть 2 аргумента, иначе он заменит найденный символ на undefined. Вероятно это опечатка, но надо же проверять примеры, перед тем как публиковать статью.
    Во-вторых, данное решение работает только с латинскими символами. В JS, паттерны, вроде \w не рассчитаны на юникод. ИМХО, это самое больное место регулярных выражений в JS. Правильная реализация будет выглядеть как-то так:
    const notALetterRegEx = /[^A-Za-z?µ?A-OO-oo-??-??-????-??-??-???-???-??-??-??-??-???-??-??-??-??-??-???-??-??-????-??-???-??-???-????-??-??-??-??-??-??-???-????-??-??-??-??-??-??-??-??-??-??-???-??-??-??-??-??-??-????-??-??-??-??-??-??-???-??-????-??-??-??-???-??-??-??-???-??-??-??-??-???-??-??-??-??-??-??-????-??-??-??-??-???-??-??-??-??-???-??-??-??-??-???-????-??-??-????-??-??-???-???-???-??-??-??-???-??-???-??-??-???-??-???-??-??-??-??-??-???-??-??-??-??-??-???-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-????-??-???-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-?????-??-??-???-??-??-??-??-??-??-????-????-???-?????-??-?-??-???-??-??-??-??-??-??-??-???-??-??-??-??-??-??-??-??-???-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-??-???-??-??-???-??-??-??-??-??-??-??-??-?A-Za-z?-??-??-??-??-?]|[\ud840-\ud868][\udc00-\udfff]|\ud800[\udc00-\udc0b\udc0d-\udc26\udc28-\udc3a\udc3c-\udc3d\udc3f-\udc4d\udc50-\udc5d\udc80-\udcfa\ude80-\ude9c\udea0-\uded0\udf00-\udf1e\udf30-\udf40\udf42-\udf49\udf80-\udf9d\udfa0-\udfc3\udfc8-\udfcf]|\ud801[\udc00-\udc9d]|\ud802[\udc00-\udc05\udc08\udc0a-\udc35\udc37-\udc38\udc3c\udc3f\udd00-\udd15\udd20-\udd39\ude00\ude10-\ude13\ude15-\ude17\ude19-\ude33]|\ud808[\udc00-\udf6e]|\ud835[\udc00-\udc54\udc56-\udc9c\udc9e-\udc9f\udca2\udca5-\udca6\udca9-\udcac\udcae-\udcb9\udcbb\udcbd-\udcc3\udcc5-\udd05\udd07-\udd0a\udd0d-\udd14\udd16-\udd1c\udd1e-\udd39\udd3b-\udd3e\udd40-\udd44\udd46\udd4a-\udd50\udd52-\udea5\udea8-\udec0\udec2-\udeda\udedc-\udefa\udefc-\udf14\udf16-\udf34\udf36-\udf4e\udf50-\udf6e\udf70-\udf88\udf8a-\udfa8\udfaa-\udfc2\udfc4-\udfcb]|\ud869[\udc00-\uded6]|\ud87e[\udc00-\ude1d]/ug;
    for(let char of str.replace(notALetterRegEx, "").toLowerCase()) { ...
    

    Выглядит дико, зато работает. Естественно, на собеседовании такое никто по памяти не напишет. Но, как мне кажется, кандидат должен отметить, что решение работает только с латинским алфавитом, а для поддержки других языков понадобится либо отдельная библиотека, такая как XRegExp, позволяющая использовать паттерны вроде p{L}, либо длиннющая регулярка, как правило сгенерированная каким-то сервисом. Например, данную простыню я сгенерировал здесь.


  1. Aries_ua
    26.03.2019 22:13

    Была еще задача на комплементарность ДНК. Решение ниже

    const inp = 'ACCGGGTTTT';
    const out = 'AAAACCCGGT';
    
    // A -> T
    // C -> G
    
    // set mirror
    // replace A to T
    // replace C to G
    // replace T to A
    // replace G to T
    
    function dnaComplement(str) {
      let newStr = '';
    
      for (let i = str.length - 1; i >= 0; i--) {
        let s = str[i];
    
        if (s === 'A') {
          newStr = newStr + 'T';
        } else if (s === 'T') {
          newStr = newStr + 'A';
        } else if (s === 'C') {
          newStr = newStr + 'G';
        } else if (s === 'G') {
          newStr = newStr + 'C';
        }
      }
    
      return newStr;
    }
    
    let resp = dnaComplement(inp);
    
    console.log('resp ->', resp);
    console.log(out === resp ? 'Done' : 'Wrong');


    Немного оффтопа
    Однажды пригласили меня на собеседование в одну фирму на позицию Senior JS developer (дело было в Кракове, Польша). В начале поговорили о жизни и ни о чем. Плавно перешли к менеджменту и микроменеджменту. И вот один из собеседователей говорит — ну что размялись, давайте теперь сделаем техническое интервью. Ну ок, не вопрос, конечно. И тут парень открывает гугл! А мне был виден его монитор немного, так как за спиной у него стеклянная стена и я видел отражение. И начинает читать вопросы для собеседования по JS с гугла Карл! Ты же сеньора собеседуешь! Ну как же так?!

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


  1. paratagas
    27.03.2019 12:10

    Мне кажется, что анаграммы можно сделать намного проще:

     function anagram(srt1, str2) {
      const srt1Prepared = srt1.replace(/[^\w\s]|_/g, "").toLowerCase().split('').sort().join('');
      const srt2Prepared = str2.replace(/[^\w\s]|_/g, "").toLowerCase().split('').sort().join('');
      
      return srt1Prepared === srt2Prepared;
    }
    
    console.log(anagram('finder', 'Friend')); // true
    console.log(anagram('hello', 'bye')); // false
    console.log(anagram('', '')); // true 


  1. wheercool
    29.03.2019 07:08

    К слову сказать Object.keys не сохраняет порядок ключей