Как мне создать функцию contains, используя сокращение вместо цикла for в JavaScript?

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

reduce([1,2,3], function(a, b) { return a + b; }, 0); //6

Использование его с чем-либо, кроме чисел, меня действительно смущает. Итак, как мне создать функцию contains, используя сокращение вместо цикла for? Комментарии приветствуются. Спасибо вам всем.

function contains(collection, target) {
  for(var i=0; i < collection.length; i++){
    if(collection[i] === target){
      return true;
    }
  }
  return false;
}
contains([1, 2, 3, 4, 5], 4);
//true

person hackermann    schedule 20.10.2015    source источник
comment
Я не думаю, что вы можете сделать это с помощью Array.prototype.reduce, но вы можете написать свою собственную функцию reduce, которая понимает значения reduced (которые завершают итерацию).   -  person MinusFour    schedule 20.10.2015
comment
Эм, Array.indexOf ?   -  person adeneo    schedule 20.10.2015
comment
Почему вы используете сокращение для содержимого?   -  person epascarello    schedule 20.10.2015
comment
на самом деле reduce() имеет некоторый смысл, потому что вы хотите преобразовать многие в один.   -  person dandavis    schedule 20.10.2015
comment
пример кажется тривиальным, поскольку indexOf() самый простой и уже существует   -  person charlietfl    schedule 20.10.2015
comment
Я знаю, что вы можете использовать Array.indexOf, но мне нужно собрать содержимое с помощью сокращения. Этот вопрос был на собеседовании, так что вот почему.   -  person hackermann    schedule 20.10.2015
comment
Через несколько лет вы сможете использовать Array.includes, обратите внимание, что на MDN есть полифилл.   -  person adeneo    schedule 20.10.2015
comment
я боюсь, что никто не будет использовать include, потому что он не содержит имени, как строковая версия. почему строковая версия всегда выходит первой (например, indexOf)? в любом случае, спасибо moo tools за дурацкое имя...   -  person dandavis    schedule 20.10.2015
comment
Произведите впечатление на группу интервью, сообщив им, что использование some было бы более эффективным, потому что он выходит из цикла, как только найденный элемент найден.   -  person Jeremy Larter    schedule 21.10.2015


Ответы (3)


Это то, что вам нужно:

function contains(collection, target) {
    return collection.reduce( function(acc, elem) {
       return acc || elem == target;
    }, false)
};

Как говорит Аданео, для этого конкретного вопроса, вероятно, есть более простой способ, но вы отметили это «функциональным программированием», поэтому я думаю, вы хотите улучшить этот способ решения проблем, который я полностью поддерживаю.

person Simon H    schedule 20.10.2015
comment
вы можете нацелить bind() на это, чтобы убить обертку/закрытие, не забудьте добавить use strict в этом случае. - person dandavis; 20.10.2015
comment
Более простым способом был бы collection.some(function(elem) { return elem == target; }), в котором мы даже могли бы реализовать some с помощью reduce. - person Bergi; 20.10.2015
comment
@FreddieCabrera отлично, не могли бы вы тогда отметить вопрос как выполненный? - person Simon H; 20.10.2015
comment
@SimonH, как это работает? Не могли бы вы провести меня через это? - person hackermann; 21.10.2015
comment
Конечно. Reduce берет accumulator с начальным значением — в данном случае false — и комбинирует его с каждым element в списке. Функция, объединяющая эти два значения, возвращает новое значение acc для следующей итерации. В конце конечное значение аккумулятора возвращается в contains - person Simon H; 21.10.2015
comment
Я думаю, вы видите, что логическая логика начинается как ложная и остается такой до elem == target, после чего acc становится истинной и остается такой. - person Simon H; 21.10.2015

Вот решение ES2015:

    const contains = (x, xs) => xs.some(y => x === y);
    let collection = [1,2,3,4,5];

    console.log(contains(4, collection)); // true;

