Dlaczego nie ma Array.prototype.flatMap w javascript?

82

flatMapjest niesamowicie przydatny w przypadku kolekcji, ale javascript nie udostępnia go podczas posiadania Array.prototype.map. Czemu?

Czy istnieje sposób na emulację flatMapw javascript zarówno w łatwy, jak i efektywny sposób bez flatMapręcznego definiowania ?

Sergey Alaev
źródło
10
"Czemu?" Ponieważ nikt jeszcze nie złożył propozycji? Oto, jak zaproponować nową funkcję . „Czy istnieje sposób na emulację flatMap ... bez ręcznego definiowania flatMap?” Uch? Nie rozumiem. Masz na myśli jak arr.reduce((arr, v) => (arr.push(...v), arr), [])?
Felix Kling
(^ to tylko spłaszczona część, więc chyba tak powinno być arr.map(...).reduce(...)).
Felix Kling
Po wysłaniu polecenia .mapping można po prostu spłaszczyć tablicę .
kennytm
1
Hmm, chcesz go „naśladować”, ale nie „definiować”. Co to może znaczyć?
8
Wkrótce będzie częścią JS. tc39.github.io/proposal-flatMap
Vijaiendran

Odpowiedzi:

99

Aktualizacja: Array.prototype.flatMap dotarła do ES2019

Jest szeroko obsługiwany w wielu środowiskach. Sprawdź, czy działa w Twojej przeglądarce, korzystając z poniższego fragmentu kodu -

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 ]


„Dlaczego nie ma Array.prototype.flatMap w javascript?”

Ponieważ programowanie nie jest magią, a każdy język nie ma funkcji / prymitywów, które ma każdy inny język. Liczy się to, że JavaScript daje możliwość samodzielnego zdefiniowania -

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

Albo przepisanie, które zwija dwie pętle w jedną -

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

const xs = [1,2,3]

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

Jeśli chcesz go przedłużyć Array.prototype, nic Cię nie powstrzymuje -

if (!Array.prototype.flatMap) {
  function flatMap (f, ctx) {
    return this.reduce
      ( (r, x, i, a) =>
          r.concat(f.call(ctx, x, i, a))
      , []
      )
  }
  Array.prototype.flatMap = flatMap
}

const ranks =
  [ 'J', 'Q', 'K', 'A' ]
  
const suits =
  [ '♡', '♢', '♤', '♧' ]

const result =
  ranks.flatMap(r =>
    suits.flatMap(s =>
      [[r, s]]
    )
  )

console.log(JSON.stringify(result))
// [ ['J','♡'], ['J','♢'], ['J','♤'], ['J','♧']
// , ['Q','♡'], ['Q','♢'], ['Q','♤'], ['Q','♧']
// , ['K','♡'], ['K','♢'], ['K','♤'], ['K','♧']
// , ['A','♡'], ['A','♢'], ['A','♤'], ['A','♧']
// ]

Dziękuję Ci
źródło
1
Uważa się, że lepiej jest nazwać wszelkie dodatki do prototypów (np. Array.prototype._mylib_flatMap), A nawet lepiej używać symboli ES6, zamiast po prostu definiować Array.prototype.flatMap.
Fengyang Wang
11
@FengyangWang „uważany za lepszy” jest wysoce subiektywny. Jeśli jest to Twoja własna aplikacja i masz powód do rozbudowy swoich prototypów, nie ma w tym nic złego. Jeśli jest to biblioteka / moduł / framework, który rozpowszechniasz do użytku innych, zgodzę się z tobą. Komentarze nie są jednak miejscem, w którym można o tym dyskutować - sprawa zasługuje na więcej wyjaśnień i szczegółów, niż powinny być zawarte w komentarzach.
Dziękuję
Pierwsza jest brzydka: flatMap zdefiniowana jako funkcja spowoduje bałagan w kodzie - wyobraź sobie kod funkcjonalny ze mapzdefiniowaną jako funkcja globalna! Drugi to po prostu zły trening
Sergey Alaev
3
@SergeyAlaev re: "wyobraź sobie mapę jako funkcję globalną" haha Robię to dokładnie w przypadku wielu moich funkcji narzędziowych. Też je curry używając sekwencji funkcji strzałek. „Brzydki” jest subiektywny. I nie przedstawiłeś żadnego argumentu na poparcie swojego komentarza o „złej praktyce”. Twoje uwagi wskazują mi, że masz niewielkie doświadczenie w programowaniu funkcjonalnym. Weźmy język taki jak Haskell, w którym preludium zawiera mnóstwo „globalnych”… nikt nie mówi, że jest „brzydki” lub „zła praktyka”. Po prostu go nie znasz.
Dziękuję
5
@SergeyAlaev lub uważają, że rakieta ma append, map, filter, foldl, foldrwśród niezliczonych innych w domyślnej przestrzeni nazw. Nie oznacza to złej praktyki, ponieważ słyszałeś, jak ktoś powiedział „globalne są złe” lub „rozszerzający się tubylcy jest zły” - rakieta jest jednym z najlepiej zaprojektowanych języków. Po prostu nie wiesz, jak / kiedy zrobić to odpowiednio.
Dziękuję
48

flatMapzostał zatwierdzony przez TC39 jako część ES2019 (ES10). Możesz go używać w ten sposób:

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

Oto moja własna implementacja metody:

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

