Эквивалент LINQ SingleOrDefault()

В Typescript я часто использую этот шаблон:

class Vegetable {
    constructor(public id: number, public name: string) {
    }
}

var vegetable_array = new Array<Vegetable>();
vegetable_array.push(new Vegetable(1, "Carrot"));
vegetable_array.push(new Vegetable(2, "Bean"));
vegetable_array.push(new Vegetable(3, "Peas"));

var id = 1;
var collection = vegetable_array.filter( xvegetable => {
    return xvegetable.id == id;
});
var item = collection.length < 1 ? null : collection[0];

console.info( item.name );

Я думаю о создании расширения JavaScript, похожий на LINQ SingleOrDefault метод, при котором он возвращает null, если он не в массиве:

var item = vegetable.singleOrDefault( xvegetable => {
    return xvegetable.id == id});

У меня вопрос, есть ли другой способ добиться этого без создания пользовательского интерфейса?

Ответы

Ответ 1

Вы всегда можете использовать Array.prototype.filter следующим образом:

var arr = [1,2,3];
var notFoundItem = arr.filter(id => id === 4)[0]; // will return undefined
var foundItem = arr.filter(id => id === 3)[0]; // will return 3

Изменить
Мой ответ относится к FirstOrDefault, а не к SingleOrDefault.
SingleOrDefault проверяет, есть ли только одно совпадение, и в моем случае (и в вашем коде) вы возвращаете первое совпадение, не проверяя наличие другого соответствия.

Кстати, если вы хотите достичь SingleOrDefault, вам нужно будет изменить это:

var item = collection.length < 1 ? null : collection[0];

в

if(collection.length > 1)
   throw "Not single result....";

return collection.length === 0 ? null : collection[0];

Ответ 2

Если вы хотите найти один элемент в массиве, я предлагаю вам использовать метод поиска ES6, например, так:

const inventory = [
    { name: 'apples', quantity: 2 },
    { name: 'bananas', quantity: 0 },
    { name: 'cherries', quantity: 5 }
];

const result = inventory.find(fruit => fruit.name === 'cherries');

console.log(result) // { name: 'cherries', quantity: 5 }

Это быстрее и проще для чтения, потому что вам не нужно писать collection[0].

Кстати, С# SingleOrDefault выдает исключение, если найдено более одного элемента. Здесь вы пытаетесь создать расширение FirstOrDefault.

Ответ 3

Теперь есть библиотека, которая предоставляет строго типизированные, запрашиваемые коллекции в машинописи.

Библиотека называется ts-generic-collection.

Исходный код на GitHub:

https://github.com/VeritasSoftware/ts-generic-collections

С помощью этой библиотеки вы можете писать запросы, как показано ниже:

    let owners = new List<Owner>();

    let owner = new Owner();
    owner.id = 1;
    owner.name = "John Doe";
    owners.add(owner);

    owner = new Owner();
    owner.id = 2;
    owner.name = "Jane Doe";
    owners.add(owner);    

    let pets = new List<Pet>();

    let pet = new Pet();
    pet.ownerId = 2;
    pet.name = "Sam";
    pet.sex = Sex.M;

    pets.add(pet);

    pet = new Pet();
    pet.ownerId = 1;
    pet.name = "Jenny";
    pet.sex = Sex.F;

    pets.add(pet);

    //query to get owners by their pet gender/sex
    let ownersByPetSex = owners.join(pets, owner => owner.id, pet => pet.ownerId, (x, y) => new OwnerPet(x,y))
                               .groupBy(x => [x.pet.sex])
                               .select(x =>  new OwnersByPetSex(x.groups[0], x.list.select(x => x.owner)));

    expect(ownersByPetSex.toArray().length === 2).toBeTruthy();

    expect(ownersByPetSex.toArray()[0].sex == Sex.F).toBeTruthy();
    expect(ownersByPetSex.toArray()[0].owners.length === 1).toBeTruthy();
    expect(ownersByPetSex.toArray()[0].owners.toArray()[0].name == "John Doe").toBeTruthy();

    expect(ownersByPetSex.toArray()[1].sex == Sex.M).toBeTruthy();
    expect(ownersByPetSex.toArray()[1].owners.length == 1).toBeTruthy();
    expect(ownersByPetSex.toArray()[1].owners.toArray()[0].name == "Jane Doe").toBeTruthy(); 

Ответ 4

Если повторное использование не является необходимым, вы могли бы реализовать singleOrDefault путем применения простого снижения функции:

В этом случае функция редуктора рассчитывается только тогда, когда массив не пустой. Выдает, когда длина больше 1, в противном случае возвращается единственный элемент. Когда массив пуст, возвращается параметр значения по умолчанию функции Reduce: в этом случае null.

Например:

[].reduce(function(acc, cur, idx, src) {
  if (src.length > 1) {
    throw 'More than one found';
  }
  return src[0];
}, null);