-
Notifications
You must be signed in to change notification settings - Fork 0
Home
JLinq.ts(.js) is an implementation of .Net's Linq To Objects. It is built using iterators for complete lazy evaluation to reduce memory and improve performance.
I wanted to learn TypeScript along with building a linq library which had method names and syntax much like the .Net version. I love underscore but I always had to lookup the method name I was looking for.
- Implement's .Net Linq methods with the same syntax
- Complete lazy evaluation to reduce memory footprint while improving performance
- You can use the Typescript file for type safety or use the .js version for plain old Javascript.
- Contains async functionality to offload work off of the UI thread. Prevents freezing on long running methods.
- Drastically outperforms Underscore.js in Chrome, Firefox, and Safari
Setup
-
Import the JLinq.js file into your html page. Or bring JLinq.ts into solution if you are using TypeScript. These files are located in the following path of the project: 'Linq4Javascript/Scripts'
<script src="/Scripts/JLinq.js"></script>
-
For Node.js just use a require statement. This will allow access to the extension methods off of the array. Every array will have access to the methods of Linq4Javascript. JLinq is the file name where the Linq4Javascript code is in. If you change the file name then update this. The ./ is set because it is in the root folder of the project.
var linq = require('./JLinq');
Basic Example
//declare my array that will represent my data source
var _Array= [];
//let's throw some sample data into my array
for (var i = 0; i < 10; i++)
{
_Array.push({ id: i, txt: i.toString(), subArray: [1,2,3] });
}
//Let's run a sample where I filter any record where Id > 5. The return value will be a iterator.
var myQuery = _Array.Where(function (x) { return x.id > 5; });
How do I get my results from my query above?
-
Call .ToArray() which will evaluate the results right away and return an array from the where clause
var results = _Array.Where(function (x) { return x.id > 5; }).ToArray();
-
I could also run though the results 1 at a time. This would reduce memory because we never have to materialize a large array when calling ToArray().
//Holds the current item of the result set var CurrentResult; //Checks the query to see if we have any more records that are returned from the query while ((CurrentResult = QueryToRun.Next()).CurrentStatus !== ToracTechnologies.JLinq.IteratorStatus.Completed) { //do something with my result var myResultItem = CurrentResult.CurrentItem; }
Chaining Methods
You can chain query methods together. No data will be evaluated until ToArray() or the .Next() method is called.
This method when ToArray() or .Next() is called would return an array of ints. (id is an int off of my object in the collection)
var myQuery = _Array.Where(function (x) { return x.id > 5; }).Select(function(x){ return { x.id;});
Or I could split my queries such as the following:
//build the start of my query
var myQuery = _Array.Where(function (x) { return x.id > 5; })
//if i have some boolean i want to check.
if (OnlyTakeTop5Items)
{
//go sort the items and only take 5 items
var myResults= myQuery.OrderBy(function(x){ return x.id; }).Take(5).ToArray();
}
Methods
Below are the list of the rest of the methods we have in the library
-
Where(function(T): boolean): Query(T)
Description: Will only return items in the collection where the predicate evaluates to true.
Example:var myQuery = _Array.Where(function (x) { return x.id > 5; }).ToArray();
-
FirstOrDefault(function(T): boolean): Nullable(T)
Description: Returns the first item where the predicate evaluates to true. If no items evaluate to true then a null value will be returned.
Notes: You can call FirstOrDefault() (with no function) to get the first item (null if not found)
Example:var myFirstItem = _Array.FirstOrDefault(function (x) { return x.id == 5; });
-
First(function(T): boolean): T - (Error If T Not Found)
Description: Returns the first item where the predicate evaluates to true. If no item evaluates to true then an error is raised.
Notes: You can call First() (with no function) to get the first item (error if not found)
Example:var myFirstItem = _Array.First(function (x) { return x.id == 5; });
-
Select(function(T): any): Query(any)
Description: Returns the result of object after it has been passed into the function. Used to transform T.
Example:var myQuery = _Array.Select(function (x) { return { newId: x.id+1}; }).ToArray();
-
SelectMany(function(T): Array(TProperty) [Property Of Collection To Flatten]): Query(TProperty)
Description: This will select every subArray property. Mainly used to flatten out sub properties.
Example:Sample Data
Object 1 = { id: 1, lst: [1,2,3]};
Object 2 = { id: 2, lst: [4,5,6]};
Syntax
var myQuery = _Array.SelectMany(function (x) { return x.lst;}).ToArray();
Result Of Query
Results: [1,2,3,4,5,6]
-
Take(number): Query(T)
Description: Only returns the x number of elements where the predicate evaluates to true.
Example:var my2Items = _Array.Where(function (x) { return x.id > 5; }).Take(2).ToArray();
-
Skip(number): Query(T)
Description: Skips over the first x number of elements where the predicate evaluates to true.
Example:var myItems = _Array.Where(function (x) { return x.id > 5; }).Skip(2).ToArray();
-
Count(): integer
Description: Returns the number of elements.
Example:var countOfItems = _Array.Where(function (x) { return x.id > 5; }).Count();
-
Count(function(T): boolean): integer
Description: Returns the number of elements found where the predicate evaluates to true.
Example:var countOfItems = _Array.Count(function(x) { return x.id > 10;});
-
All(function(T): boolean): boolean
Description: If all the items evaluate to true then true will be returned.
Example:var myItems = _Array.All(function (x) { return x.id > 5; });
-
Any(function(T): boolean): boolean
Description: Returns true if 1 item evaluates to true.
Note: You don't have to pass in a function. This will return if the collection or query has at least 1 element.
Example:var hasItem = _Array.Where(function (x) { return x.id > 5; }).Any(function(x){ return x.id < 50; });
-
DefaultIfEmpty(DefaultValue: T): Query(T)
Description: Will return the DefaultValue passed in when no elements are found in the query. Example:var myQuery = _Array.Where(x => x.Id == 4 || x.Id == 5).DefaultIfEmpty({Id: 5, Txt: 'DefaultItem');
-
Last(function(T): boolean): T
Description: Returns the last item in the collection that evaluates to true.
Note: You don't have to pass in a function. This will just return the last item in the collection / query.
Example:var lastItem = _Array.Last(function(x){ return x.id < 9; });
-
Distinct(function(T) : TProperty(any)): Array(TProperty)
Description: Returns a list of the distinct values of the property passed in
Example:var distinctIds = _Array.Distinct(function(x){ return x.id; }).ToArray();
-
Min(): number
Description: Returns the min number in the collection.
Example:var minId = _Array.Select(function(x){ return x.id; }).Min();
-
Max(): number
Description: Returns the max number in the collection.
Example:var maxId = _Array.Select(function(x){ return x.id;}).Max();
-
Sum(): number
Description: Returns the sum of the numbers in the collection.
Example:var sumOfIds = _Array.Select(function(x){ return x.id; }).Sum();
-
Average(): number
Description: Returns the average of the numbers in the collection.
Example:var avgerageOfIds = _Array.Select(function(x){ return x.id; }).Average();
-
ElementAt(IndexToReturn: number): T
Description: Returns the element at the specified index. Will throw the following error if the index is greater then the size of the query result: 'ArgumentOutOfRangeException. The size of the collection is less then the index specified. There are only QueryResultLength' + ' elements in the query.'. Use ElementAtDefault if you don't want an error to be raised. Example:var elementAtIndex2 = _Array.Where(function (x) { return x.id > 5; }).ElementAt(2);
-
ElementAtDefault(IndexToReturn: number): Nullable(T)
Description: Returns the element at the specified index. Will return null if the index is greater then the size of the query. Example:`var elementAtIndex2 = _Array.Where(function (x) { return x.id > 5; }).ElementAtDefault(2);`
-
GroupBy(function(T): TGroupByField(any)) : Query(TGroupByField, T)
Description Returns an array of Keys with an array of items in that key.
Example:var groupedData = _Array.GroupBy(function(x){ return x.id; });
-
OrderBy(function(T): TSortByProperty(any)) : Query(T)
Description: Returns a sorted array by the property passed in.
Example:var sortedData = _Array.Where(function (x) { return x.id > 5; }).OrderBy(Function(x){ return x.id; });
-
OrderByDescending(function(T): TSortByProperty(any)) : Query(T)
Description: Returns a desc sorted array by the property passed in.
Example:var sortedData = _Array.Where(function (x) { return x.id > 5; }).OrderByDescending(Function(x) { return x.id; });
Multi Property Sorting
You can use multiple fields to sort (additional sort columns) by adding the
OrderBy(...).ThenBy(function(x){ return x.Id2;})
Or to secondary sort by desc then use:
OrderBy(...).ThenByDescending(function(x){ return x.Id2;})
You can keep tacking on additional sort columns:
OrderBy(...).ThenBy(function(x){ return x.Id2;}).ThenBy(function(x){return x.Id3;});
-
Paginate(function(T) CurrentPageNumber: number, HowManyRecordsPerPage: number): Array(T)
Description: Pages the data and only returns the current page of data. Example:var myPagedData = _Array.Paginate(1, 100);
-
SingleOrDefault(function(T): boolean): T?
Description: First Item If Predicate Found, If Multiple Items Are Found Then Error Is Raised. If Nothing Is Found Then Null Is Returned
Note: You can call SingleOrDefault() (with no function) to get the single item (error if not found)
Example:var myFirstItem = _Array.SingleOrDefault(function (x) { return x.id == 5; });
-
Single(function(T): boolean): T (multiple items found will raise an error)
Description: First Item If Predicate Found, If Multiple Items Are Found Then Error Is Raised. If Nothing Is Found Then Error Is Raised.
Note: You can call Single() (with no function) to get the single item (error if not found)
Example:var myFirstItem = _Array.Single(function (x) { return x.id == 5; });
-
Concat(function(T): Array(T) | Or Query(T): Query(T)
Description: Combines both sets of data into one dataset.
Note: Concat does not removes duplicate values
Example:var myConcatResult = _Array.Concat(_AnotherArray);
or Example 2:var myConcatResult = _Array.Where(x => x.Id == 1).Concat(_AnotherQuery);
-
Union(function(T): Array(T) | Query(T)): Query(T)
Description: Combines both sets of data into one dataset. Removes any duplicate items.
Example:var myUnionResult = _Array.Union(_AnotherArray);
or Example2:var myUnionResult = _Array.Where(x => x.Id == 1).Union(_AnotherQuery);
-
Join(OuterArray Or Query, function(I): InnerTablePropertySelector, function(O): OuterTablePropertySelector, function(I,O): Creates the join record where I is the inner table and O is the outer table. Description: Will join 2 datasets by a key and will create a new row based on the 2 records that match. Similar to a Sql inner join Example:
var mySkipWhileResult= _Array.Join(_OuterArray, x => x.Id, y => y.Id, (x,y) => { Id: x.Id, StateName: y.Description);
Note: Not supported in async mode yet.
-
GroupJoin(OuterArray Or Query, function(I): InnerTablePropertySelector, function(O): OuterTablePropertySelector, function(I,O[]): Creates the join record where I is the inner table and O is an arry of records from the outer table. Description: Will join 2 datasets by a key and will create a new row based on the 2 records that match. Example:
var mySkipWhileResult= _Array.GroupJoin(_OuterArray, x => x.Id, y => y.Id, (x,y) => { Id: x.Id, StateName: y.Description);
Note: Not supported in async mode yet.
-
SkipWhile(function(T): boolean): Query(T)
Description: Will Bypasses elements in a sequence as long as a specified condition is true and then returns the remaining elements. "Where" will return everything that meet the condition. SkipWhile will find the first element where the condition is met, and return the rest of the elements.
Example:var mySkipWhileResult= _Array.SkipWhile(function(x){ return x.id > 2; });
-
TakeWhile(function(T): boolean): Query(T)
Description: Will return all the elements before the test no longer passes. "Where" will return everything that meet the condition. TakeWhile will exit the routine wasn't it doesn't pass the expression
Example:var myTakeWhileResult= _Array.TakeWhile(function(x){ return x.id > 2; });
-
Aggregate(function(WorkingT: T, CurrentT: T): T): Query(T)
Description: creates a running total "T" then passes it in for each element
Example:
//datasource
var QueryToAggregate: Array<number> = [1, 2, 3, 4];
//go build the query
var ResultOfQuery = QueryToAggregate.Where(x => x > 2).Aggregate((WorkingT, CurrentT) => WorkingT + CurrentT);
AsQueryable
If I have a scenario where I might have a filter / may not. I can declare the array AsQueryable. Using AsQueryable() will simplify the syntax.
ie: var query = List.AsQueryable();
if (filter1 != null)
{
query = query.Where(function(x .....))
}
if (filter2 != null)
{
query = query.Where(function(x......))
}
//grab the results
var results = query.ToArray();
Async is used to offload any query off of the UI thread to prevent freezing and slowness on the site. The implementation uses Html 5 Web Workers. If a user is using an older browser, JLinq will fallback to the normal ToArray() method to complete the query results.
Note: In most cases the performance will be identical or slower. The goal is to offload the data not have a faster throughput. asynchronous vs parallelism
Syntax:
//build my query.
var query = UnitTestFramework._Array.Where(x => x.Id == 1 || x.Id == 2);
//let's to run this query in a web worker.
var runQuery = query.ToArrayAsync(function(result)
{
//do something with the result array
}, function(errorMessageObject){
//do something if there is an error. Web worker produces silent error without handler
}, 'http://MyWebSite/Scripts/JLinq.js');
ToArrayAsync Parameters:
-
SuccessCallBack: The callback that runs once the query has completed. The return parameter is the result of the query [an array of T]
-
ErrorCallBack: Web workers provide silent errors. Thus when an error is raised, we return whatever error information we can back to the main thread.
-
JLinqUrlPath: The web worker needs a reference to JLinq.js. So provide the path so we can run an importScripts to bring in JLinq.js
Notes: Web Workers can't touch the dom, create alerts, etc. Please check the limitations of web workers. So don't do anything weird like Array.Where(function(x) { x == 2 ? Alert('It Is 2');return true : return false; });
Dictionaries
Instead of calling .ToArray() you can push the results to a dictionary. The function passed into the ToDictionary is the dictionary key selector.
var myDictionary = _Array.Where(function (x) { return x.id > 5; }).ToDictionary(function(x){ return x.id;});
You can also use a composite key
var myDictionary = _Array.Where(function (x) { return x.id > 5; }).ToDictionary(function(x){ return { Id: x.Id, Id2: x.Id2;});
Methods
* ContainsKey(TKeyValue): boolean --> Does the key value exists in the dictionary
* Count(): integer --> How many items exist in the dictionary
* GetItem(TKeyValue): TValue --> Returns the value of the KeyValuePair if its exists. Returns null if we can't find the item in the dictionary.
* Keys(): Array(TKeyValue) --> Gets all the keys in the dictionary and returns them. Array Of Keys
* Values(): Array(TValue) --> Gets all the values in the dictionary and returns them. Array Of Values
* GetAllItems(): Array(TKey,TValue) -->Gets all the key / value pairs in the dictionary. Array Of TKey and TValue (Key value pair)
Hash Sets Instead of pushing things to an array or a dictionary you can push it to a hashset. This uses an object behind the scenes for fast item lookup.
Push the results to a hashset
_Array.Where(x => x.Id == 1).ToHashSet();
Methods
* ContainsItem(T): boolean --> Tries to find the key / object in the hashset
* Add(T): boolean --> Add an item to the hashset. Will return true if it was added. Returns false if it already exists in the hashset
* Values(): Array(T) --> Gets all the values in the hashset
* Remove(T) - Removes the item pased in
* BuildHashSet(HashSetDataSource: Iterator(TValue)) --> Builds a hashset from a Iterator, so if we have an iterator we don't need to materialize that AND a hashset and a key selector
* Count(): integer --> Gets the count of items in the hashset