Artykuł MDN na flatMap

Kutyel
źródło
W szczególności flapMap jest oficjalnie w węźle 11.0.0 według MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ...
Carl Walsh
3
@CarlWalsh Przepraszam, nie mogę się oprzeć. Co to do cholery jest flapMap? chcę spłaszczyć mapę, a nie machać nią!
ErikE
Jeśli chodzi o inny temat, to ostatecznie zamienia JavaScript Array w Monads ™ ️ 🙃
Kutyel
To jest stara odpowiedź, ale jest to dość nieefektywne, szczególnie w przypadku dużych tablic (tworzy nową tablicę i kopiuje wszystkie poprzednie elementy w każdej iteracji). Innym podejściem byłoby użycie concat const flatMap = (arr, ...args) => [].concat(...arr.map(...args));(lub użycie, applyjeśli nie możesz użyć operatora spreadu) - chociaż ta wersja concat ma problemy z rozmiarem stosu wywołań dla dużych tablic, jak wspomniano w rozwiązaniu niższym
Bali Balo
10

Wiem, że powiedziałeś, że nie chcesz tego definiować samodzielnie, ale ta implementacja jest dość banalną definicją.

Jest też to z tej samej strony github:

Oto trochę krótszy sposób korzystania z rozprzestrzeniania es6, podobnie jak w przypadku renaudtertrais - ale z użyciem es6 i bez dodawania do prototypu.

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

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

flatMap(arr, s)

Czy któreś z tych pomogłoby?

Należy zauważyć (dzięki @ftor), że to drugie "rozwiązanie" cierpi z powodu "Przekroczenia maksymalnego rozmiaru stosu wywołań", jeśli jest wywoływane na naprawdę dużej (np. 300k elementów) tablicy a.

Alan Mimms
źródło
2
Ta implementacja może wysadzić stos w przypadku dużych tablic.
Żeby było jasne, jeśli dobrze cię rozumiem, @ftor, mówisz o rekurencyjnej implementacji w linku, który podałem, a nie o tej, którą pokazuję w bloku kodu na końcu mojej odpowiedzi, prawda?
Alan Mimms
2
Spróbuj [].concat(...(new Array(300000).fill("foo").map(x => x.toUpperCase()))). Jasne, to sprawa narożna. Ale powinieneś przynajmniej o tym wspomnieć.
Dzięki. Dodałem uwagę na ten temat.
Alan Mimms
7

Lodash udostępnia funkcję flatmap, która jest dla mnie praktycznie równoważna z Javascriptem udostępniającym ją natywnie. Jeśli nie jesteś użytkownikiem Lodash, Array.reduce()metoda ES6 może dać ten sam wynik, ale musisz odwzorować, a następnie spłaszczyć w dyskretnych krokach.

Poniżej znajduje się przykład każdej metody, mapujący listę liczb całkowitych i zwracający tylko kursy.

Lodash:

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

ES6 Reduce:

[1,2,3,4,5].map(i => i%2 !== 0 ? [i] : []).reduce( (a,b) => a.concat(b), [] )
Matthew Marichiba
źródło
4

Jednym dość zwięzłym podejściem jest wykorzystanie Array#concat.apply:

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

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

JLRishe
źródło
1

Zrobiłem coś takiego:

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]

serhii
źródło
Cześć serhii, próbowałem tego kodu, gdy rodzic tylko jeden obiekt da nieoczekiwany wynik. `` `` Array.prototype.flatMap = function (selector) {if (this.length == 1) {return selector (this [0]); } return this.reduce ((prev, next) => (/ * first * / selector (prev) || / * all after first * / prev) .concat (selector (next)))}; ``
Johnny
0

Mamy teraz flatMap()w Javascript! I jest obsługiwany całkiem dobrze

Metoda flatMap () najpierw odwzorowuje każdy element za pomocą funkcji mapującej, a następnie spłaszcza wynik do nowej tablicy. Jest identyczny z mapą (), po której następuje flat () o głębokości 1

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

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

bigInt
źródło
Pojawia się błąd podczas uruchamiania opublikowanego kodu{ "message": "Uncaught TypeError: [1,2,3].flatMap is not a function", "filename": "https://stacksnippets.net/js", "lineno": 15, "colno": 23 }
SolutionMill
Jakiego brosera używasz? Edge, IE i Samsung Internet jeszcze go nie obsługują, chociaż Edge wkrótce będzie działał na V8. Aby je wesprzeć, możesz użyć pliku polyfil
bigInt
Wersja Chrome 67.0.3396.87 (oficjalna kompilacja) (64-bitowa)
SolutionMill
Twoja przeglądarka jest przestarzała. Należy ją zaktualizować lub ustawić automatyczne aktualizowanie.
bigInt
0

Array.prototype.flatMap()pojawił się w JS. Jednak nie wszystkie przeglądarki mogą je obsługiwać. Sprawdź aktualną zgodność z przeglądarkami internetowymi Mozilli .

Co do flatMap()metody nie jest pierwszym odwzorowuje każdy element, przy użyciu funkcji zwrotnego jako argumentu, a następnie spłaszcza się wynik do nowej tablicy (2d tablicy jest teraz 1d, ponieważ elementy są spłaszczone).

Oto również przykład użycia funkcji:

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

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

console.log(newArr);

Willem van der Veen
źródło