RyotaMurohoshi / ImportedLinq

ImportedLinq provides utility collection methods that are imported from other programming languages.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ImportedLinq (Beta)

ImportedLinq provides utility collection methods that are imported from other programming languages.

What is ImportedLinq?

LINQ to Objects is one of the best language feature in C#. But LINQ to Objects does not have some methods and features.

For example,

  • MaxBy finds the elements that have maximum key value in the sequence by using the default comparer.
  • Buffer generates a sequence of buffers over the source sequence with specified length.
  • Flatten converts sequence of sequence to sequence.
  • IsEmpty determines whether sequence is empty or not.

These methods are common in other programming languages.

ImportedLinq consists of LINQ like extension methods that are imported from other programming language such MaxBy, Buffer, Flatten and IsEmpty.

ImportedLinq provide collections methods that are common in other programming language to C# programmer. And ImportedLinq helps programmers who are new to C# but have experience to other programming language.

Project Status

ImportedLinq is beta version. Breaking changes may be occur.

Requirements

ImportedLinq needs .NET Standard 1.0.

If you use ImportedLinq in Unity, Please download unitypackage from release page

How to use ImportedLinq?

Add using directive like next code.

using ImportedLinq;

Like LINQ methods, use as extension method for IEnumerable<T>.

IReadOnlyList<Monster> monsters = LoadMonsters();

IReadOnlyCollection<Monster> maxLevelMonsters = monsters.MaxBy(it => it.Level);

IEnumerable<IReadOnlyList<Monster>> bufferedMonsters = monsters.Buffer(8);

Difference from other libraries

There are some great collection method libraries for C#.

This section describes difference between them and ImportedLinq.

Please note that ImportedLinq provides utility collection methods that are imported from other Languages. This is ImportedLinq's motto.

Difference from Ix.NET

Ix.NET

The Interactive Extensions (Ix) is a .NET library which extends LINQ to Objects to provide many of the operators available in Rx but targeted for IEnumerable

from Ix.NET section description.

Ix.NET provides many utility methods for IEnumerable<T>. But please note that these methods are correspond methods to Rx.NET methods.

Difference from MoreLINQ

MoreLINQ

LINQ to Objects is missing a few desirable features.

This project enhances LINQ to Objects with extra methods, in a manner which keeps to the spirit of LINQ.

from MoreLINQ's README top content.

MoreLINQ is great C# collection method library and has many extension collection methods for IEnumerable<T>. Some of them are overlapped with ImportedLinq.

But MoreLINQ does not have methods whose implementation are so simple like IsEmpty and flatten. See this issue.

And there are so many useful methods in MoreLINQ. Compare MoreLINQ, ImportedLinq provides only methods that are common in other programming languages.

Methods

Show example method usage with next code.

enum Direction
{
    Up, Right, Down, Left,
}

enum MonsterType
{
    Grass, Fire, Water,
}

struct Monster
{
    public int Level { get; }
    public MonsterType MonsterType { get; }
    public Monster(int level, MonsterType monsterType) => (Level, MonsterType) = (level, monsterType);
}

struct Position
{
    public int X { get; }
    public int Y { get; }

    public Position(int x, int y) => (X, Y) = (x, y);

    public static Position Move(Position position, Direction direction)
    {
        switch (direction)
        {
            case Direction.Up: return new Position(position.X, position.Y + 1);
            case Direction.Right: return new Position(position.X + 1, position.Y);
            case Direction.Down: return new Position(position.X, position.Y - 1);
            case Direction.Left: return new Position(position.X - 1, position.Y);
            default: throw new Exception();
        }
    }
}

MaxBy / MinBy

Finds the elements that have maximum / minimum key value in the sequence by using the default comparer.

IEnumerable<Monster> source = new List<Monster>
{
    new Monster(5, MonsterType.Grass),
    new Monster(16, MonsterType.Grass),
    new Monster(32, MonsterType.Grass),
    new Monster(5, MonsterType.Fire),
    new Monster(55, MonsterType.Fire),
    new Monster(5, MonsterType.Water),
    new Monster(36, MonsterType.Water),
};

IReadOnlyCollection<Monster> expected = new List<Monster>
{
    new Monster(55, MonsterType.Fire),
};

