Для получения полного доступа
зарегистрируйтесь.

jestonedev    99   20 723


Видимо, пользователь решил о себе ничего не говорить.
  • Зарегистрирован 4 года назад
Профиль завершён на 0 %
0 %

Вот такие вот задачки иногда попадаются на собеседовании на должность Frontend Web Developer: Написать парсер математических выражений методом рекурсивного спуска. Должны поддерживаться операции сложения, вычитания, умножения, деления, возведения в степень, а также скобки. Хоть задача и тривиальная, но мне стало интересно ваше мнение, как подобная задача коррелирует с должностью? Видимо оценить уровень владения языком?

P.S.: Известные проблемы данной реализации:

  1. Неочевидность сообщения об ошибке: пользователю может быть не всегда понятно, почему в одних случаях выводится Token unexpected, а в других Invalid formula
  2. Проблема двух знаков минус в начале выражения: "(--2)" - вернет NaN, а не "Invalid formula" Писалось на коленке, поэтому прошу понять и простить.

var TokenType = {
    Empty: { name: "Empty" },
    Number: { name: "Number", expectAfter: [ "Empty", "Operator" ] },
    Operator: { name: "Operator", expectAfter: [ "Number", "Expression" ] },
    Expression: { name: "Expression", expectAfter: [ "Empty", "Operator" ] }
};

var Operator = [
    { operator: "^", priority: 3, eval: function(a,b) { return Math.pow(a,b); } },
    { operator: "*", priority: 2, eval: function(a,b) { return a*b; } },
    { operator: "/", priority: 2, eval: function(a,b) { return a/b; } },
    { operator: "+", priority: 1, eval: function(a,b) { return a+b; } },
    { operator: "-", priority: 1, eval: function(a,b) { return a-b; } }
];

function Evaluator()
{
}

Evaluator.prototype = {
    eval: function(str) {
        var tokens = [];
        while(str !== "") {
            var token = this.extractNumber(str, tokens.length === 0);
            if (token === null) {
                token = this.extractOperator(str);
            }
            if (token === null) {
                token = this.extractExpression(str);
            }
            if (token === null) {
                throw Error('Token unexpected '+str);
            }
            this.assert(tokens[tokens.length - 1], token);
            tokens.push(token);
            str = str.substr(token.rawValue.length);
        }
        if (tokens.length === 0) return null;
        if (tokens[tokens.length - 1].type === TokenType.Operator) {
            throw new Error('Invalid formula');
        }
        return this.processTokens(tokens);
    },

    processTokens: function(tokens) {
        var processTokens = [];
        while (tokens.length > 1) {
            var maxPriority = tokens.filter(function(t) {
                return t.type === TokenType.Operator;
            }).map(function(t) {
                return t.operator.priority;
            }).reduce(function (acc, v) {
                return v > acc ? v : acc;
            }, Number.MIN_VALUE);
            for (var i = 0; i < tokens.length; i++) {
                if (tokens[i].type === TokenType.Operator && tokens[i].operator.priority === maxPriority) {
                    var leftOperand = processTokens.pop();
                    var operator = tokens[i].operator;
                    var rightOperand = tokens[i + 1];
                    var evalValue = operator.eval(Number(leftOperand.value), Number(rightOperand.value));
                    processTokens.push({"value": evalValue, "rawValue": evalValue, "type": TokenType.Number});
                    i++;
                } else {
                    processTokens.push(tokens[i])
                }
            }
            tokens = processTokens;
            processTokens = [];
        }
        if (tokens.length === 0) {
            throw new Error('Unexpected error');
        }
        return tokens[0].value;
    },

    assert: function(prevToken, currentToken)
    {
        if (prevToken == undefined && currentToken.type.expectAfter.indexOf("Empty") !== -1)
            return;
        if (prevToken !== undefined && currentToken.type.expectAfter.indexOf(prevToken.type.name) !== -1)
            return;
        throw Error("Invalid formula");
    },

    clear: function(str)
    {
        return str.replace(/\s+/g,"");
    },

    extractNumber: function(str, allowMinus)
    {
        var regExp = new RegExp("^"+(allowMinus ? "[\-]?" : "")+"[0-9]*(?:[.][0-9]+)?","g");
        var match = regExp.exec(str);
        if (match === null || match.length === 0 || match[0].length === 0) return null;
        return { "value": match[0], "rawValue": match[0], "type": TokenType.Number };
    },

    extractOperator: function(str)
    {
        for(var i = 0; i < Operator.length; i++)
        {
            var regExp = new RegExp("^"+ RegExp.escape(Operator[i].operator)+"","g");
            var match = regExp.exec(str);
            if (match !== null && match.length > 0)
                return { "value": match[0], "rawValue": match[0], "operator": Operator[i], "type": TokenType.Operator }
        }
        return null;
    },

    extractExpression: function(str)
    {
        var regExp = /^(\(.*\))/g;
        var match = regExp.exec(str);
        if (match === null || match.length < 2) return null;

        var brackets = ['('];
        var value = match[0];
        for (var i = 1; i < value.length; i++)
        {
            if (value[i] === '(') {
                brackets.push('(');
            }
            if (value[i] === ')') {
                brackets.pop();
            }
            if (brackets.length === 0) {
                break;
            }
        }
        value = value.substr(1, i-1);
        if (value === "") {
            throw new Error("Invalid formula");
        }
        var e = new Evaluator();
        return { "value": e.eval(value), "rawValue": "("+value+")", "type": TokenType.Expression };
    }
};

