Como fazer o equivalente a LINQ SelectMany () apenas em javascript

93

Infelizmente, não tenho JQuery ou Underscore, apenas javascript puro (compatível com o IE9).

Estou querendo o equivalente a SelectMany () da funcionalidade LINQ.

// SelectMany flattens it to just a list of phone numbers.
IEnumerable<PhoneNumber> phoneNumbers = people.SelectMany(p => p.PhoneNumbers);

Posso fazer isso?

EDITAR:

Graças às respostas, consegui fazer funcionar:

var petOwners = 
[
    {
        Name: "Higa, Sidney", Pets: ["Scruffy", "Sam"]
    },
    {
        Name: "Ashkenazi, Ronen", Pets: ["Walker", "Sugar"]
    },
    {
        Name: "Price, Vernette", Pets: ["Scratches", "Diesel"]
    },
];

function property(key){return function(x){return x[key];}}
function flatten(a,b){return a.concat(b);}

var allPets = petOwners.map(property("Pets")).reduce(flatten,[]);

console.log(petOwners[0].Pets[0]);
console.log(allPets.length); // 6

var allPets2 = petOwners.map(function(p){ return p.Pets; }).reduce(function(a, b){ return a.concat(b); },[]); // all in one line

console.log(allPets2.length); // 6
toddmo
fonte
5
Isso não é nada lamentável. JavaScript puro é incrível. Sem contexto, é muito difícil entender o que você está tentando alcançar aqui.
Sterling Archer de
3
@SterlingArcher, veja como a resposta acabou sendo específica. Não havia muitas respostas possíveis e a melhor resposta era curta e concisa.
toddmo

Respostas:

128

para uma seleção simples, você pode usar a função de redução de Array.
Digamos que você tenha uma matriz de matrizes de números:

var arr = [[1,2],[3, 4]];
arr.reduce(function(a, b){ return a.concat(b); });
=>  [1,2,3,4]

var arr = [{ name: "name1", phoneNumbers : [5551111, 5552222]},{ name: "name2",phoneNumbers : [5553333] }];
arr.map(function(p){ return p.phoneNumbers; })
   .reduce(function(a, b){ return a.concat(b); })
=>  [5551111, 5552222, 5553333]

Editar:
desde que es6 flatMap foi adicionado ao protótipo Array. SelectManyé sinônimo de flatMap.
O método primeiro mapeia cada elemento usando uma função de mapeamento e, em seguida, nivela o resultado em uma nova matriz. Sua assinatura simplificada no TypeScript é:

function flatMap<A, B>(f: (value: A) => B[]): B[]

Para realizar a tarefa, só precisamos mapear cada elemento para phoneNumbers

arr.flatMap(a => a.phoneNumbers);
Sagi
fonte
11
Esse último também pode ser escrito comoarr.reduce(function(a, b){ return a.concat(b.phoneNumbers); }, [])
Timwi
Você não deve precisar usar .map () ou .concat (). Veja minha resposta abaixo.
WesleyAC de
isso não altera o array original?
Ewan
Menos importante agora, mas flatMapnão atende ao pedido do OP de uma solução compatível com o IE9 - compatibilidade do navegadorflatmap .
ruffin
1
reduz () falha em um array vazio, então você teria que adicionar um valor inicial. consulte stackoverflow.com/questions/23359173/…
halllo
26

Como uma opção mais simples Array.prototype.flatMap () ou Array.prototype.flat ()

const data = [
{id: 1, name: 'Dummy Data1', details: [{id: 1, name: 'Dummy Data1 Details'}, {id: 1, name: 'Dummy Data1 Details2'}]},
{id: 1, name: 'Dummy Data2', details: [{id: 2, name: 'Dummy Data2 Details'}, {id: 1, name: 'Dummy Data2 Details2'}]},
{id: 1, name: 'Dummy Data3', details: [{id: 3, name: 'Dummy Data3 Details'}, {id: 1, name: 'Dummy Data3 Details2'}]},
]

const result = data.flatMap(a => a.details); // or data.map(a => a.details).flat(1);
console.log(result)

Necip Sunmaz
fonte
3
Simples e conciso. De longe a melhor resposta.
Ste Brown de
plano () não está disponível no Edge de acordo com MDN a partir de 04/12/2019.
pettys
Mas o novo Edge (baseado em Chromium) irá suportá-lo.
Necip Sunmaz
2
Parece que Array.prototype.flatMap () também é uma coisa, então seu exemplo poderia ser simplificado paraconst result = data.flatMap(a => a.details)
Kyle
12

Para aqueles que estão um pouco mais tarde, entendem o javascript, mas ainda querem um método simples SelectMany Typed em Typescript:

function selectMany<TIn, TOut>(input: TIn[], selectListFn: (t: TIn) => TOut[]): TOut[] {
  return input.reduce((out, inx) => {
    out.push(...selectListFn(inx));
    return out;
  }, new Array<TOut>());
}
Joel Harkes
fonte
8

Sagi está correto ao usar o método concat para nivelar uma matriz. Mas para obter algo semelhante a este exemplo, você também precisaria de um mapa para a parte selecionada https://msdn.microsoft.com/library/bb534336(v=vs.100).aspx

