Чому в JavaScript немає Array.prototype.flatMap?


82

flatMapє неймовірно корисним для колекцій, але javascript не надає такого, маючи Array.prototype.map. Чому?

Чи є спосіб емуляції flatMapв javascript як простим, так і ефективним способом без визначення flatMapвручну?


10
- Чому? Тому що ще ніхто не зробив пропозицію? Ось як запропонувати нову функцію . "Чи є спосіб імітувати flatMap ... без визначення flatMap вручну?" А? Я не розумію. Ви маєте на увазі, як arr.reduce((arr, v) => (arr.push(...v), arr), [])?
Фелікс Клінг

(^ це лише сплющена частина, тому, мабуть, так і повинно бути arr.map(...).reduce(...)).
Фелікс Клінг

Ви можете просто згладити масив після .mapйого пінгу.
kennytm

1
Хм, ви хочете його "наслідувати", але не "визначати". Що це може означати?

8
Незабаром це стане частиною JS. tc39.github.io/proposal-flatMap
Vijaiendran

Відповіді:


99

Оновлення: Array.prototype.flatMap на шляху до рідного ECMAScript. На даний момент це на етапі 4 .

Він широко підтримується в багатьох середовищах. Перевірте, чи працює це у вашому браузері, використовуючи цей фрагмент нижче -

const data =
  [ 1, 2, 3, 4 ]
  
console.log(data.flatMap(x => Array(x).fill(x)))
// [ 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 ]


Чому в JavaScript немає Array.prototype.flatMap?

Оскільки програмування не є магією, і кожна мова не має властивостей / примітивів, якими володіє кожна інша мова

Важливим є

JavaScript дає вам можливість самостійно визначити його

const concat = (x,y) =>
  x.concat(y)

const flatMap = (f,xs) =>
  xs.map(f).reduce(concat, [])

const xs = [1,2,3]

console.log(flatMap(x => [x-1, x, x+1], xs))

Або перезапис, який згортає дві петлі в одну

const flatMap = (f,xs) =>
  xs.reduce((acc,x) =>
    acc.concat(f(x)), [])

const xs = [1,2,3]

console.log(flatMap(x => [x-1, x, x+1], xs))

Якщо ви хочете, щоб це було на Array.prototype, вас ніщо не заважає

const concat = (x, y) => x.concat(y)

const flatMap = (f, xs) => xs.map(f).reduce(concat, [])

if (Array.prototype.flatMap === undefined) {
  Array.prototype.flatMap = function(f) {
    return flatMap(f, this)
  }
}

const xs = [1, 2, 3]

console.log(xs.flatMap(x => [x-1, x, x+1]))
.as-console-wrapper { top: 0; max-height: 100% !important; }


1
Вважається кращим простору імен будь-які доповнення до прототипів (наприклад Array.prototype._mylib_flatMap), або навіть краще використовувати символи ES6, а не просто визначати Array.prototype.flatMap.
Fengyang Wang

11
@FengyangWang "вважається кращим" є дуже суб'єктивним. Якщо це ваш власний додаток, і у вас є причина для розширення своїх прототипів, у ньому немає нічого поганого. Якщо це бібліотека / модуль / фреймворк, який ви поширюєте для використання іншими, то я б погодився з вами. Однак коментарі не є місцем для обговорення цього питання - питання заслуговує більше пояснень та деталей, ніж слід наводити в коментарях.
Дякую,

По-перше, це негарно: flatMap, визначена як функція, зробить код безладним - уявіть собі функціональний код із mapвизначеною як глобальна функція! По-друге, це просто погана практика
Сергій Алаєв

3
@SergeyAlaev re: "уявіть карту як глобальну функцію" ха-ха, я роблю саме це для багатьох своїх функцій. Я їх теж каррі, використовуючи послідовності функцій зі стрілками. "Некрасивий" суб'єктивний. І ви не наводили жодних аргументів на користь свого коментаря щодо "поганої практики". Ваші зауваження вказують мені, що у вас мало досвіду функціонального програмування. Візьмемо таку мову, як Хаскелл, де прелюдія включає купу "глобалів" ... ніхто не каже, що це "потворно" чи "погана практика". Ви просто не знайомі з цим.
Дякую,

5
@SergeyAlaev або розглянути Ракетка , що є append, map, filter, foldl, foldrсеред незліченних інших в просторі імен за замовчуванням. Це не робить це поганою практикою, тому що ви чули, як хтось каже "глобали погані" або "розширення корінних жителів це погано" - рекет є однією з найкращих мов. Ви просто не знаєте, як / коли це зробити належним чином.
Дякую,

48

flatMapбув затверджений TC39 як частина ES2019 (ES10). Ви можете використовувати його так:

[1, 3].flatMap(x => [x, x + 1]) // > [1, 2, 3, 4]

Ось моя власна реалізація методу:

const flatMap = (f, arr) => arr.reduce((x, y) => [...x, ...f(y)], [])

MDN Стаття на flatMap


Зокрема, flapMap офіційно знаходиться в Node 11.0.0 згідно з MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Carl Walsh

3
@CarlWalsh Вибачте, я не можу протистояти. Що за біса flapMap? я хочу вирівняти свою карту, а не розмахувати її!
ErikE

