I want to do a search for Music instruments which has its informations Name, Category and Origin as I asked in my post.
But now I want to sort/group the result by similarity/equality to the keyword such as.
If I have the list
{ Drum, Grand Piano, Guitar, GuitarrĂ³n, Harp, Piano} << sorted by name
and if I queried "p" the result should be { Piano, Grand Piano, Harp }
but it shows Harp first because of the source list's sequence
and if I add {Grand Piano} to the list and query "piano"
the result shoud be like { Piano, Grand Piano }
or query "guitar"
it should be { Guitar, GuitarrĂ³n }
here's my code
static IEnumerable<MInstrument> InstrumentsSearch(IEnumerable<MInstrument> InstrumentsList, string query, MInstrument.Category[] SelectedCategories, MInstrument.Origin[] SelectedOrigins)
{
var result = InstrumentsList
.Where(item => SelectedCategories.Contains(item.category))
.Where(item => SelectedOrigins.Contains(item.origin))
.Where(item =>
{
if (
(" " + item.Name.ToLower()).Contains(" " + query.ToLower())
|| item.Name.IndexOf(query) != -1
)
{
return true;
}
return false;
}
)
.Take(30);
return result.ToList<MInstrument>();
}
Or the result may be like my old self-invented algorithm that I called "by order of occurence",
that is just OK to me.
And the further things to do is I need to search the Name, Category or Origin such as.
If i type "Italy" it should found Piano or something from Italy.
Or if I type "string" it should found Guitar.
Is there any way to do those things, please tell me.
Thanks in advance.
You want OrderBy / OrderByDescending --
result = InstrumentsList.
.Where(...)
.OrderByDescending(instrument =>
StringSimilarityScore(instrument.Name, searchString))
.Take(30);
As to the definition of StringSimilarityScore -- a full-on fuzzy match would be best, but you could start by quantifying the match based on the proportion of the name matched by the search string:
double StringSimilarityScore(string name, string searchString)
{
if (name.Contains(searchString))
{
return (double)searchString.Length / (double)name.Length;
}
return 0;
}
You might then want to consider the position of the search string within the name (earlier is better), for the cases where a single letter is specified -- but I'll leave that up to you. :-)
Related
Suppose I have this table:
Image
Perimeter
a
1
b
1
b
2
d
3
e
1
I want to return the images that have relationship with only ONE perimeter.
The expected result would be images "a,d,e" because image "b" has relationship with perimeter "1" and "2".
The objective is to remove the releated image when I delete the perimeter. But if it is linked to another perimeter, I can't remove it.
How can I write this query with LINQ?
I think it would be something like this:
SELECT "ImageId"
WHERE "PerimeterId" = PerimeterId IN
(
SELECT "ImageId"
GROUP BY "ImageId"
HAVING COUNT("PerimeterId") = 1
)
but I don't know how to convert it to LINQ.
You could use a NOT EXISTS
var query = dbo.Table
.Where(t => !dbo.Table.Any(t2 => t.Image = t.Image && t.Perimeter != t2.Perimeter));
You can easily adapt this to only select the image part. But, if you are coming from SQL, thinking about "Selecting rows" based on a "HAVING()" group calculation, then you will want to look at the .SelectMany() LINQ method. This lets you "combine back together data partitioned into groups". While your needs are to only return "one from each group", it's easy to see where this can be adjusted.
This can be run in the "C# interactive window" of SSDT 2015:
struct imagePerimeter { //this might be whatever object type it is for you...
public string Image { get; set; } //a,b,b,d,e
public int Perimeter { get; set; } //1,1,2,3,1
}
Func<string, int, imagePerimeter> newIP = (i, p) => new imagePerimeter() { Image = i, Perimeter = p };
List<imagePerimeter> results = new List<imagePerimeter>() { {newIP("a",1) }
,{newIP("b",1) }
,{newIP("b",2) }
,{newIP("d",3) }
,{newIP("e",1) } };
Func<imagePerimeter, string> ipImage = (ip) => ip.Image; //the Func's "ipImage" and "newIP" could just be inlined into LINQ, but it helps to see and debug at times IMO.
var imagesWithOnePerimeter = results.GroupBy<imagePerimeter, string>(ipImage) //even in SQL, the "GROUP BY" conceptually comes first, in LINQ, it comes first in code too!
.Select(grp => new { Image = grp.Key, PerimeterCount = grp.Count(), Details = grp }) //there's probably a more technical term, but notice how we "carry forward" the original reference to [grp]
.Where(subTotals => subTotals.PerimeterCount == 1)
.SelectMany(filtered => filtered.Details.AsEnumerable())
.ToList();
How to convert a query to bool?
I used the "ALL (x => x)" but did not give the answer I needed.
Code Line
checkItemInventory.Where(x => listCost.Contains(x.Id));
In this case, the listcost would have 2 items, I needed to check if the checkItemInventory has these 2 items.
"All items in the inventory have an id that present in listcost". listCost needs to have the same number of items as inventory (assuming Id is unique) possibly more, to stand a chance of returning true
checkItemInventory.All(x => listCost.Contains(x.Id))
"At least one item in the inventory has an id that is also in listCost". Listcost could minimally have only one id in it, to stand a chance of returning true
checkItemInventory.Any(x => listCost.Contains(x.Id))
As you can see, neither of these are what you want as you seem to be saying you want to check whether every item in listcost is also present in the inventory. This is like the top code, but the other way round ("all items in listCost are present in inventory" vs "all items in inventory are present in listcost"
I think I'd make a dictionary out of the inventory first, unless it's already something that supports a fast lookup:
var d = checkItemInventory.Select(x => new { x.Id, x.Id }).ToDictionary();
var boolResult = listCost.All(lc => d.ContainsKey(lc));
If inventory is small, you could use this approach:
listCost.All(lc => checkItemInventory.Any(cii => cii.Id == lc));
Just be mindful that internally it might do something like:
bool all = true;
foreach(lc in listCost){
bool found = false;
foreach(cci in checkItemInventory)
if(lc == cci.Id){
found = true;
break;
}
all &= found;
if(!all)
return false;
}
return true;
Which is a lot of repeated comparisons (for every item in listCost, the whole inventory is scanned), could be slow
Edit
I asked for clarification of how you store your inventory and your costs of building items. Here's one assumption I made, and how a solutio based on it might work:
Assuming your inventory has the kind of item and a count saying how many of that item the player is carrying:
class InventoryItem{
int ItemKindId { get; set;}
int CountOf { get; set; }
}
player.Inventory.Add(new InventoryItem() {
ItemKindId = Constants.WOOD, //1
CountOf = 10 //holding 10 items of wood
};
player.Inventory.Add(new InventoryItem() {
ItemKindId = Constants.STONE, //2
CountOf = 5 //holding 5 items of stone
};
Assuming you have a Recipe for making e.g. an axe, it needs 1 wood and 2 stone, but it lists them in simple order:
int[] axeRecipe = new int[] { Constants.WOOD, Constants.STONE, Constants.STONE };
Might be easiest to group the recipe:
var recipe = axeRecipe.GroupBy(item => item)
/*
now we have a grouping of the recipe[item].Key as the material and a
recipe[item].Count() of how much. The group is like a dictionary:
recipe[Constants.WOOD] = new List<int>{ Constants.WOOD };
recipe[Constants.STONE] = new List<int>{ Constants.STONE, Constants.STONE, };
A group item has a Key and a list of objects that have that key
Because my recipe was simply ints, the Key is the same number as all the
items in the list
*/
//for all items in the recipe
grp.All(groupItem =>
//does the player inventory contain any item
playerInventory.Any(inventoryItem =>
//where the material kind is the same as the recipe key (material)
inventoryItem.ItemKindId == groupItem.Key &&
//and the count they have of it, is enough to make the recipe
inventoryItem.CountOf >= groupItem.Count()
);
You can of course reduce this to a single line if you want: axeRecipe.GroupBy(...).All(...)
You could map the listCost to a list of int and then use Except() and Any() to check whether all items are contained:
bool containsAll = !listCost.Select(x => x.Id).Except(checkItemInventory).Any();
[UPDATE]
You are telling us the following:
How to convert a query to bool? I used the "ALL (x => x)" but did not give the answer I needed.
checkItemInventory.Where(x => listCost.Contains(x.Id));
In this case, the listcost would have 2 items, I needed to check if
the checkItemInventory has these 2 items.
if you need to check if there is any result then you can use:
bool hasItems = checkItemInventory.Where(x => listCost.Contains(x.Id)).Any();
if you need to count the result you can use
checkItemInventory.Where(x => listCost.Contains(x.Id)).Count();
You could use a Join to create a method based Linq query and use the results to check if the length of the list is greater than 0. Then turn that into a boolean.
var query = checkItemInventory.Join(listCost,
inventory => inventory.Id,
cost => cost.Id,
(inventory, cost) => new { id = inventory.Id });
var count = query.ToList().Count();
var b = (count > 0);
If I get it correctly, listCost can have less elements than checkItemInventory. You want to check that all elements in listCost have a corresponding element in checkItemInventory. Correct? If yes, try this:
listCost.All(x => checkItemInventory.Contains(x));
I don't know the type of these lists, so you might need to use x.id in some places
I've researched and found that I use .Contains on the array to filter an entity, but my resulting list is empty what is wrong?
public List<Order> GetOrderstoShip()
{
var shipvia = new string[] { "UPS", "FED", "EX", "USP" };
var orders = db.Orders.Where(
x => x.VOID != "Y" && x.GONEDATE == "")
.ToList();
var exporders = orders.Where(
x => shipvia.Contains(x.SHIPVIA.ToUpper())
).ToList();
// Problem: exporders contains 0 items
// My database field contains the following:
// FEDEX, UPS, USPS, FEDEX GND, FEDEXGND
// And, I made sure orders has a list of 900+ items
return exporders;
}
New Development: I went into my DB and edited one record. I set the ShipVia field exaclty to UPS (previously it was UPS GND) and I got one item back in exporder.
That means the .Contains is exact match. So how do I get it to do something similar to the SQL Like?
I thought the whole point of using contains was that is does partial match.
Just a take a stab. You might have white space in your database table. You can try to trim the results first.
shipvia.Contains(x.SHIPVIA.Trim().ToUpper())
I would suggest doing this filter before the results are returned. This will speed up the database query time.
I had to use a work-around since I only had three strings to check for, I expanded my filter by using or... not the most elegant but it lets me continue with my project!
If anyone figures out why the .Contains is not working, please let me know...
public List<Order> GetOrderstoShip()
{
var shipvia = new string[] { "UPS", "FED", "EX", "USP" };
var orders = db.Orders.Where(
x => x.VOID != "Y" && x.GONEDATE == "" && (
x.SHIPVIA.Contains("UPS") ||
x.SHIPVIA.Contains("FED") ||
x.SHIPVIA.Contains("USP"))
).ToList();
var exporders = orders.Where(
x => shipvia.Contains(x.SHIPVIA.Trim().ToUpper())
).ToList();
// Problem: exporders contains 0 items
// My database field contains the following:
// FEDEX, UPS, USPS, FEDEX GND, FEDEXGND
// And, I made sure orders has a list of 900+ items
return orders;
}
I have an object (KS), which holds ID and Title (which has a number as part of the Title).
All I'm trying to do is sort it into descending order. The object has:
ID Title
1 1 Outlook VPN
2 2 Outlook Access
3 4 Access VBA
4 3 Excel Automation
So when order by Title, it should read:
ID Title
3 4 Access VBA
4 3 Excel Automation
2 2 Outlook Access
1 1 Outlook VPN
The code I'm using to sort it is:
IEnumerable<KS> query = results.OrderByDescending(x => x.Title);
However, query still has the objects in the original order!
Is there something to do with having numbers at the start of Title that I'm missing?
EDIT
I've added the code from the controller for clarity:
[HttpPost]
// [ValidateAntiForgeryToken]
// id is a string of words eg: "outlook access vpn"
// I split the words and want to check the Title to see how many words appear
// Then sort by the most words found
public JsonResult Lookup(string id)
{
List<string> listOfSearch = id.Split(' ').ToList();
var results = db.KS.Where(x => listOfSearch.Any(item => x.Title.Contains(item)));
// search each result, and count how many of the search words in id are found
// then add the count to the start of Title
foreach (KS result in results)
{
result.KSId = 0;
foreach (string li in listOfSearch)
{
if (result.Title.ToLower().Contains(li.ToLower()))
{
result.KSId += 1;
}
}
result.Title = result.KSId.ToString() + " " + result.Title;
}
// sort the results based on the Title - which has number of words at the start
IEnumerable<KS> query = results.OrderByDescending(x => x.Title).ToList();
return Json(query, JsonRequestBehavior.AllowGet);
}
Here is a screenshot after query has been populated showing Titles in the order: 1, 2, 1, 1:
Model for the object if it helps is:
public class KS
{
public int KSId { get; set; }
public string KSSol { get; set; }
public string Title { get; set; }
public string Fix { get; set; }
}
As I said in a comment, put a .ToList() where you declare your results variable. That is:
var results = db.KS.Where(x => listOfSearch.Any(item => x.Title.Contains(item)))
.ToList();
If you don't do that, the foreach loop will modify objects that might not be the same as the objects you sort later, because the database query is run again each time you enumerate your IQueryable<>.
You can always just ignore the strange behavior and go the safe way:
List<KS> query = results.ToList();
query.Sort((a, b) => a.Whatever.CompareTo(b.Whatever));
return Json(query, blah);
I simple did this and it worked for me :-
var sortedOrder = Query.OrderBy(b => b.Title.Substring(b.Title.IndexOf(" ")));
All I have done is SubString the Title at the index of of the blank space when ordering the objects in the sequence, that way, the OrderBy is looking at the first character in the title rather than the number at the beginning.
Old question, but maybe this will help someone using C#. I used the following expressions to sort a list of objects based on their quantity parameter in ascending or descending order. Can modify it to compare text as the original question was concerned with.
Ascending Order:
locationMaterials.Sort((x, y) => x.Quantity.CompareTo(y.Quantity));
Descending Order:
locationMaterials.Sort((x, y) => y.Quantity.CompareTo(x.Quantity));
You are missing .ToList()
IEnumerable<KS> query = results.OrderByDescending(x => x.Title).ToList();
results.OrderByDescending(x => x.Title) is a query, and it has no data.
ToList() forces the query to be executed.
[EDIT]
My answer assumes that your results has acually not been materialized, and that that is the source of your problem.
This used to work for me and then it failed. I want to return only those items that contain all the filters, not at least one filter as it is doing now. WHat is wrong here?
private IQueryable<IndexItem> FilteredIndex (IQueryable<IndexItem> index, IEnumerable<string> filters)
{
var filteredIndex= from f in filters.AsQueryable()
where f.Length>0
from i in index
where i.FilterNames.Contains(f)
select i;
return filteredIndex;
}
Straight forward. For a given item from index check that it is true for all filters that the given item contains the filter. With this just select all items from index for that the given condition is true.
index.Where(item =>
filters.All(filter => item.FilterNames.Contains(filter)))
I am not sure if the check for length greater than zero is required, nut it is easily integrated.
index.Where(item =>
filters.All(filter =>
(filter.Length > 0 ) || (item.FilterNames.Contains(filter))))
It works with LINQ to Objects and I guess it does what you want, but I am not sure if it works with LINQ to SQL.
How about something like:
foreach(string s in filters) {
if(s.Length == 0) continue;
string tmp = s; // because of "capture" problem
index = index.Where(i => i.FilterNames.Contains(tmp));
}
return index;
This applies a succession of Where clauses, covering all the filters - essentially AND.
Turn it around. What you want is those items in index where every item in FilterNames has a corresponding entry in filters. I'm not sure how performant it'd be, but a count comparison should do. Something like:
private IQueryable<IndexItem> FilteredIndex(IQueryable<IndexItem> index, IEnumerable<string> filter)
{
var filteredIndex = from i in index
where (from s in i.FilterNames
where filter.Contains(s)
select s).Count() == i.FilterNames.Count
select i;
return filteredIndex;
}