Linq query with All rule in array - c#

I have a list of images and I want to search for multiple keywords with a BOTH rules
For example if I search for "dancing child" I want to show a list of items with both keywords dancing and child
I implemented a query something like this:
List<string> target_keywords = //an array contains Keywords to Lookup
var RuleAny_results = (from imageItem in images
select new{ imageItem,
Rank =target_keywords.Any(x => imageItem.Title != null && imageItem.Title.ToLower().Contains(x)) ? 5 :
target_keywords.Any(x => imageItem.Name != null && imageItem.Name.ToLower().Contains(x)) ? 4 :
0
}).OrderByDescending(i => i.Rank);
//exclude results with no match (ie rank=0 ) and get a Distinct set of items
_searchResult = (from item in RuleAny_results
where item.Rank != 0
select item.imageItem).Distinct().ToList();
But this will return results with any of the items in the target_keywords, e.g. if I search for "dancing child" above code returns list of items with any of the keywords dancing or child. But I want the list with Both dancing and child keywords only
So how can I convert the query so that it fetch all records that contains BOTH keywords?

System.Linq.Enumerable::All is what you want.
using System.Linq;
using System.Collections.Generic;
struct ImageItem {
public string Title { get; set; }
public string Name { get; set; }
}
bool Contains(string toSearch, string x) {
return toSearch != null && toSearch.ToLower().Contains(x);
}
IEnumerable<ImageItem> FilterItems(IEnumerable<string> targetKeywords, IEnumerable<ImageItem> items) {
return items.Where(item => targetKeywords.All(x => Contains(item.Name, x) || Contains(item.Title, x)));
}