if (RegExp.escape === undefined) {
	RegExp.escape= function(s) {
		return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
	};
}

Разметка HTML:

<div class="parent">
  <div class="child"></div>
  <div class="child"></div>
  <div class="child"></div>
  <div class="child"></div>
  <div class="child"></div>
  <div class="child"></div>
  <div class="child"></div>
  <div class="child"></div>
  <div class="child"></div>
</div>
$columns: 4; // количество колонок

.parent {
  display: flex;
  flex-flow: row wrap;
}

.child {
  flex: 1 0 100%/$columns;
  background: #222;
  min-height: 15vh;
  box-sizing: border-box;
  border: 5px solid #fff;
}

.child:nth-last-child(2):nth-child(#{$columns}n) {
  min-width: 50%;
}

Код принадлежит не мне, а Justin Windle, но я просто не мог не поделиться достаточно простой, но эффектной стилизацией checkbox под кнопки OP-1 синтезатора. Эффект объемности достигается за счет очень тщательной настройки box-shadow и text-shadow, мягкость нажатия реализован через transition cubic-bezier, и в заключение легкий blur в 1px. В общем-то это все. Никакого волшебства.

Живой пример: http://codepen.io/soulwire/pen/bKens/?editors=110

HTML:

<div class="container">
  <div class="toggle">
    <input type="checkbox">
    <span class="button"></span>
    <span class="label">+</span>
  </div>
  <div class="toggle">
    <input type="checkbox" checked>
    <span class="button"></span>
    <span class="label">–</span>
  </div>
