Comments 53
const convert = str => str.toLowerCase().match(/\w/g).sort().join('');
const anagram = (strA, strB) => convert(strA) === convert(strB);
Мало того из строки вырежутся, к примеру, русские буквы, и строки 'abc' и 'abcабц' будут равны
const convert = str => str.toLowerCase().match(/\w/g).sort().join('');
const anagram = (strA, strB) => strA !== '' && strB !== '' && convert(strA) === convert(strB);
Так лучше?
Следует также учесть, что анаграмма это «Слово или словосочетание, образованное путём перестановки букв, составляющих другое слово (или словосочетание).». То есть пустая строка не может быть анаграммой.
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')
}
}
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 — можно ли сократить количество условий.
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)
}
Аааа, ужас-ужас-ужас!!!
Если ваш интервьюер всерьёз считает, что приведённый рекурсивный код лучше тупого цикла — бегите оттуда бегом! Этот код работает за экспоненциальное время, в то время, как цикл — за линейное. В приличных же местах интервьюер должен знать, как эта задача решается за логарифмическое время.
«написать на javascript (ECMAscript например не старше 5) код, который будет мониторить результаты любых ajax запросов расположенных на той же странице и в случае ошибок отправлять данные на сервер логов/статистики»
В ответах мы смотрим на оформление кода, на попытки подключения сторонних либ и самое главное, наличие списка уточняющих вопросов :)
Во-первых, странно, что вообще не упомянуто самое эффективное решение, оно не больно-то сложнее приведённого:
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
Универсального решения, работающего во всех случаях, я не знаю, увы.
Задача может усложняться разными регистрами и пробелами, вроде «А роза упала на лапу азора».
Тогда нужно отдельно двигать правый и левый указатель, (на пробелах предвигать). В целом в статье разбор типовых задач странный. Решения далеко не самые оптимальные и местами даже спорные.
Проблема в том, что не любой графемный кластер можно нормализовать:
// 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
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]
palindrome('race car') // false
Неплохо бы добавить
str = str.toLowerCase().replace(/ /g,'');
palindrome('А роза упала на лапу Азора') // true
// есть еще вариант от пенсионного фонда
palindrome('Оголи жопу пожилого'); // true
Код на проверку палиндрома не сработает, как писали выше, если будут пробелы и знаки препинания.
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 в консоль. Ваша функция этого не делает, и еще со строчной обычно называют конструкторы, это если придираться ;)
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 => `${num % 3 ? '' : 'fizz'}${num % 5 ? '' : 'buzz'}` || num;
const getFizzBuzz = num => `${'fizz'.repeat(!(num % 3))}${ 'buzz'.repeat(!(num % 5))}` || num;
Главный посыл не в количестве "крутых" фишек, а в оптимальности алгоритма. "Извращения" (как Вы сказали) не добавляют новизны, но читабельность кода ухудшают.
5 типовых задач на собеседованиях по JavaScript: разбор и решения