IReadOnlyCollection<Monster> actual = source.MaxBy(it => it.Level);

Assert.Equal(expected, actual);

Buffer

Generates a sequence of buffers over the source sequence with specified length.

IEnumerable<Monster> source = new List<Monster>
{
    new Monster(5, MonsterType.Grass),
    new Monster(16, MonsterType.Grass),
    new Monster(32, MonsterType.Grass),
    new Monster(5, MonsterType.Fire),
    new Monster(55, MonsterType.Fire),
    new Monster(5, MonsterType.Water),
    new Monster(36, MonsterType.Water),
};

IEnumerable<IReadOnlyList<Monster>> expected = new[]
{
    new[]
    {
        new Monster(5, MonsterType.Grass),
        new Monster(16, MonsterType.Grass),
        new Monster(32, MonsterType.Grass),
    },
    new[]
    {
        new Monster(5, MonsterType.Fire),
        new Monster(55, MonsterType.Fire),
        new Monster(5, MonsterType.Water),
    },
    new[]
    {
        new Monster(36, MonsterType.Water),
    },
};

IEnumerable<IReadOnlyList<Monster>> actual = source.Buffer(3);

Assert.Equal(expected, actual);

Flatten

Converts sequence of sequence to sequence.

IEnumerable<IEnumerable<Monster>> source = new List<Monster[]>
{
    new[]
    {
        new Monster(5, MonsterType.Grass),
        new Monster(16, MonsterType.Grass),
        new Monster(32, MonsterType.Grass),
    },
    new[]
    {
        new Monster(5, MonsterType.Fire),
        new Monster(55, MonsterType.Fire),
    },
    new[]
    {
        new Monster(5, MonsterType.Water),
        new Monster(36, MonsterType.Water),
    },
};

IEnumerable<Monster> expected = new List<Monster>
{
    new Monster(5, MonsterType.Grass),
    new Monster(16, MonsterType.Grass),
    new Monster(32, MonsterType.Grass),
    new Monster(5, MonsterType.Fire),
    new Monster(55, MonsterType.Fire),
    new Monster(5, MonsterType.Water),
    new Monster(36, MonsterType.Water),
};

IEnumerable<Monster> actual = source.Flatten();

Assert.Equal(expected, actual);

IsEmpty

Determines whether sequence is empty or not.

IEnumerable<Monster> source = new List<Monster>
{
    new Monster(5, MonsterType.Grass),
    new Monster(16, MonsterType.Grass),
    new Monster(32, MonsterType.Grass),
    new Monster(5, MonsterType.Fire),
    new Monster(55, MonsterType.Fire),
    new Monster(5, MonsterType.Water),
    new Monster(36, MonsterType.Water),
};

Assert.False(source.IsEmpty());

Zip

Creates ValueTuple(First, Second) to the corresponding elements of two sequences, produces a sequence of the ValueTuple(First, Second).

IEnumerable<Monster> sourceMonsters = new List<Monster>
{
    new Monster(5, MonsterType.Grass),
    new Monster(16, MonsterType.Grass),
    new Monster(32, MonsterType.Grass),
    new Monster(5, MonsterType.Fire),
    new Monster(55, MonsterType.Fire),
    new Monster(5, MonsterType.Water),
    new Monster(36, MonsterType.Water),
};

IEnumerable<Position> sourcePositions = new List<Position>
{
    new Position(0, 1),
    new Position(1, 3),
    new Position(4, -1),
};

IEnumerable<(Monster First, Position Second)> actual = sourceMonsters.Zip(sourcePositions);

IEnumerable<(Monster First, Position Second)> expected = new List<(Monster First, Position Second)>
{
    (new Monster(5, MonsterType.Grass), new Position(0, 1)),
    (new Monster(16, MonsterType.Grass), new Position(1, 3)),
    (new Monster(32, MonsterType.Grass), new Position(4, -1)),
};

Assert.Equal(expected, actual);

WithIndex

Creates ValueTuple(Element, Index) to the corresponding element and its index, produces sequence of the ValueTuple(Element, Index).

