Pull to refresh

Comments 53

UFO just landed and posted this here
const convert = str => str.toLowerCase().match(/\w/g).sort().join('');
const anagram = (strA, strB) => convert(strA) === convert(strB);
На пустой строке выдаст ошибку, match не отработает, равно как и на строке с русскими буквами или знаками препинания, в общем на любой где \w нет.
Мало того из строки вырежутся, к примеру, русские буквы, и строки 'abc' и 'abcабц' будут равны
Ваше решение падает на пустой строке, причем здесь постановка задачи?
const convert = str => str.toLowerCase().match(/\w/g).sort().join('');
const anagram = (strA, strB) => strA !== '' && strB !== '' && convert(strA) === convert(strB);

Так лучше?
Следует также учесть, что анаграмма это «Слово или словосочетание, образованное путём перестановки букв, составляющих другое слово (или словосочетание).». То есть пустая строка не может быть анаграммой.
const str = s => [...s].sort().join('');
const anagram = (s1, s2) => s1 && str(s1) === str(s2) || false;


Так изящнее.
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')
  }
}

У вас в выводе fizzbuzz будет размазан на две строки?
UFO just landed and posted this here
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);
FizzBuzz

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

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


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


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

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


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

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


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

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


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


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

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

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

Там как раз Николас рассматривает факториал
1) Не увидел в книге по ссылке никаких дополнительных условий, кроме стандартного для tail call optimization требования, что рекурсивный вызов должен быть хвостовым (т. е. быть последней операцией в функции).

2) Рассуждения о преимуществах, которые даёт tail call optimization в JS, безусловно очень полезны и занимательны, но важно понимать, что в реальности они разбиваются о то, что в данный момент эта оптимизация поддерживается только в JSC (движок Safari). И, учитывая крайне принципиальную позицию разработчиков из Google, нет шансов, что это изменится в обозримом будущем.
важно понимать, что в реальности они разбиваются о то, что в данный момент эта оптимизация поддерживается только в JSC (движок Safari). И, учитывая крайне принципиальную позицию разработчиков из Google, нет шансов, что это изменится в обозримом будущем
Это правда. Изучил вопрос подробнее. Действительно, на данный момент поддержка есть только в Safari. Видимо, имплементация данной оптимизации не тривиальная. =(
Вы, видимо, изучили вопрос недостаточно подробно. :)
Дело в том, что в V8 (движок всех Chromium-based браузеров) tail call elimination давно реализована и… преднамеренно выключена его разработчиками. ¯\_(ツ)_/¯

Вот тут можно прочитать подробнее. Аргументация в целом очень похожа на таковую у Гвидо и у разработчиков Go (тут, увы, пруфов не будет), поэтому я упомянул об этом как о «принципиальной позиции разработчиков из Google».
И синтаксис от Mozilla и Microsoft интересный. ждем.jpg
Я бы не особо надеялся. (:
Истории уже три с лишним года, а воз и ныне там. В в официальном списке proposal'ов в стандарт ES нет ни одного касающегося tail call, даже на stage 0.
За такое рекурсивное нахождение чисел Фибоначчи лично я бы сразу развернул интервьюируемого на выход как злостного вредителя. И вообще статья жесть.
Если вместо этого вы используете рекурсию, это может понравиться интервьюеру и дать вам несколько дополнительных очков.


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)
}


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