/* arr is something like this from the example PetOwner[] petOwners = 
                    { new PetOwner { Name="Higa, Sidney", 
                          Pets = new List<string>{ "Scruffy", "Sam" } },
                      new PetOwner { Name="Ashkenazi, Ronen", 
                          Pets = new List<string>{ "Walker", "Sugar" } },
                      new PetOwner { Name="Price, Vernette", 
                          Pets = new List<string>{ "Scratches", "Diesel" } } }; */

function property(key){return function(x){return x[key];}}
function flatten(a,b){return a.concat(b);}

arr.map(property("pets")).reduce(flatten,[])
Fabio Beltramini
fonte
Eu vou fazer um violino; Posso usar seus dados como json. Tentará nivelar sua resposta a uma linha de código. "Como nivelar uma resposta sobre o nivelamento de hierarquias de objetos" lol
toddmo
Sinta-se à vontade para usar seus dados ... Eu abstraí explicitamente a função do mapa para que você pudesse selecionar facilmente qualquer nome de propriedade sem ter que escrever uma nova função a cada vez. Basta substituir arrpor peoplee "pets"por"PhoneNumbers"
Fabio Beltramini
Editou minha pergunta com a versão simplificada e votou em sua resposta. Obrigado.
toddmo
1
As funções auxiliares fazer as coisas limpas, mas com ES6 você pode simplesmente fazer isso: petOwners.map(owner => owner.Pets).reduce((a, b) => a.concat(b), []);. Ou, ainda mais simples petOwners.reduce((a, b) => a.concat(b.Pets), []);,.
ErikE
3
// you can save this function in a common js file of your project
function selectMany(f){ 
    return function (acc,b) {
        return acc.concat(f(b))
    }
}

var ex1 = [{items:[1,2]},{items:[4,"asda"]}];
var ex2 = [[1,2,3],[4,5]]
var ex3 = []
var ex4 = [{nodes:["1","v"]}]

Vamos começar

ex1.reduce(selectMany(x=>x.items),[])

=> [1, 2, 4, "asda"]

ex2.reduce(selectMany(x=>x),[])

=> [1, 2, 3, 4, 5]

ex3.reduce(selectMany(x=> "this will not be called" ),[])

=> []

ex4.reduce(selectMany(x=> x.nodes ),[])

=> ["1", "v"]

NOTA: use uma matriz válida (não nula) como valor inicial na função de redução

Bogdan Manole
fonte
3

tente isto (com ES6):

 Array.prototype.SelectMany = function (keyGetter) {
 return this.map(x=>keyGetter(x)).reduce((a, b) => a.concat(b)); 
 }

array de exemplo:

 var juices=[
 {key:"apple",data:[1,2,3]},
 {key:"banana",data:[4,5,6]},
 {key:"orange",data:[7,8,9]}
 ]

usando:

juices.SelectMany(x=>x.data)
Cem Tuğut
fonte
3

Eu faria isso (evitando .concat ()):

function SelectMany(array) {
    var flatten = function(arr, e) {
        if (e && e.length)
            return e.reduce(flatten, arr);
        else 
            arr.push(e);
        return arr;
    };

    return array.reduce(flatten, []);
}

var nestedArray = [1,2,[3,4,[5,6,7],8],9,10];
console.log(SelectMany(nestedArray)) //[1,2,3,4,5,6,7,8,9,10]

Se você não quiser usar .reduce ():

function SelectMany(array, arr = []) {
    for (let item of array) {
        if (item && item.length)
            arr = SelectMany(item, arr);
        else
            arr.push(item);
    }
    return arr;
}

Se você deseja usar .forEach ():

function SelectMany(array, arr = []) {
    array.forEach(e => {
        if (e && e.length)
            arr = SelectMany(e, arr);
        else
            arr.push(e);
    });

    return arr;
}
WesleyAC
fonte
2
Eu acho engraçado que eu perguntei isso 4 anos atrás, e a notificação de estouro de pilha para sua resposta apareceu, e o que estou fazendo neste momento é lutar com arrays js. Boa recursão!
toddmo
Estou feliz que alguém viu! Fiquei surpreso que algo como eu escrevi ainda não estava listado, dado o tempo de duração do tópico e quantas tentativas de resposta existem.
WesleyAC
@toddmo Pelo que vale, se você está trabalhando em matrizes js agora, você pode se interessar pela solução que adicionei recentemente aqui: stackoverflow.com/questions/1960473/… .
WesleyAC de
2

Aqui está, uma versão reescrita da resposta de joel-harkes no TypeScript como uma extensão, utilizável em qualquer array. Então você pode literalmente usar como somearray.selectMany(c=>c.someprop). Transferido, isso é javascript.

declare global {
    interface Array<T> {
        selectMany<TIn, TOut>(selectListFn: (t: TIn) => TOut[]): TOut[];
    }
}

Array.prototype.selectMany = function <TIn, TOut>( selectListFn: (t: TIn) => TOut[]): TOut[] {
    return this.reduce((out, inx) => {
        out.push(...selectListFn(inx));
        return out;
    }, new Array<TOut>());
}


export { };
Digno 7
fonte