Try this:--
you have to just replace Any keyword in syntax with All
And one more rank condition for all keyword in both fields
Replace target_keywords.Any( with target_keywords.All(
List<string> target_keywords = //an array contains Keywords to Lookup
var RuleAny_results = (from imageItem in images
select new{ imageItem,
Rank =target_keywords.Any(x => imageItem.Title != null && imageItem.Title.ToLower().Contains(x)) ? 5 :
target_keywords.All(x => imageItem.Name != null && imageItem.Name.ToLower().Contains(x)) ? 4 :
target_keywords.All(x => (imageItem.Name != null && imageItem.Name.ToLower().Contains(x)) || imageItem.Title != null && imageItem.Title.ToLower().Contains(x)) ? 3 :
0
}).OrderByDescending(i => i.Rank);
//exclude results with no match (ie rank=0 ) and get a Distinct set of items
_searchResult = (from item in RuleAny_results
where item.Rank != 0
select item.imageItem).Distinct().ToList();

class ImageDemo
{
public string Title { get; set; }
public string Name { get; set; }
}
static void TestCode()
{
List<string> target_keywords = new List<string>(){"dancing","child"};
List<ImageDemo> images = new List<ImageDemo>()
{
new ImageDemo{Title = "dancing"} ,
new ImageDemo{Name = "child"} ,
new ImageDemo{Title = "child", Name="dancing"} ,
new ImageDemo{Title = "dancing", Name="child"} ,
new ImageDemo{Name="dancing child"} ,
new ImageDemo{Title="dancing child"}
};
var searchFuncs = target_keywords.Select(x =>
{
Func<ImageDemo, bool> func = (img) =>
{
return (img.Title ?? string.Empty).Contains(x) || (img.Name ?? string.Empty).Contains(x);
};
return func;
});
IEnumerable<ImageDemo> result = images;
foreach (var func in searchFuncs)
{
result = result.Where(x => func(x));
}
foreach (var img in result)
{
Console.WriteLine(string.Format("Title:{0} Name:{1}", img.Title, img.Name));
}
}
is it the right code you want now?

Related

How to avoid using IF-Else and use inline if condition inside the .where() function?

q = q.Where(s =>
!matchingRecords.Contains(s.Id)
|| (s.SecId != null)
);
but the matchingrecords could be null or having 0 items in it since it's a list. So, in that case it would fail in the above code. I want to check this contains only if
the matching records is not null and have some elements else not.
One way is to put IF-Else block and repeat the code but I want to do it inline, how ?
So, if input conditions are:
matchingRecords is not null;
matchingRecords not empty (contains elements, .Count > 0);
no if-else usage allowed;
could it be done through ternary?
var list = matchingRecords?.Count > 0 ?
q.Where(s => !matchingRecords.Contains(s.Id) && s.SecId != null).ToList()
: new List<Record>();
matchingRecords? checks for null and .Count after checks for "not empty". If-else replaced with ternary, which would filter collection using Where or return new List<Record> on else case.
Sample:
class Program
{
private static List<int> matchingRecords; // It is null, we "forget" to initialize it
static void Main(string[] args)
{
var list = new List<Record>()
{
new Record { Id = 0, SecId ="Some SeqId" },
new Record { Id = 1, SecId = null },
new Record { Id = 2, SecId = "Another SeqId" },
};
var filteredRecords = FilterRecords(list);
}
static IEnumerable<Record> FilterRecords(IEnumerable<Record> q)
{
return matchingRecords?.Count > 0 ? // Checking for not null and not empty (if case)
q.Where(s => !matchingRecords.Contains(s.Id) && s.SecId != null)
: q; // else case
}
}
public class Record
{
public int Id { get; set; }
public string SecId { get; set; }
}
Not sure that properly reproduced your situation, so correct me if something is wrong.
q = q.Where(s => (matchingRecords != null && matchingRecords.Count > 0 &&
!matchingRecords.Contains(s.Id))
|| (s.SecId != null)
);
The condition matchingRecords != null && matchingRecords.Count > 0 will ensure !matchingRecords.Contains(s.Id) is executed only if matchingRecords has at least 1 record

Linq select Objects from List which have empty String

is it possible in Linq to select from IEnumerable of this object
public class Foo
{
public int Id { get; set; }
public string Type { get; set; }
}
where Type is "" ?
if I loop over the list with that
foreach (Foo f in dataFoos)
{
Console.WriteLine(f.Id + f.Type);
}
it looks like
1one
2
3three
I have tried
var emptyType0 = dataFoos.Where(f => f.Type.Length <= 1);
var emptyType1 = dataFoos.Where(f => f.Type == null || f.Type == "");
both did not return any result. Any hint on how to properly check if String values are empty ?
if I do that
var df = dataFoos.Where(f => String.IsNullOrWhiteSpace(f.Type));
foreach (Foo f in df)
{
Console.WriteLine(f.Id + f.Type);
}
var df1 = dataFoos.Where(f => !String.IsNullOrWhiteSpace(f.Type));
foreach (Foo f in df1)
{
Console.WriteLine(f.Id + f.Type);
}
the second loop does not return any value
I am using dotnetcore c#. Thanks for any hint
This should cover almost every type of null/blank/just whitespace
var emptyType1 = foos.Where(f => String.IsNullOrWhiteSpace(f.Type));
but more likely what you want to do is exclude those - not include them
var dataFoos = foos.Where(f => !String.IsNullOrWhiteSpace(f.Type));
foreach (Foo f in dataFoos)
{
Console.WriteLine(f.Id + f.Type);
}

Update list by another list (linq)

I have List of object of class "Data" that look like:
class Data
{
int code;
string name;
...
DateTime date_update;
}
and I have another list of class, like:
class RefCodes
{
int old_code;
int new_code;
string new_name;
DateTime date_update;
}
The list of "Data" contains like 1,000 objects.
The list of "RefCodes" contains like 30 objects.
I need to replace in list "Data",
the fields:
"code" to be with value of "new_code",
and the "name" to be with value of "new_name".
The replacement need to be only for the objects that their code exist in list "RefCodes".
by the query: if code in Data.code == RefCodes.old_code
How can I do it?
I think you're looking for this:
foreach (var rcodeObj in RefCode)
{
foreach(var obj in (Data.Where(t => t.code == rcodeObj.old_code)))
{
obj.code = rcodeObj.new_code;
obj.name = rcodeObj.new_name;
}
}
If you are using C#6 you could use linq to do something like this
var updatedData = data.Select(x => new Data
{
code = refCodes.FirstOrDefault(y => y.old_code == x.code)?.new_code ?? x.code,
name = refCodes.FirstOrDefault(y => y.old_code == x.code)?.new_name ?? x.name,
});
You can use the following code:
foreach (var x in DataList)
{
var itemRefCode = RefCodesList.FirstOrDefault(d => d.old_code == x.code);
if (itemRefCode != null)
{
x.code = itemRefCode.new_code;
x.name = itemRefCode.new_name;
}
}
You can iterate through each of the lists and update the values as follows. Here I am using some sample inputs as shown below. Note that I am considering the fields of the classes to be public, for simplicity:
List<Data> dataList = new List<Data>
{
new Data { code = 1, name = "A" },
new Data { code = 2, name = "B" },
new Data { code = 10, name = "C" },
};
List<RefCodes> refList = new List<RefCodes>
{
new RefCodes { old_code = 1, new_code = 11, new_name = "X" },
new RefCodes { old_code = 2, new_code = 22, new_name = "Y" }
};
Console.WriteLine("Before");
dataList.ForEach(data => Console.WriteLine(data.code + ": " + data.name));
Console.WriteLine("");
Here is the code to do the updating:
foreach (var refCodes in refList)
{
foreach (var data in dataList)
{
if (data.code == refCodes.old_code)
{
data.code = refCodes.new_code;
data.name = refCodes.new_name;
}
}
}
Console.WriteLine("After");
dataList.ForEach(data => Console.WriteLine(data.code + ": " + data.name));
Output:
Before
1: A
2: B
10: C
After
11: X
22: Y
10: C
Would this solve your problem:
public void Update( List<Data> data, List<RefCodes> refCodes )
{
List<RefCodes> differences = refCodes
.Where( r => data.Any( d => r.old_code == d.code ) )
.ToList();
differences.ForEach( ( RefCodes item ) =>
{
Data element = data.FirstOrDefault( d => d.code == item.old_code );
element.code = item.new_code;
element.name = item.new_name;
} );
}
What you need is a Left Outer Join.
For example,
IEnumerable<Data> query = from data in dataList
join refCode in refList on data.code equals refCode.old_code into joined
from subCode in joined.DefaultIfEmpty()
select new Data
{
code = subCode?.new_code ?? data.code,
name = subCode?.new_name ?? data.name,
date_update = subCode == null ? data.date_update : DateTime.Now
};
will return a sequence with the result you expect.
**Let say tempAllocationR is list 1 and tempAllocationV is List2 **
var tempAllocation = new List<Object>();
if (tempAllocationR.Count > 0 && tempAllocationV.Count > 0)
{
foreach (TempAllocation tv in tempAllocationV)
{
var rec = tempAllocationR.FirstOrDefault(tr => tr.TERR_ID == tv.TERR_ID && tr.TERR == tv.TERR && tr.Team == tv.Team);
if (rec != null)
{
rec.Vyzulta = tv.Vyzulta;
}
else
{
tempAllocationR.Add(tv);
}
}
tempAllocation = tempAllocationR;
}
else if (tempAllocationV.Count == 0 && tempAllocationR.Count > 0)
{
tempAllocation = tempAllocationR;
}
else if (tempAllocationR.Count == 0 && tempAllocationV.Count > 0)
{
tempAllocation = tempAllocationV;
}

Project attribute values

Not sure if this is possible. I have a subset of 'MarketInventory' nodes:
<MARKET_INVENTORY _Type="TotalSales" _MonthRangeType="Prior7To12Months" _Count="18"/>
<MARKET_INVENTORY _Type="TotalSales" _MonthRangeType="Prior4To6Months" _Count="6"/>
<MARKET_INVENTORY _Type="TotalSales" _MonthRangeType="Last3Months" _Count="11"/>
<MARKET_INVENTORY _Type="TotalSales" _TrendType="Stable"/>
filtered on the _Type="TotalSales" node.
I'm wondering if it's possible to project the _Count value attributes into this class:
public class MarketInventoryListing
{
public string Prior7To12Months { get; set; }
public string Prior4To6Months { get; set; }
public string LastThreeMonths { get; set; }
}
This is as far as I got:
var marketInventoryTotalListings = from totalListings in xe.Descendants("MARKET_INVENTORY")
where (string) totalListings.Attribute("_Type") == "TotalSales"
select new MarketInventoryListing()
{
Prior7To12Months =
(
from thing in totalListings.Descendants()
where (string)totalListings.Attribute("_MonthRangeType") == "Prior7To12Months"
select thing.Attribute("_Count").Value
)
};
It's not working for 2 reasons:
Select is returning IEnumerable where as you need a single string
All MARKET_INVENTORY elements are on the same level so "from thing in totalListings.Descendants()" is not returning anything. All these elements are siblings at this point.
I changed your code to address those issues and it works as below:
var marketInventoryTotalListings = (from totalListings in xe.Descendants("MARKET_INVENTORY")
where (string)totalListings.Attribute("_Type") == "TotalSales"
select new MarketInventoryListing()
{
Prior7To12Months =
(
from thing in totalListings.Parent.Descendants()
where (string)totalListings.Attribute("_MonthRangeType") == "Prior7To12Months"
select thing.Attribute("_Count").Value
).FirstOrDefault(),
}).FirstOrDefault();
If you are sure that there will be just one node for each, then you can use FirstOrDefault like this:-
var marketInventoryTotalListings = from totalListings in xe.Descendants("MARKET_INVENTORY")
where (string)totalListings.Attribute("_Type") == "TotalSales"
let prior712 = xe.Descendants("MARKET_INVENTORY")
.FirstOrDefault(x => (string)x.Attribute("_MonthRangeType") == "Prior7To12Months")
let prior46 = xe.Descendants("MARKET_INVENTORY")
.FirstOrDefault(x => (string)x.Attribute("_MonthRangeType") == "Prior4To6Months")
let last3 = xe.Descendants("MARKET_INVENTORY")
.FirstOrDefault(x => (string)x.Attribute("_MonthRangeType") == "Last3Months")
select new MarketInventoryListing
{
Prior7To12Months = prior712 != null ? (string)prior712.Attribute("_Count") : "",
Prior4To6Months = prior712 != null ? (string)prior46.Attribute("_Count") : "",
LastThreeMonths = last3 != null ? (string)last3.Attribute("_Count") : "",
};
Otherwise if they are multiple then you should have IEnumerable<string> as the datatype in the properties of MarketInventoryListing instead of string.

Use linq to match up pairs of rows in a set

In the system I use modifications to data are received in pairs of rows old and new with a RowMod flag, for example deleted, added, updated and unchanged rows come through as:
RowID Data RowMod
Row1 "fish" ""
Row1 "fish" "D"
Row2 "cat" "A"
Row3 "fox" ""
Row3 "dog" "U"
Row4 "mouse" ""
I'd like to match these up using the RowID that each row has and get something like:
RowID OldData NewData RowMod
Row1 "fish" null "D"
Row2 null "cat" "A"
Row3 "fox" "dog" "U"
Row4 "mouse" "mouse" ""
class Program
{
static void Main(string[] args)
{
IEnumerable<DataRow> rows = new[]
{
new DataRow(1,"fish",""),
new DataRow(1,"fish","D"),
new DataRow(2,"cat","A"),
new DataRow(3,"fox",""),
new DataRow(3,"dog","U"),
new DataRow(4,"mouse","")
};
var result = rows
.GroupBy(x => x.Id)
.Select(g => new
{
Count = g.Count(),
Id = g.First().Id,
FirstRow = g.First(),
LastRow = g.Last()
}).Select(item => new
{
RowId = item.Id,
OldData = item.Count == 1 && item.FirstRow.RowMod != "" ? null : item.FirstRow.Data,
NewData = item.LastRow.RowMod == "D" ? null : item.LastRow.Data,
RowMod = item.LastRow.RowMod
});
//Or using query syntax
var result2 = from x in rows
orderby x.Id, x.RowMod
group x by x.Id into g
select new
{
RowId = g.First().Id,
OldData = g.Count() == 1 && g.First().RowMod != "" ? null : g.First().Data,
NewData = g.Last().RowMod == "D" ? null : g.Last().Data,
RowMod = g.Last().RowMod
};
// Test
Console.WriteLine("RowID\tOldData\tNewData\tRowMod");
foreach (var item in result)
{
Console.WriteLine("{0}\t'{1}'\t'{2}'\t'{3}'",item.RowId,item.OldData ?? "null",item.NewData ?? "null",item.RowMod);
}
}
}
public class DataRow
{
public int Id { get; set; }
public string Data { get; set; }
public string RowMod { get; set; }
public DataRow(int id, string data, string rowMod)
{
Id = id;
Data = data;
RowMod = rowMod;
}
}
Output:
RowID OldData NewData RowMod
1 'fish' 'null' 'D'
2 'null' 'cat' 'A'
3 'fox' 'dog' 'U'
4 'mouse' 'mouse' ''
I am not sure if this is the best way to achieve your requirement but this is what I have:-
var result = rows.GroupBy(x => x.RowId)
.Select(x =>
{
var firstData = x.FirstOrDefault();
var secondData = x.Count() == 1 ? x.First().RowMod == "A" ? firstData : null
: x.Skip(1).FirstOrDefault();
return new
{
RowId = x.Key,
OldData = firstData.RowMod == "A" ? null : firstData.Data,
NewData = secondData != null ? secondData.Data : null,
RowMod = String.IsNullOrEmpty(firstData.RowMod) && secondData != null ?
secondData.RowMod : firstData.RowMod
};
});
Working Fiddle.
Getting the two parts of the intended object can be done iteratively:
foreach(var rowId in myList.Select(x => x.RowId).Distinct())
{
//get the left item
var leftItem = myList.SingleOrDefault(x => x.RowId == rowId && String.IsNullOrWhiteSpace(x.rowmod);
//get the right item
var rightItem = myList.SingleOrDefault(x => x.RowId == rowId && !String.IsNullOrWhiteSpace(x.rowmod);
}
Your question doesn't specify how you create the second object. Is it a different class?
Either way, you can extrapolate from the above snippet that either item might be null if it doesn't exist in the original set.
All you need to do is use those found objects to create your new object.
While I love LINQ a lot, I don't think it is appropriate here as you want to buffer some values while iterating. If you do this with LINQ, it will be at best not performing well, at worst it will iterate the collection multiple times. It also looks way cleaner this way in my opinion.
IEnumerable<TargetClass> MapOldValues(IEnumerable<SourceClass> source)
{
var buffer = new Dictionary<string, string>();
foreach(var item in source)
{
string oldValue;
buffer.TryGetValue(item.RowId, out oldValue);
yield return new TargetClass
{
RowId = item.RowId,
OldData = oldValue,
NewData = (item.RowMod == "D" ? null : item.Data),
RowMod = item.RowMod };
// if the rows come sorted by ID, you can clear old values from
// the buffer to save memory at this point:
// if(oldValue == null) { buffer.Clear(); }
buffer[item.RowId] = item.Data;
}
}
if you then only want the latest updates, you can go with LINQ:
var latestChanges = MapOldValues(source).GroupBy(x => x.RowId).Select(x => x.Last());
I guess there are more elegant ways to do it, but this produces the output you expect:
public class MyClass
{
public int RowID { get; set; }
public string Data { get; set; }
public string RowMod { get; set; }
}
var result = (from id in myList.Select(x => x.RowID).Distinct()
let oldData = myList.Where(x => x.RowID == id).SingleOrDefault(x => x.RowMod.Equals("")) != null
? myList.Where(x => x.RowID == id).Single(x => x.RowMod.Equals("")).Data
: null
let newData = myList.Where(x => x.RowID == id).SingleOrDefault(x => !x.RowMod.Equals("")) != null
? myList.Where(x => x.RowID == id).Single(x => !x.RowMod.Equals("")).Data
: null
let rowMod = myList.Where(x => x.RowID == id).SingleOrDefault(x => !x.RowMod.Equals("")) != null
? myList.Where(x => x.RowID == id).Single(x => !x.RowMod.Equals("")).RowMod
: null
select new
{
RowID = id,
OldData = oldData,
NewData = rowMod == null ? oldData : rowMod.Equals("D") ? null : newData,
RowMod = rowMod
});
foreach (var item in result)
{
Console.WriteLine("{0} {1} {2} {3}", item.RowID, item.OldData ?? "null", item.NewData ?? "null", item.RowMod ?? "-");
}

Categories

Resources