</div>
@import compass
@import url(http://fonts.googleapis.com/css?family=Lato:700)

html, body
  font-family: 'Lato', sans-serif
  background: url(http://s.cdpn.io/1715/dark_stripes.png)
  text-align: center
  height: 100%

.container
  text-align: center
  position: absolute
  margin-top: -80px
  width: 100%
  top: 50%

.toggle
  margin: 4px
  display: inline-block

.toggle
  $size: 140px
  box-shadow: inset 0 0 35px 5px rgba(0,0,0,0.25), inset 0 2px 1px 1px rgba(255,255,255,0.9), inset 0 -2px 1px 0 rgba(0,0,0,0.25)
  border-radius: 8px
  background: #ccd0d4
  position: relative
  height: $size
  width: $size

  &:before
    $radius: $size * 0.845
    $glow: $size * 0.125
    box-shadow: 0 0 $glow $glow / 2 #fff
    border-radius: $radius
    background: #fff
    position: absolute
    margin-left: ( $radius - $glow ) * -0.5
    margin-top: ( $radius - $glow ) * -0.5
    opacity: 0.2
    content: ''
    height: $radius - $glow
    width: $radius - $glow
    left: 50%
    top: 50%

  .button
    $radius: $size * 0.688
    -webkit-filter: blur(1px)
    -moz-filter: blur(1px)
    filter: blur(1px)
    transition: all 300ms cubic-bezier(0.230, 1.000, 0.320, 1.000)
    box-shadow: 0 15px 25px -4px rgba(0,0,0,0.5), inset 0 -3px 4px -1px rgba(0,0,0,0.2), 0 -10px 15px -1px rgba(255,255,255,0.6), inset 0 3px 4px -1px rgba(255,255,255,0.2), inset 0 0 5px 1px rgba(255,255,255,0.8), inset 0 20px 30px 0 rgba(255,255,255,0.2)
    border-radius: $radius
    position: absolute
    background: #ccd0d4
    margin-left: $radius * -0.5
    margin-top: $radius * -0.5
    display: block
    height: $radius
    width: $radius
    left: 50%
    top: 50%

  .label
    transition: color 300ms ease-out
    text-shadow: 1px 1px 3px #ccd0d4, 0 0 0 rgba(0,0,0,0.8), 1px 1px 4px #fff
    line-height: $size - 1
    text-align: center
    position: absolute
    font-weight: 700
    font-size: 42px
    display: block
    opacity: 0.9
    height: 100%
    width: 100%
    color: rgba(0,0,0,0.4)

  input
    opacity: 0
    background :red
    position: absolute
    cursor: pointer
    z-index: 1
    height: 100%
    width: 100%
    left: 0
    top: 0
    
    &:active
      ~ .button
        box-shadow: 0 15px 25px -4px rgba(0,0,0,0.4), inset 0 -8px 30px 1px rgba(255,255,255,0.9), 0 -10px 15px -1px rgba(255,255,255,0.6), inset 0 8px 25px 0 rgba(0,0,0,0.4), inset 0 0 10px 1px rgba(255,255,255,0.6)
   
      ~ .label
        font-size: 40px
        color: rgba(0,0,0,0.45)
        
    &:checked
      ~ .button
        box-shadow: 0 15px 25px -4px rgba(0,0,0,0.4), inset 0 -8px 25px -1px rgba(255,255,255,0.9), 0 -10px 15px -1px rgba(255,255,255,0.6), inset 0 8px 20px 0 rgba(0,0,0,0.2), inset 0 0 5px 1px rgba(255,255,255,0.6)

      ~ .label
        font-size: 40px
        color: rgba(0,0,0,0.4)

Для этого воспользуемся великолепным и простым компонентом Slick, работающим даже в IE8. Сразу скажу, что слайдер может работать с любыми элементами, это не обязательно должны быть изображения. Что он умеет: автопрокрутка, адаптивность, поддержка swip-прокрутки, поддержка rtl/ltr, accessibility, синхронизация между несколькими слайдерами, вариативная ширина слайдов, особая стилизация центрального элемента, ленивая загрузка изображений, динамическое добавление слайдов и др. С подробным описанием и массой примеров можно ознакомиться по ссылке http://kenwheeler.github.io/slick/

<!-- слайды -->
<div class="main-page__slider">
  <img data-lazy="IMG_7200.jpg" title="IMG_7200.jpg">
  <img data-lazy="IMG_7207.jpg" title="IMG_7207.jpg">
  <img data-lazy="IMG_7212.jpg" title="IMG_7212.jpg">
  <img data-lazy="IMG_7221.jpg" title="IMG_7221.jpg">
  <img data-lazy="IMG_7224.jpg" title="IMG_7224.jpg">
  <img data-lazy="IMG_7204.jpg" title="IMG_7204.jpg">
</div>

 // Код инициализации
  $('.main-page__slider').slick({
    lazyLoad: 'ondemand',
    slidesToShow: 5,
    slidesToScroll: 1,
    variableWidth: true,	
    dots: true,
    infinite: true,
    speed: 500,
    centerMode: true
  });

/* Ну и немнокжо стилизации по вкусу */
.main-page__slider .slick-slide {
	padding: 5px;
	border: 1px solid #eee;
	box-sizing: border-box;
    margin: 5px 10px;
    outline: none;
    box-shadow: #ccc 2px 2px 4px;
}

.main-page__slider {
    margin: 0 auto; 
	width: calc(100% - 40px); /* 40px - ширина для кнопок навигации */
}

.main-page__slider .slick-arrow:before {
	color: #4B6A93;
}

Стилизация placeholder на примере анимации "убегания" вправо при получении фокуса

<input class="auth--login" placeholder="Enter your login" >
/* С использованием autoprefixer */
.auth--login::placeholder {
    text-indent: 0px;   
    transition: text-indent 0.3s ease;
}

.auth--login:focus::placeholder {
    text-indent: 500px;
}


/* Без autoprefixer */
.auth--login::-webkit-input-placeholder {
    text-indent: 0px;   
    -webkit-transition: text-indent 0.3s ease;   
            transition: text-indent 0.3s ease;
}

.auth--login::-moz-placeholder {
    text-indent: 0px;   
    transition: text-indent 0.3s ease;
}

.auth--login:-ms-input-placeholder {
    text-indent: 0px;   
    transition: text-indent 0.3s ease;
}

.auth--login::placeholder {
    text-indent: 0px;   
    -webkit-transition: text-indent 0.3s ease;   
            transition: text-indent 0.3s ease;
}

.auth--login:focus::-webkit-input-placeholder {
    text-indent: 500px;
}

.auth--login:focus::-moz-placeholder {
    text-indent: 500px;
}

.auth--login:focus:-ms-input-placeholder {
    text-indent: 500px;
}

.auth--login:focus::placeholder {
    text-indent: 500px;
}

Даже если вы не работали с MS SQL Server, для вас не будет неожиданностью тот факт, что план выполнения хранимой процедуры (и не только ее) вычисляется лишь однажды, после чего он кешируется и используется со всеми последующими входными параметрами. Обычно это поведение - это то, чего мы ожидаем. Но иногда такое поведение приводит к тому, что план запроса, являющийся оптимальным для изначальных параметров, может оказаться не оптимальным для другого набора параметров. Как решить подобную проблему?

-- Исходная хранимая процедура
ALTER PROCEDURE [dbo].[usp_contrysearch]
@country varchar(80)
AS
SELECT p.lastname, p.dob, p.sex, c.country
FROM people p join country c
ON p.personid = c.personid
WHERE c.country = @country
GO

-- ------------------
-- 1 вариант: Использование DBCC FREEPROCCACHE
-- Выполнение этой инструкции каждый раз будет очищать кеш планов запросов.
-- Эта инструкция не очищает план нативно-скомпилированных хранимых процедур.
-- Подробнее о синтаксисе можно почитать по ссылке:
-- https://msdn.microsoft.com/en-us/library/ms174283.aspx
DBCC FREEPROCCACHE
GO
EXEC usp_countrysearch 'UK'

-- ------------------
-- 2 вариант: Использование опции WITH RECOMPILE команды EXEC.
-- При каждом запуске хранимой процедуры с данной опцией будет производиться перекомпиляция:
EXEC usp_contrysearch 'UK' WITH RECOMPILE
GO

-- ------------------
-- 3 вариант: Использование опции RECOMPILE при создании хранимой процедуры,
-- после чего все запросы на исполнение будут приводить к ее принудительной перекомпиляции 
-- (даже без указания EXEC WITH RECOMPILE):
ALTER PROCEDURE [dbo].[usp_contrysearch]
@country varchar(80)
AS
SELECT p.lastname, p.dob, p.sex, c.country
FROM people p join country c
ON p.personid = c.personid
WHERE c.country = @country
OPTION (RECOMPILE)
GO

-- ------------------
-- 4 вариант: Строго говоря, этот вариант не относится к принудительной перекомпиляции. 
-- Это вариант предварительной оптимизации, но я о нем все равно упомяну.
-- Если мы имеем известный набор планов для хранимой процедуры
-- и набор значений входных параметров для каждого из них, то мы можем выбрать наиболее оптимальный 
-- и скомпилировать хранимую процедуру так, чтобы всегда использовался этот план:
ALTER PROCEDURE [dbo].[usp_contrysearch]
@country varchar(80)
AS
SELECT p.lastname, p.dob, p.sex, c.country
FROM people p join country c
ON p.personid = c.personid
WHERE c.country = @country
OPTION (OPTIMIZE FOR (@country = 'UK')) 
-- теперь для любых входных параметров будет использоаться план как для @country = 'UK'
GO

Данный стеш не является реализацией какого-то полезного функционала, а служит лишь для ознакомления с нововведениями синтаксиса шестой версии языка C#

// ******************
// 1. Интерполяция строк
const string name = "Vasily";
var text = $"My name is {name}.";   // My name is Vasily

// ******************
// 2. Индексный инициализатор
var list = new Dictionary<string, int>
{
    ["Three"] = 3,
    ["Four"] = 4
};

// ******************
// 3. Новый способ инициализации auto-implemented свойств
public int MyProp { get; } = 3;

// ******************
// 4. Возможность объявления методов класса в стрелочной нотации лямбда
class MyClass {
	public static int Mul3(int x) => 3*x; // MyClass.Mul3(4) --> 12
}

// ******************
// 5. Оператор .? - это самое ожидаемое мной нововведение в синтаксис языка
// и я с нетерпением жду, когда другие языки (в частности JavaScript и PHP) реализуют эту идею.
// Данный оператор позволяет решить проблему проверки на null в многовложенных цепочках обращений:
var list = new List<Person>() { new Person() }; 
var chiefSurname = list[0]?.Chief?.Surname;  // Если Chief == null, в chiefSurname будет записано null
PropertyChanged?.Invoke(e)  // Вызов обработчика события при условии, что он сущестует

// ******************
// 6. nameof() - это не метод, это инструкция, аналогичная инструкции typeof(), 
// возвращает имя объекта/свойства в виде строки
var person = new Person();
Console.WriteLine(nameof(person.Surname));  // --> "Surname"

// ******************
// 7. Условные catch-блоки
try
{
    throw new ArgumentException("Blablabla","myparam");
}
// В этот catch-блок будет выполнен вход только при соответствии условия
catch (ArgumentException e) when (e.ParamName == "myparam")
{
    Console.WriteLine($"Argument {e.ParamName} is missing");
}
// Все остальные ошибки типа ArgumentException будут обработаны этим catch-блоком
catch (ArgumentException e)
{
    Console.WriteLine("Any unknown argument");
}

// ******************
// 8. Статический импорт методов. Никогда не используйте эту возможность! 
// Привожу пример лишь для полноты изложения
using static System.Console;
// теперь статические методы класса Console можно использовать без указания имени класса
ReadLine();

// ******************
// 9. Появилась возможность использовать await в блоках catch/finally
async static void Test()
{
    try
    {
        throw new Exception();
    }
    catch (Exception e)
    {
        await LogResultAsync("Site download failed", e);
    }
}

async static Task LogResultAsync(string msg, Exception e)
{
    await Task.Delay(1000).ContinueWith((r) => { Console.WriteLine(msg); });
}
Сниппет,  JavaScript

Promise.any

Любой, кто работал с классом Promise, рано или поздно осозновал, что в нем не хватает такого важного метода, как Promise.any, который бы резолвился, если бы хотя бы один из переданных ему в качестве аргумента промисов являлся успешным. В противном случае, если ни один из промисов не был успешно выполнен, Promise.any должен вызывать reject.

Примеры использования:

> Promise.any([Promise.resolve(4), Promise.resolve(3)]).
   then(function(val) { console.log(val) })
< 4
> Promise.any([Promise.reject(4), Promise.resolve(3)]).
   then(function(val) { console.log(val) })
< 3
> Promise.any([Promise.reject(4), 
   new Promise(function(resolve, reject) { 
      setTimeout(function() { resolve(7); },1000); }),      
   Promise.resolve(3)]).then(function(val) { console.log(val) })
< 3
if (!Promise.any)
    Promise.any = function(promises) {
        return new Promise(function(resolve, reject) {
            var failedPromises = 0;
            promises.forEach( function(promise) { 
                promise.then(function(value) { resolve(value); }).
                        catch(function() { 
                            failedPromises++; 
                            if (failedPromises === promises.length) 
                                reject('All promises rejected') });
            });
        })
    }

-- ненормальное программирование --

<body onload='setInterval(onkeydown=function(d){for(d=d||5,(e=d.keyCode)&&(d=e%2?e%3?-1:1:5),t="",d=c-d,e=a|b<<d,0>d|a&b<<d&&(a=e=parseInt((a|b<<c).toString(d=32).replace(/v/,""),d),b=new Date%2?1:3),c=d,i=1;31>i;)t+=".#"[1&(1<<30|e).toString(2)[i]]+(i++%5?"":"\n");O.innerHTML=t},888,a=0,b=3,c=32)'><pre id=O>

Использование string hello = "Hello"; int i = 42; dynamic il = new IL<int>(/аргументы метода/new object[] { hello, i }); //тело метода il.Emit(OpCodes.Ldarg_0); il.EmitCall(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), null); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ret); //выполняем и получаем результат int j = il.Exec(); Console.WriteLine(j); Console.ReadLine();

internal class IL<T>: DynamicObject 
{
	private object[] args;
	private DynamicMethod method;
	private ILGenerator generator;
	
	public IL(object[] args) 
	{
		this.args = args;
		Type[] _args = new Type[args.Length];
		for (int i = 0; i < args.Length; i++)
			_args[i] = args[i].GetType();
		this.method = new DynamicMethod("Method", typeof(T), _args, typeof(string).Module);
		this.generator = this.method.GetILGenerator(); 
	}
	
	public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) 
	{
		try 
		{
			return null == (result = typeof(ILGenerator).InvokeMember(binder.Name, BindingFlags.InvokeMethod, null, this.generator, args));
		} catch 
		{
			return null != (result = null); 
		}
	}
	
	public T Exec() 
	{
		return (T)this.method.Invoke(this, this.args); 
	}
}

Использование (EcmaScript5):

pipe(function(x) { return x + x }).pipe(function(x) { return x*x; }).pipe(function(x) { return x - 3; }).map([1,2,3])

Использование (EcmaScript6):

pipe(x => x + x).pipe(x => x*x).pipe(x => x - 3).map([1,2,3])

Сама функция:

function pipe(func) {
    var callstack = [func];
    return {
        pipe: function(func) {
            if (typeof(func) === "function")
                callstack.push(func);
            return this;
        },
        map: function(data) {
            callstack.forEach(
                function(func) {
                    data = data.map(function(val) {
                        return func(val); });
                });
            return data;
        }
    }
}

Результат — [ 1, 13, 33 ]