Cómo hacer el equivalente de LINQ SelectMany () solo en javascript

90

Desafortunadamente, no tengo JQuery o Underscore, solo javascript puro (compatible con IE9).

Quiero el equivalente de SelectMany () de la funcionalidad LINQ.

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

¿Puedo hacerlo?

EDITAR:

Gracias a las respuestas, conseguí que esto funcionara:

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
fuente
5
Eso no es nada desafortunado. JavaScript puro es asombroso. Sin contexto, es muy difícil entender lo que está tratando de lograr aquí.
Sterling Archer
3
@SterlingArcher, vea cuán específica resultó ser la respuesta. No hubo demasiadas respuestas posibles y la mejor respuesta fue corta y concisa.
toddmo

Respuestas:

120

para una selección simple, puede usar la función de reducción de Array.
Digamos que tiene una matriz de matrices 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:
dado que es6 flatMap se ha agregado al prototipo de Array. SelectManyes sinónimo de flatMap.
El método primero mapea cada elemento usando una función de mapeo, luego aplana el resultado en una nueva matriz. Su firma simplificada en TypeScript es:

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

Para lograr la tarea solo necesitamos flatMap cada elemento a phoneNumbers

arr.flatMap(a => a.phoneNumbers);
Sagi
fuente
11
Ese último también se puede escribir comoarr.reduce(function(a, b){ return a.concat(b.phoneNumbers); }, [])
Timwi
No debería necesitar usar .map () o .concat (). Vea mi respuesta a continuación.
WesleyAC
¿No altera esto la matriz original?
Ewan
Ahora es menos importante, pero flatMapno cumple con la solicitud del OP de una solución compatible con IE9: compatibilidad del navegador paraflatmap .
Ruffin
24

Como opción más simple, Array.prototype.flatMap () o 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
fuente
3
Sencillo y conciso. De lejos, la mejor respuesta.
Ste Brown
flat () no está disponible en Edge según MDN a partir del 12/4/2019.
pettys
Pero el nuevo Edge (basado en Chromium) lo admitirá.
Necip Sunmaz
2
Parece que Array.prototype.flatMap () también es una cosa, por lo que su ejemplo podría simplificarse aconst result = data.flatMap(a => a.details)
Kyle
12

Para aquellos que un tiempo después, entienden javascript pero aún quieren un método simple Typed SelectMany en Typecript:

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
fuente
8

Sagi tiene razón al usar el método concat para aplanar una matriz. Pero para obtener algo similar a este ejemplo, también necesitaría un mapa para la parte seleccionada 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
fuente
Voy a hacer un violín; Puedo usar tus datos como json. Intentará acoplar su respuesta a una línea de código. "Cómo aplanar una respuesta sobre cómo aplanar jerarquías de objetos" jajaja
toddmo
Siéntase libre de usar sus datos ... Extraje explícitamente la función del mapa para que pueda seleccionar fácilmente cualquier nombre de propiedad sin tener que escribir una nueva función cada vez. Simplemente reemplace arrcon peopley "pets"con"PhoneNumbers"
Fabio Beltramini
Edité mi pregunta con una versión plana y voté tu respuesta. Gracias.
toddmo
1
Las funciones de ayuda hacer las cosas limpias, pero con ES6 que sólo pueden hacer esto: petOwners.map(owner => owner.Pets).reduce((a, b) => a.concat(b), []);. O, aún más simple, 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"]}]

Empecemos

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 una matriz válida (no nula) como valor inicial en la función de reducción

Bogdan Manole
fuente
3

prueba esto (con es6):

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

matriz de ejemplo:

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

utilizando :

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

Yo haría esto (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]

Si no desea utilizar .reduce ():

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

Si desea utilizar .forEach ():

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

    return arr;
}
WesleyAC
fuente
2
Creo que es gracioso que pregunté esto hace 4 años, y apareció la notificación de desbordamiento de pila para su respuesta, y lo que estoy haciendo en este momento es luchar con matrices js. ¡Buena recursión!
toddmo
¡Me alegro de que alguien lo haya visto! Me sorprendió que algo como lo que escribí no estuviera ya en la lista, dado el tiempo que lleva el hilo y cuántos intentos de respuestas hay.
WesleyAC
@toddmo Por lo que vale, si está trabajando en matrices js en este momento, es posible que le interese la solución que agregué recientemente aquí: stackoverflow.com/questions/1960473/… .
WesleyAC
2

Aquí tiene, una versión reescrita de la respuesta de joel-harkes en TypeScript como una extensión, utilizable en cualquier matriz. Así que literalmente puedes usarlo como somearray.selectMany(c=>c.someprop). Transpilado, esto es 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 { };
Digno7
fuente