Що стосується іншої теми, це нарешті перетворює JavaScript Array's на Monads ™ ️ 🙃
Kutyel,

Це стара відповідь, але це досить неефективно, особливо для великих масивів (створює новий масив і копіює всі попередні елементи на кожній ітерації). Іншим підходом було б використовувати concat const flatMap = (arr, ...args) => [].concat(...arr.map(...args));(або використовувати, applyякщо ви не можете використовувати оператор поширення) - хоча ця версія concat має проблеми з розміром стека викликів для величезних масивів, як згадується в рішенні нижче
Bali

10

Я знаю, ви сказали, що не хочете визначати це самостійно, але ця реалізація є досить тривіальним визначенням.

Це також є з тієї ж сторінки github:

Ось трохи коротший спосіб використання es6-розширення, подібного до ренаудтретра - але використання es6 і не додавання до прототипу.

var flatMap = (a, cb) => [].concat(...a.map(cb))

const s = (v) => v.split(',')
const arr = ['cat,dog', 'fish,bird']

flatMap(arr, s)

Чи допомогло б щось із цього?

Слід зазначити (завдяки @ftor), що це останнє "рішення" страждає від "перевищено максимальний розмір стека викликів", якщо його викликати дійсно великим (наприклад, 300 тис. Елементів) масивом a.


2
Ця реалізація може підірвати стек для великих масивів.

Щоб бути зрозумілим, якщо я вас правильно розумію, @ftor, ви говорите про рекурсивну реалізацію в посиланні, яке я дав, а не про те, яке я показую в блоці коду в кінці моєї відповіді, так?
Алан Міммс,

2
Спробуйте [].concat(...(new Array(300000).fill("foo").map(x => x.toUpperCase()))). Звичайно, це кутова справа. Але вам слід хоча б згадати про це.

Дякую. Я додав примітку з цього приводу.
Алан Міммс

7

Lodash надає функцію плоскої карти, яка для мене практично еквівалентна Javascript, що забезпечує її безпосередньо. Якщо ви не користувач Lodash, то Array.reduce()метод ES6 може дати вам той самий результат, але вам потрібно зіставити, а потім зрівняти дискретними кроками.

Нижче наведено приклад кожного методу, що відображає список цілих чисел і повертає лише шанси.

Лодаш:

_.flatMap([1,2,3,4,5], i => i%2 !== 0 ? [i] : [])

ES6 Зменшення:

[1,2,3,4,5].map(i => i%2 !== 0 ? [i] : []).reduce( (a,b) => a.concat(b), [] )

4

Досить стислим підходом є використання Array#concat.apply:

const flatMap = (arr, f) => [].concat.apply([], arr.map(f))

console.log(flatMap([1, 2, 3], el => [el, el * el]));


1

Я зробив щось подібне:

Array.prototype.flatMap = function(selector){ 
  return this.reduce((prev, next) => 
    (/*first*/ selector(prev) || /*all after first*/ prev).concat(selector(next))) 
}

[[1,2,3],[4,5,6],[7,8,9]].flatMap(i => i); //[1, 2, 3, 4, 5, 6, 7, 8, 9]

[{subarr:[1,2,3]},{subarr:[4,5,6]},{subarr:[7,8,9]}].flatMap(i => i.subarr); //[1, 2, 3, 4, 5, 6, 7, 8, 9]


Привіт serhiii, я спробував цей код, коли батьківський лише один об'єкт дасть несподіваний результат. `` `Array.prototype.flatMap = функція (селектор) {if (this.length == 1) {селектор повернення (цей [0]); } повернути this.reduce ((попередня, наступна) => (/ * перша * / селектор (попередня) || / * все після першої * / попередньої) .concat (селектор (наступна)))}; ``
Джонні,

0

Тепер у нас є flatMap()Javascript! І це підтримується досить добре

Метод flatMap () спочатку відображає кожен елемент за допомогою функції відображення, а потім згладжує результат у новий масив. Він ідентичний карті (), за якою слідує квартира () глибиною 1

const dublicate = x => [x, x];

console.log([1, 2, 3].flatMap(dublicate))


Помилка при запуску опублікованого коду{ "message": "Uncaught TypeError: [1,2,3].flatMap is not a function", "filename": "https://stacksnippets.net/js", "lineno": 15, "colno": 23 }
SolutionMill

Яким брозером ви користуєтесь? Edge, IE та Samsung Internet поки що не підтримують це, адже Edge незабаром запрацює на V8. Отже, щоб підтримати тих, ви можете використовувати поліфіль
bigInt

Chrome версії 67.0.3396.87 (Офіційна збірка) (64-розрядна версія )
SolutionMill

Ваш браузер застарілий .. Вам слід оновити його або налаштувати на автоматичне оновлення.
bigInt

0

Array.prototype.flatMap()зараз прибув до JS. Однак не всі браузери можуть підтримувати їх перевірку на поточну сумісність браузера з веб-документами Mozilla .

Те , що flatMap()метод робить спочатку відображає кожен елемент , використовуючи функцію зворотного виклику в якості аргументу, а потім згладжує результат в новий масив (2D - масив тепер 1d , так як елементи розплющені).

Ось також приклад використання функції:

let arr = [[2], [4], [6], [8]]

let newArr = arr.flatMap(x => [x * 2]);

console.log(newArr);

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.