Если ваш интервьюер всерьёз считает, что приведённый рекурсивный код лучше тупого цикла — бегите оттуда бегом! Этот код работает за экспоненциальное время, в то время, как цикл — за линейное. В приличных же местах интервьюер должен знать, как эта задача решается за логарифмическое время.
Ну, быстрое возведение в степень матриц — это всё-таки уже немного перебор для JS)
Анаграммы в реальном проекте не используем, поэтому предлагаем вот такую задачку (одну из 3)
«написать на javascript (ECMAscript например не старше 5) код, который будет мониторить результаты любых ajax запросов расположенных на той же странице и в случае ошибок отправлять данные на сервер логов/статистики»
В ответах мы смотрим на оформление кода, на попытки подключения сторонних либ и самое главное, наличие списка уточняющих вопросов :)
Мне вот интересно как вы fetch будете мониторить?
а ограничение версии ecma не больше 5-й не напрягает?
Меня напрягает что вы собственное условие задачи читать не умеете, где в ней сказано, что на странице, которую надо мониторить, нет fetch? В вашем условии задачи написано, используя es5 написать мониторинг всех ajax. Во-вторых как вам поможет, что кандидат знает или не знает как перекрыть метод send?
Во-вторых как вам поможет, что кандидат знает или не знает как перекрыть метод send?
до сих пор помогало :)
Тестовое задание вполне себе покрывает часть наших нужд по теме фронтенда. Главное чтобы человек думать умел, остальному — научим/научится
У вас написано «не старше», а не «не больше». 6й версии меньше лет, чем 5й => она моложе.
По поводу проверки на палиндром.

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


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

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

Тогда нужно отдельно двигать правый и левый указатель, (на пробелах предвигать). В целом в статье разбор типовых задач странный. Решения далеко не самые оптимальные и местами даже спорные.
UFO just landed and posted this here
Действительно, я про него как-то не знал (упс). Ценная штука в некоторых случаях.

Проблема в том, что не любой графемный кластер можно нормализовать:
// devanagari ssi 
[...'षि'].length;  // => 2
[...'षि'.normalize()].length;  // => 2

// ok hand with dark skin tone
[...'\u{270c}\u{1f3ff}'].length;  // => 2
[...'\u{270c}\u{1f3ff}'.normalize()].length;  // => 2
Проблему с графемными кластерами можно решить через метод normalize()
const reverse = str => [...str].reverse().join('');
const str = 'А \u{0415}\u{0308} А';
const strNormalized = str.normalize()
reverse(strNormalized );  // 'А Ё А'
reverse(strNormalized ) === strNormalized ; // true
итератор по строке обходит 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]

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

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

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