Большое преимущество Array.prototype.some по сравнению с Array.prototype.reduce заключается в том, что первый завершает итерацию, как только условие становится true, тогда как последний всегда проходит весь массив. Это означает, что contains(4, xs) останавливает итерацию с четвертым элементом xs.

person Community    schedule 13.07.2016
comment
Ну, ОП запрашивает конкретно решение reduce ... кроме этого, вы, конечно, правы. - person le_m; 13.07.2016
comment
@le_m Ты прав. Но иногда нам нужно реагировать не так, как ожидает ОП, даже если это означает, что мы не получаем репутацию: D - person ; 13.07.2016
comment
@LUH3417 Согласен на 100% - person Mulan; 15.07.2016

Как сделать X с помощью Y?

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

Вы можете использовать Array.prototype.reduce, если хотите, но, учитывая то, как он работает, он всегда будет перебирать все содержимое массива, даже если совпадение найдено в первом элементе. Это означает, что вы не должны использовать Array.prototype.reduce для своей функции.

Однако вы можете использовать решение сокращения, если вы пишете reduce, который поддерживает ранний выход. Ниже показан reducek, который передает продолжение обратному вызову. Применение продолжения продолжит сокращение, но возврат значения приведет к досрочному выходу. Похоже на то, что доктор прописал...

Этот ответ должен сопровождать ответ LUH3417, чтобы показать вам, что, прежде чем вы узнаете о Array.prototype.some, вам не следует сидеть и ждать, пока ECMAScript реализует нужное вам поведение. Этот ответ демонстрирует, что вы можете использовать процедуру сокращения и по-прежнему иметь поведение раннего выхода.

const reducek = f=> y=> ([x,...xs])=>
  x === undefined ? y : f (y) (x) (y=> reducek (f) (y) (xs))

const contains = x=>
  reducek (b=> y=> k=> y === x ? true : k(b)) (false)

console.log(contains (4) ([1,2,3,4,5])) // true
console.log(contains (4) ([1,2,3,5]))   // false
console.log(contains (4) ([]))          // false

Глядя на reducek здесь и пример функции contains, должно быть несколько очевидно, что contains можно обобщить, что и есть Array.prototype.some.

Опять же, программирование — это не волшебство, поэтому я покажу вам, как вы могли бы это сделать, если бы Array.prototype.some еще не существовало.

const reducek = f=> y=> ([x,...xs])=>
  x === undefined ? y : f (y) (x) (y=> reducek (f) (y) (xs))

const some = f=>
  reducek (b=> x=> k=> f(x) ? true : k(b)) (false)

const contains = x=> some (y=> y === x)

console.log(contains (4) ([1,2,3,4,5])) // true
console.log(contains (4) ([1,2,3,5]))   // false
console.log(contains (4) ([]))          // false

