Як зробити еквівалент LINQ SelectMany () просто в javascript


90

На жаль, у мене немає JQuery чи Underscore, лише чистий javascript (сумісний з IE9).

Я хочу еквівалент SelectMany () від функціональності LINQ.

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

Чи можу я це зробити?

РЕДАГУВАТИ:

Завдяки відповідям я отримав це:

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

5
Це зовсім не прикро. Чистий JavaScript дивовижний. Без контексту дуже важко зрозуміти, чого ви тут прагнете досягти.
Стерлінг Арчер,

3
@SterlingArcher, подивись, наскільки конкретною виявилася відповідь. Тут було не так вже й багато можливих відповідей, і найкраща відповідь була короткою і короткою.
toddmo

Відповіді:


121

для простого вибору ви можете використовувати функцію зменшення масиву.
Скажімо, у вас є масив масивів чисел:

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]

Редагувати:
оскільки es6 flatMap було додано до прототипу Array. SelectManyє синонімом до flatMap.
Спочатку метод спочатку відображає кожен елемент за допомогою функції відображення, а потім згладжує результат у новий масив. Його спрощений підпис у TypeScript:

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

Для того, щоб досягти завдання, нам просто потрібно зрівняти кожен елемент з телефонними номерами

arr.flatMap(a => a.phoneNumbers);

11
Цей останній також можна записати якarr.reduce(function(a, b){ return a.concat(b.phoneNumbers); }, [])
Тімві

Вам не потрібно використовувати .map () або .concat (). Дивіться мою відповідь нижче.
WesleyAC

це не змінює оригінальний масив?
Юан,

Зараз це менш важливо, але flatMapне відповідає запиту ОП на рішення, сумісне з IE9 - браузер,flatmap сумісний з .
jorffin

25

Як простіший варіант Array.prototype.flatMap () або 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)


3
Просто і лаконічно. Безумовно найкраща відповідь.
Ste Brown

flat () недоступний у Edge відповідно до MDN станом на 4.12.2019.
pettys

Але новий Edge (на основі Chromium) його підтримає.
Неціп Сунмаз,

2
Схоже, Array.prototype.flatMap () - це теж річ, тому ваш приклад можна спроститиconst result = data.flatMap(a => a.details)
Кайл,

12

Для тих, хто пізніше зрозумів javascript, але все одно хочеться простого методу Typed SelectMany у 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>());
}

8

Сагі правильно використовує метод concat для згладжування масиву. Але щоб отримати щось подібне до цього прикладу, вам також знадобиться карта для виділеної частини 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,[])

Я збираюся зробити скрипку; Я можу використовувати ваші дані як json. Спробує зрівняти вашу відповідь з одним рядком коду. "Як згладити відповідь про згладжування ієрархій об’єктів" ха-ха
toddmo

Не соромтеся використовувати ваші дані ... Я чітко абстрагував функцію карти, щоб ви могли легко вибрати будь-яку назву властивості, не щоразу писати нову функцію. Просто замініть arrна peopleта "pets"з"PhoneNumbers"
Фабіо Белтраміні

Відредагував моє запитання в урізаній версії та проголосував за вашу відповідь. Дякую.
toddmo

1
Допоміжні функції робити чисті речі, але з ES6 ви можете просто зробити це: petOwners.map(owner => owner.Pets).reduce((a, b) => a.concat(b), []);. Або, ще простіше, 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"]}]

Давайте розпочнемо

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"]

ПРИМІТКА: використовуйте дійсний масив (не нульовий) як початкове значення у функції зменшення


3

спробуйте це (за допомогою es6):

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

приклад масиву:

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

за допомогою:

juices.SelectMany(x=>x.data)

3

Я б зробив це (уникаючи .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]

Якщо ви не хочете використовувати .reduce ():

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

Якщо ви хочете використовувати .forEach ():

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

    return arr;
}

2
Мені здається смішним, що я запитав це 4 роки тому, і з’явилося сповіщення про переповнення стеку для вашої відповіді, і те, що я роблю в цей момент, бореться з масивами js. Приємна рекурсія!
toddmo

Я просто рада, що хтось це побачив! Мене здивувало, що щось на зразок написаного я ще не було в списку, враховуючи, як довго триває нитка і скільки є спроб відповідей.
WesleyAC

@toddmo Для чого це варто, якщо ви зараз працюєте над масивами js, вас може зацікавити рішення, яке я нещодавно додав тут: stackoverflow.com/questions/1960473/… .
WesleyAC

2

Ось, ви переписали версію відповіді joel-harkes у TypeScript як розширення, яке можна використовувати для будь-якого масиву. Таким чином, ви можете буквально використовувати це як somearray.selectMany(c=>c.someprop). Перекладено, це 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 { };

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