IEnumerable<Monster> source = new List<Monster>
{
    new Monster(5, MonsterType.Grass),
    new Monster(16, MonsterType.Grass),
    new Monster(32, MonsterType.Grass),
    new Monster(5, MonsterType.Fire),
    new Monster(55, MonsterType.Fire),
    new Monster(5, MonsterType.Water),
    new Monster(36, MonsterType.Water),
};

IEnumerable<(Monster Element, int Index)> actual = source.WithIndex();

IEnumerable<(Monster Element, int Index)> expected = new List<ValueTuple<Monster, int>>
{
    ValueTuple.Create(new Monster(5, MonsterType.Grass), 0),
    ValueTuple.Create(new Monster(16, MonsterType.Grass), 1),
    ValueTuple.Create(new Monster(32, MonsterType.Grass), 2),
    ValueTuple.Create(new Monster(5, MonsterType.Fire), 3),
    ValueTuple.Create(new Monster(55, MonsterType.Fire), 4),
    ValueTuple.Create(new Monster(5, MonsterType.Water), 5),
    ValueTuple.Create(new Monster(36, MonsterType.Water), 6),
};

Assert.Equal(expected, actual);

Scan

Generates a accumulated values sequence with scanning the source sequence and applying the accumulator function.

IEnumerable<Direction> directions = new[]
{
    Direction.Down,
    Direction.Down,
    Direction.Left,
    Direction.Up,
    Direction.Right,
    Direction.Up
};

Position seed = new Position(0, 0);

IEnumerable<Position> actual = directions.Scan(seed, Position.Move);
IEnumerable<Position> expected = new[]
{
    new Position(0, -1),
    new Position(0, -2),
    new Position(-1, -2),
    new Position(-1, -1),
    new Position(0, -1),
    new Position(0, 0),
};

Assert.Equal(expected, actual);

Partition

Partitions a sequence to Value(True, False) with a predicate, True is satisfied and False is not.

IEnumerable<Monster> source = new List<Monster>
{
    new Monster(5, MonsterType.Grass),
    new Monster(16, MonsterType.Grass),
    new Monster(32, MonsterType.Grass),
    new Monster(5, MonsterType.Fire),
    new Monster(55, MonsterType.Fire),
    new Monster(5, MonsterType.Water),
    new Monster(36, MonsterType.Water),
};

IEnumerable<Monster> expectedOver30Level = new List<Monster>
{
    new Monster(32, MonsterType.Grass),
    new Monster(55, MonsterType.Fire),
    new Monster(36, MonsterType.Water),
};

IEnumerable<Monster> expectedUnderOrEqual30Level = new List<Monster>
{
    new Monster(5, MonsterType.Grass),
    new Monster(16, MonsterType.Grass),
    new Monster(5, MonsterType.Fire),
    new Monster(5, MonsterType.Water),
};

(IReadOnlyCollection<Monster> actualOver30Level, IReadOnlyCollection<Monster> actualUnderOrEqual30Level)
    = source.Partition(it => it.Level > 30);

Assert.Equal(expectedOver30Level, actualOver30Level);
Assert.Equal(expectedUnderOrEqual30Level, actualUnderOrEqual30Level);

CountBy

Creates a Dictionary<TKey, int> from an source sequence according to a specified key selector function and exist counts in source sequence.

IEnumerable<Monster> source = new List<Monster>
{
    new Monster(5, MonsterType.Grass),
    new Monster(16, MonsterType.Grass),
    new Monster(32, MonsterType.Grass),
    new Monster(5, MonsterType.Fire),
    new Monster(55, MonsterType.Fire),
    new Monster(5, MonsterType.Water),
    new Monster(36, MonsterType.Water),
};

IReadOnlyDictionary<MonsterType, int> expected = new Dictionary<MonsterType, int>
{
    {MonsterType.Grass, 3},
    {MonsterType.Fire, 2},
    {MonsterType.Water, 2},
};

IReadOnlyDictionary<MonsterType, int> actual = source.CountBy(it => it.MonsterType);

Assert.Equal(expected, actual);

How to Contribute

If you find bug or problem, please create issue in English.

If you have feature request, please create issue with link to other programming language collection method's document in English.

Author

Ryota Murohoshi is game Programmer in Japan.

License

This library is under MIT License.

About

ImportedLinq provides utility collection methods that are imported from other programming languages.

License:MIT License


Languages

Language:C# 100.0%