person Mulan    schedule 15.07.2016
comment
Я не думаю, что вы должны передавать y в f, и продолжение не должно принимать параметр - person Bergi; 15.07.2016
comment
@Bergi, я заинтригован ^_^ Можете ли вы связать вставку кода, которая работает с рекомендациями, которые вы дали? - person Mulan; 15.07.2016
comment
Только const reducek = f => y => xs => xs.length == 0 ? y : f(xs[0])(() => reducek(f)(y)(xs.slice(1)) и const contains = x => reducek (y => k => y === x || k())(false) - person Bergi; 15.07.2016
comment
@Bergi Хорошо, теперь reducek больше нельзя применять к обычным лямбда-выражениям, а только к лямбда-выражениям, которые могут справляться с ленью. Но мы можем получить более конкретные функции более высокого порядка, такие как map, some или contains, которые наследуют поведение раннего выхода reducek. Или я что-то пропустил? - person ; 15.07.2016
comment
@LUH3417: я понятия не имею, что вы подразумеваете под обычными лямбда-выражениями, но да, вы можете получить практически любую функцию списка из reducek - person Bergi; 15.07.2016
comment
@Bergi Берги Ой, извините, вот пример: reducek(x => y => x + y)([])[1,2,3]) больше не работает. Требуется x => y => x + y(). - person ; 15.07.2016
comment
@LUH3417: Да, работа с ленью явно необходима для раннего выхода, но обратите внимание, что ваш пример также не работал с оригинальным reducek Наомика. - person Bergi; 15.07.2016
comment
@ Берги заметил. Дурак я. - person ; 15.07.2016
comment
Я намеревался использовать reducek специально, когда вы хотели контролировать лень. Пример суммы LUH3417 может использовать reducek (y=> x=> k=> k (x + y)) (0) ([1,2,3]) //=> 6, но в этом сценарии не имеет смысла использовать reducek. Вместо этого неленивое сокращение, reduce, было бы более подходящим. const reduce = f=> reducek (y=> x=> k=> k (f (y) (x))), где reduce (y=> x=> x + y) (0) ([1,2,3]) //=> 6 будет работать без возможности досрочного выхода. - person Mulan; 15.07.2016
comment
Я хотел просто защитить вашу версию reducek, но мне это не удалось :( - person ; 15.07.2016
comment
@LUH3417 все в порядке. Я все еще не понимаю, как заставить что-то вроде stopAddingAfter20 работать с упрощенным интерфейсом Берги. С моим кодом я мог бы сделать const stopAddingAfter20 = reducek (acc=> x=> k=> acc > 20 ? acc : k(acc + x)) (0), а затем stopAddingAfter20 ([1,2,3,4,5,6,7,8,9]) //=> 21, но я не могу понять, как получить аналогично функционирующий код, не вводя неуклюжие свободные переменные. Лямбда-редуктор традиционно получает два параметра, acc и x, поэтому замена одного из них интерфейсом продолжения, похоже, лишает его полезности. - person Mulan; 15.07.2016
comment
@Bergi, продолжая, мне нравится простота вашего reducek, но я не вижу, как его можно применить в других общих случаях :( - person Mulan; 15.07.2016
comment
Кажется, сложно реализовать takeподобные функции с этой версией reducek (также известной как foldr). Возможно, это работает с левоассоциативным fold. Я попытаюсь понять это. - person ; 15.07.2016
comment
@ LUH3417 это конкретно reducek, складывающееся влево (вероятно, лучше назвать foldlk. Сокращение вправо нужно писать отдельно. Вот foldr: const foldr = f=> y=> ([x,...xs])=> x === undefined ? y : f (foldr (f) (y) (xs)) (x) и пример использования foldr (y=> x=> (${y} + ${x})) (0) ([1,2,3]) //=> '(((0 + 3) + 2) + 1)' , Я не сразу понял, как преобразовать это в foldrk (или возможно ли это), но я вернусь к этому позже сегодня ^_^ - person Mulan; 15.07.2016
comment
@ LUH3417 Я неправильно прочитал ваш последний комментарий. reducek определенно не foldr. Реализация take с reducek тривиальна. const take = n=> reducek (xs=> x=> k=> xs.length === n ? xs : k([...xs, x])) ([]) и take (3) ([1,2,3,4,5]) //=> [1,2,3] - person Mulan; 15.07.2016
comment
Хм, либо ваш foldr странный, либо я запутался (последнее, вероятно). Когда я применяю это foldr к concat = ys => xs => xs.concat(ys), это не работает. Вы только что перевернули аргументы, содержащие рекурсивный вызов и x в своей реализации. Чтобы изменить ассоциативность, вам нужно изменить порядок foldx и f в моем представлении: foldl = f => y => ([x, ...xs]) => xs.length ? foldl(f)(f(y)(x))(xs) : f(y)(x) и foldr = f => y => ([x, ...xs]) => xs.length ? f(x)(foldr(f)(y)(xs)) : f(x)(y). Опять же, это тоже может быть неправильно. Я проверю это еще раз и, возможно, предоставлю это как вопрос о SO. - person ; 15.07.2016
comment
Таким образом, эта часть вашей исходной reducek функции f(y)(x)(y => reducek... по сравнению с f(x)(() => reducek... версии Берги имеет значение и требует дальнейшего изучения. - person ; 15.07.2016
comment
Вы перевернули аргументы, содержащие рекурсивный вызов, и x... нет, все не так просто. Если вы с чем-то сравниваете foldr, это должен быть эквивалент foldl, который не использует базовую реализацию foldlk. Разница очень заметна в этой сути. - person Mulan; 15.07.2016
comment
@ LUH3417 Версия Берги имеет значение ... да, я понимаю разницу, но я все еще жду, когда он ответит на мой другой вопрос об этом. Я не уверен, как реализовать нетривиальные процедуры (например, stopAddingAfter20, о которых я упоминал выше), используя его код. - person Mulan; 16.07.2016
comment
Хорошо, мы согласны с foldl/foldr. Я думаю, что версия Берги вашего reducek - это просто ленивый foldr, а foldr не поддерживает stopAddingAfter20 чисто функциональным способом, потому что он правоассоциативен и, таким образом, начинает фактическое вычисление (f) справа налево: ...(6+(7+(8+9))). - person ; 16.07.2016
comment
@ LUH3417, да, у меня было время рассмотреть это поближе. Берги reducek на самом деле представляет собой редукцию, складывающуюся вправо, поэтому она ведет себя по-другому. Я почти уверен, что невозможно получить поведение раннего выхода из правой складки, потому что вычисление не может завершиться до тех пор, пока не будет оценен крайний правый элемент списка. Очевидным обманом было бы перевернуть входной список, а затем свернуть его влево (но, конечно, не будет работать с бесконечными списками/генераторами) - person Mulan; 16.07.2016
comment
@naomik: А, я вижу, твой reducek тоже позволял складывать влево. Мой допускает лень только во втором аргументе редуктора. - person Bergi; 16.07.2016
comment
@naomik Вы можете упростить свои some: const some = pred => reducek(() => x => k => pred(x) || k(false))();. Эта реализация также показывает избыточность аккумулятора в связи с some/every (см. мои комментарии ниже). - person ; 16.07.2016
comment
@naomik Почему some/every может быть получено из reducek @Bergi, но не из вашего stopAddingAfter20? Проблема в том, что аккумулятор reducek так сказать перегружен. Пока стек строится, это преобразователь. Когда стек раскручен, это произвольное значение. Логически аккумулятор не может быть использован для раннего выхода. Таким образом, мы можем получить только функции списка из reducek, которые не полагаются на аккумулятор. Но some/every очевидно сравнивают два аргумента и, таким образом, ожидают бинарный редуктор? ... - person ; 16.07.2016
comment
@naomik ... Однако первый аргумент обратного вызова some/every предоставляется как свободная переменная вне рекурсивной итерации. Ваш reducek вводит продолжение в качестве еще одного аргумента, который освобождает аккумулятор от того, чтобы быть преобразователем. Теперь мы можем вывести функции списка, которые также полагаются на аккумулятор. Ваш reducek больше похож на функцию в стиле передачи продолжения, чем на функцию с ленивой оценкой. - person ; 16.07.2016
comment
@ LUH3417 верно, что some можно так написать. some и every являются своего рода исключительными случаями, потому что на каждой итерации мы можем отбросить любой предыдущий ответ, поэтому аккумулятор не нужен. Это возможно, потому что мы можем сделать логический вывод: если текущая итерация выполняется, у нас все еще нет ответа с ранним выходом из предыдущей итерации, и это единственное, что в любом случае может нам сообщить аккумулятор... - person Mulan; 16.07.2016
comment
@ LUH3417 ... some и every можно сложить влево или вправо, и ответ всегда будет одним и тем же. Это не всегда так. Мое намерение состояло в том, чтобы предоставить reducek более общего назначения, которое подходило бы для любой функции, требующей раннего выхода. - person Mulan; 16.07.2016