Как сделать эквивалент 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
toddmo
источник
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 был добавлен в прототип массива. SelectManyявляется синонимом flatMap.
Метод сначала отображает каждый элемент с помощью функции сопоставления, а затем объединяет результат в новый массив. Его упрощенная подпись в TypeScript:

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

Для выполнения задачи нам просто нужно сопоставить каждый элемент с phoneNumbers.

arr.flatMap(a => a.phoneNumbers);
Саги
источник
11
Последнее также можно записать какarr.reduce(function(a, b){ return a.concat(b.phoneNumbers); }, [])
Timwi
Вам не нужно использовать .map () или .concat (). Смотрите мой ответ ниже.
WesleyAC
не изменяет ли это исходный массив?
Ewan
Менее важно сейчас, но flatMapне отвечает запросу OP на решение, совместимое с IE9 - совместимость браузера дляflatmap .
ругань
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 с 12/4/2019.
pettys 05
Но новый 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. Постараюсь свести ваш ответ к одной строчке кода. «Как сгладить ответ о сглаживании иерархий объектов» lol
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

Итак, переписанная версия ответа Джоэла-Харкса на TypeScript в качестве расширения, которое можно использовать в любом массиве. Так что вы можете буквально использовать это как somearray.selectMany(c=>c.someprop). Trans-piled, это 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 { };
Достойно7
источник