Мда, эксперты…
Код на проверку палиндрома не сработает, как писали выше, если будут пробелы и знаки препинания.
Fizzbuzz решён прямо в тупую, никакого понимания вообще.
Анаграммы… странноваты
Гласные вообще решаются в одну строку, два действия(если нет дополнительных условий).
Только Фибоначчи нормально.
Если эта дама людей собеседует, то ей стоит слегка поднять уровень.
Внесу и я свою лепту в fizzbuzz :)
function Fizzbuzz (num) {
  let response = '';
  if (!(num % 3)) response += 'fizz';
  if (!(num % 5)) response += 'buzz';
  return response || num;
}
Написал точно такое же. Очень странно что оптимальным названо то что в статье, как впрочем и решения других задач.

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 в консоль. Ваша функция этого не делает, и еще со строчной обычно называют конструкторы, это если придираться ;)
А чем s ? s : i лучше, чем s || i для i ∈ ℕ?
Ничем, просто я так написал, потому что мог.
В анаграмме у вас две ошибки в одной строке:
for(let char of str.replace(/[^\w]/g).toLowerCase()) { ...


Во-первых, у метода replace должно быть 2 аргумента, иначе он заменит найденный символ на undefined. Вероятно это опечатка, но надо же проверять примеры, перед тем как публиковать статью.
Во-вторых, данное решение работает только с латинскими символами. В JS, паттерны, вроде \w не рассчитаны на юникод. ИМХО, это самое больное место регулярных выражений в JS. Правильная реализация будет выглядеть как-то так:
const notALetterRegEx = /[^A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶ-ͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԣԱ-Ֆՙա-ևא-תװ-ײء-يٮ-ٯٱ-ۓەۥ-ۦۮ-ۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴ-ߵߺऄ-हऽॐक़-ॡॱ-ॲॻ-ॿঅ-ঌএ-ঐও-নপ-রলশ-হঽৎড়-ঢ়য়-ৡৰ-ৱਅ-ਊਏ-ਐਓ-ਨਪ-ਰਲ-ਲ਼ਵ-ਸ਼ਸ-ਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલ-ળવ-હઽૐૠ-ૡଅ-ଌଏ-ଐଓ-ନପ-ରଲ-ଳଵ-ହଽଡ଼-ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கங-சஜஞ-டண-தந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-ళవ-హఽౘ-ౙౠ-ౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠ-ೡഅ-ഌഎ-ഐഒ-നപ-ഹഽൠ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะา-ำเ-ๆກ-ຂຄງ-ຈຊຍດ-ທນ-ຟມ-ຣລວສ-ຫອ-ະາ-ຳຽເ-ໄໆໜ-ໝༀཀ-ཇཉ-ཬྈ-ྋက-ဪဿၐ-ၕၚ-ၝၡၥ-ၦၮ-ၰၵ-ႁႎႠ-Ⴥა-ჺჼᄀ-ᅙᅟ-ᆢᆨ-ᇹሀ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙶᚁ-ᚚᚠ-ᛪᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᤀ-ᤜᥐ-ᥭᥰ-ᥴᦀ-ᦩᧁ-ᧇᨀ-ᨖᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮ-ᮯᰀ-ᰣᱍ-ᱏᱚ-ᱽᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₔℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℼ-ℿⅅ-ⅉⅎↃ-ↄⰀ-Ⱞⰰ-ⱞⱠ-Ɐⱱ-ⱽⲀ-ⳤⴀ-ⴥⴰ-ⵥⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々-〆〱-〵〻-〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆷㇰ-ㇿ㐀-䶵一-鿃ꀀ-ꒌꔀ-ꘌꘐ-ꘟꘪ-ꘫꙀ-ꙟꙢ-ꙮꙿ-ꚗꜗ-ꜟꜢ-ꞈꞋ-ꞌꟻ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꤊ-ꤥꤰ-ꥆꨀ-ꨨꩀ-ꩂꩄ-ꩋ가-힣豈-鶴侮-頻並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּ-סּףּ-פּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼ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}, либо длиннющая регулярка, как правило сгенерированная каким-то сервисом. Например, данную простыню я сгенерировал здесь.
Была еще задача на комплементарность ДНК. Решение ниже
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 с гугла Карл! Ты же сеньора собеседуешь! Ну как же так?!

Нда… теперь эта компания в моем личном черном списке. Нельзя же так не уважать людей, которые к вам приходят.
Мне кажется, что анаграммы можно сделать намного проще:
 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 

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

На самом деле, там всё немного сложнее — начиная с ES6 спецификация весьма строго описывает требуемый порядок ключей как для Object.keys(), так и для for (... in ...).
Сначала идут целочисленные свойства (в порядке возрастания), потом строковые (в порядке создания), потом symbols (тоже в порядке создания).


Проблема в том, что если попадётся браузер, не реализующий полностью ES6 (а как минимум в одном я почти уверен), то всё пойдёт не по плану, и вырулить на fallback-решение на полифиллах не очень-то получится.

Мда. Всегда что-то можно оптимизировать. Даже фуз-буз задачку. Другие не стал читать — слишком много слов.


function getFizzBuzz(num) {
    var result = '';
    if ( num % 3 == 0 ) {
        result += 'fizz';
    }
    if ( num % 5 == 0 ) {
        result += 'buzz';
    }
    return result || num;
}
Или давайте ещё извратимся.
const getFizzBuzz = num => `${'fizz'.repeat(!(num % 3))}${ 'buzz'.repeat(!(num % 5))}` || num;

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

Понятие оптимальности зависит от показателей, которые мы хотим оптимизировать. Если мы хотим оптимизировать показатель «продемонстрировать интервьюеру знакомство с фичами ES6 и умение писать write-only код»… =)

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

Sign up to leave a comment.