Looking for LINQ expression for foreach loop - c#

I have 2 device classes,
public class Device1
{
public string DeviceName { get; set; }
public string IP { get; set; }
public bool IsExist { get; set; }
}
public class Device2
{
public string DeviceName { get; set; }
public string DeviceIP { get; set; }
}
The current value for "IsExist" is "false" for "Device1[]" array,
private static Device1[] GetDevice1Arr()
{
List<Device1> d1List = new List<Device1>() {
new Device1 { DeviceName="d1", IP="1", IsExist=false},
new Device1 { DeviceName="d2", IP="1", IsExist=false}
};
return d1List.ToArray();
}
Now "Device2[]" array don't having "IsExist",
private static Device2[] GetDevice2Arr()
{
List<Device2> d2List = new List<Device2>() {
new Device2 { DeviceName="d1", DeviceIP="3"},
new Device2 { DeviceName="d2", DeviceIP="1"},
new Device2 { DeviceName="d2", DeviceIP="2"},
new Device2 { DeviceName="d3", DeviceIP="3"}
};
return d2List.ToArray();
}
Now I am comparing both array "Device1[]" and "Device2[]" by using 2 "foreach" loop, if DeviceName and DeviceIP is same, I am resetting "IsExist" = "true".
Looking for LINQ replacement here or any alternate way. Thanks!
Device1[] d1 = GetDevice1Arr();
Device2[] d2 = GetDevice2Arr();
foreach(var device1 in d1)
{
foreach(var device2 in d2)
{
if(device2.DeviceName == device1.DeviceName && device2.DeviceIP == device1.IP)
{
device1.IsExist = true;
}
}
}

You can replace the inner foreach loop with Linq, but not the outer one since Linq is for querying not updating. What you have is essentially an Any query (does any item in d2 match this condition?):
Device1[] d1 = GetDevice1Arr();
Device2[] d2 = GetDevice2Arr();
foreach(var device1 in d1)
{
device1.IsExist = d2.Any(device2 =>
device2.DeviceName == device1.DeviceName
&& device2.DeviceIP == device1.IP));
}
There may be alternate ways using Intersect, Join, Where, etc. to find the items that need to be updated, but in the end a foreach loop is the proper way to update them.

Looks like you're trying to do a join. You can do that in LINQ, but you'll still need a foreach to update IsExist on the result:
var itemsToUpdate = from d1 in GetDevice1Arr()
join d2 in GetDevice2Arr()
on new { d1.DeviceName, d1.IP }
equals new { d2.DeviceName, IP = d2.DeviceIP }
select d1;
foreach(var d1 in itemsToUpdate)
d1.IsExist = true;

One liner
d1.Where(dev1=> d2.Any(dev2=>dev2.DeviceName == dev1.DeviceName &&
dev2.DeviceIP == dev1.IP))
.ToList()
.ForEach(dev1=>dev1.IsExist = true);
Final Output
d1.Dump(); //LinqPad feature

Since both are list(type casted to array), you can use List.ForEach to iterate over first list, and Any to iterate over inner list.
d1.ForEach( d=> d.IsExist = d2.Any(x => x.DeviceIP == d.IP && x.DeviceName == d.DeviceName);
This one, and all other solutions use two level iterations, and are just shorthands to your existing solution. You cannot get away with it.

Another suggestion with using a join, but as an emulated 'left' join to get true or false:
Device1[] d1 = GetDevice1Arr();
Device2[] d2 = GetDevice2Arr();
foreach(var d in from dev1 in d1
join dd in d2 on new {dev1.DeviceName, dev1.IP} equals new {dd.DeviceName, IP = dd.DeviceIP} into d3
select new {dev1, Exists = d3.Any()})
d.dev1.IsExist= d.Exists;

d1.Where(x => d2.Any(y => x.IsExist = (x.DeviceName == y.DeviceName && x.IP == y.DeviceIP))).ToList();

Related

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);
}

Get string from another string array if value matches

String Array 1: (In this format: <MENU>|<Not Served?>|<Alternate item served>)
Burger|True|Sandwich
Pizza|True|Hot Dog
String Array 2: (Contains Menu)
Burger
Pizza
Grill Chicken
Pasta
I need the menu is served or any alternate item served for that particular item.
Code:
for(int i = 0; i < strArr2.Length; i++)
{
if(strArr2.Any(_r => _r.Split('|').Any(_rS => _rS.Contains(strArr1[i]))))
{
var menu = strArr2[i];
var alternate = ? // need to get alternate item
}
}
As I commented in the code, how to get the alternate item in that string array? Please help, thanks in advance.
P.S: Any help to trim if condition is also gladly welcome.
Instead of any, you may use Where to get the value matching.
#Markus is having the detailed answer, I am just using your code to find a quick fix for you.
for(int i = 0; i < strArr2.Length; i++)
{
if(strArr2.Any(_r => _r.Split('|').Any(_rS => _rS.Contains(strArr1[i]))))
{
var menu = strArr2[i];
var alternate = strArr2.Where(_rs => _rs.Split('|').Any(_rS => _rS.Contains(strArr1[i]))).First().Split('|').Last();
}
}
In order to simplify your code, it is a good idea to better separate the tasks. For instance, it will be much easier to handle the contents of string array 1 after you have converted the contents into objects, e.g.
class NotServedMenu
{
public string Menu { get; set; }
public bool NotServed { get; set; }
public string AlternateMenu { get; set; }
}
Instead of having an array of strings, you can read the strings to a list first:
private IEnumerable<NotServedMenu> NotServedMenusFromStrings(IEnumerable<string> strings)
{
return (from x in strings select ParseNotServedMenuFromString(x)).ToArray();
}
private NotServedMenu ParseNotServedMenuFromString(string str)
{
var parts = str.Split('|');
// Validate
if (parts.Length != 3)
throw new ArgumentException(string.Format("Unable to parse \"{0}\" to an object of type {1}", str, typeof(NotServedMenu).FullName));
bool notServedVal;
if (!bool.TryParse(parts[1], out notServedVal))
throw new ArgumentException(string.Format("Unable to read bool value from \"{0}\" in string \"{1}\".", parts[1], str));
// Create object
return new NotServedMenu() { Menu = parts[0],
NotServed = notServedVal,
AlternateMenu = parts[2] };
}
Once you can use the objects, the subsequent code will be much cleaner to read:
var notServedMenusStr = new[]
{
"Burger|True|Sandwich",
"Pizza|True|Hot Dog"
};
var notServedMenus = NotServedMenusFromStrings(notServedMenusStr);
var menus = new[]
{
"Burger",
"Pizza",
"Grill Chicken",
"Pasta"
};
var alternateMenus = (from m in menus join n in notServedMenus on m equals n.Menu select n);
foreach(var m in alternateMenus)
Console.WriteLine("{0}, {1}, {2}", m.Menu, m.NotServed, m.AlternateMenu);
In this sample, I've used a Linq join to find the matching items.
You could do something like that
string[] strArr1 = { "Burger|True|Sandwich", "Pizza|True|Hot Dog" };
string[] strArr2 = { "Burger", "Pizza", "Grill Chicken", "Pasta" };
foreach (string str2 in strArr2)
{
string str1 = strArr1.FirstOrDefault(str => str.Contains(str2));
if (str1 != null)
{
string[] splited = str1.Split('|');
string first = splited[0];
bool condition = Convert.ToBoolean(splited[1]);
string second = splited[2];
}
}

Union Lists using IEqualityComparer

I'we got two Lists of my class Nomen:
var N1 = new List<Nomen>();
var N2 = new List<Nomen>();
public class Nomen
{
public string Id;
public string NomenCode;
...
public string ProducerName;
public decimal? minPrice;
}
I need to join them. I used to do it like this:
result = N2.Union(N1, new NomenComparer()).ToList();
class NomenComparer : IEqualityComparer<Nomen>
{
public bool Equals(Nomen x, Nomen y)
{
return x.Equals(y);
}
public int GetHashCode(Nomen nomen)
{
return nomen.GetHashCode();
}
}
public override int GetHashCode()
{
return (Id + NomenCode + ProducerName).GetHashCode();
}
public bool Equals(Nomen n)
{
if (!String.IsNullOrEmpty(Id) && Id == n.Id) return true;
return (NomenCode == n.NomenCode && ProducerName == n.ProducerName);
}
As you can see, if Ids or NomenCode and ProducerName are equal, for me it's the same Nomen.
now my task have changed and I need to take, if they equal, the one with less minPrice. Please, help me to solve this problem.
Tried to do the same with linq, but failed
var groups = (from n1 in N1
join n2 in N2
on new { n1.Id, n1.NomenCode, n1.ProducerName } equals new { n2.Id, n2.NomenCode, n2.ProducerName }
group new { n1, n2 } by new { n1.Id, n1.NomenCode, n1.ProducerName } into q
select new Nomen()
{
NomenCode = q.Key.NomenCode,
ProducerName = q.Key.ProducerName,
minPrice = q.Min(item => item.minPrice)
}).ToList();
Mostly because I need to join Lists by Ids OR {NomenCode, ProducerName} and I don't know how to do it.
Concat, GroupBy and then Select again? for example (less untested than before):
var nomens = N1.Concat(N2)
.GroupBy(n=>n, new NomenComparer())
.Select(group=>group.Aggregate( (min,n) => min == null || (n.minPrice ?? Decimal.MaxValue) < min.minPrice ? n : min));
Linq joins with OR conditions have been answered in this SO post:
Linq - left join on multiple (OR) conditions
In short, as Jon Skeet explains in that post, you should do something like
from a in tablea
from b in tableb
where a.col1 == b.col1 || a.col2 == b.col2
select ...

Multiple values for single key

I have a list that contains Categories and their accept/reject counts but there is a problem with this list. I'm using a LINQ query to access the data, and I grouped them by both category name and Accept/Reject Code(ResultCode). So the data is in this form:
Almost all of the Categories have both AP counts and RJ counts. And what I'm trying to do is to show each Category's accept and reject count. What should I use? Hashtables don't fit in this problem, I tried Dictionary with int List as value but couldn't add when the same key appeared.
UPDATE:
List<ModReportingDM.ReportObjects.AllCategories> allcats = new List<ModReportingDM.ReportObjects.AllCategories>();
Dictionary<string, ModReportingDM.ReportObjects.ResultCode> dict = new Dictionary<string, ModReportingDM.ReportObjects.ResultCode>();
ModReportingDM.ReportObjects.ResultCode x = new ModReportingDM.ReportObjects.ResultCode();
allcats = reportBLL.GetAllCats(model.ModId, model.ReportStartDate, model.ReportEndDate);
if (allcats != null)
{
model.AllCatsList = new List<ModReportingDM.ReportObjects.AllCategories>();
foreach (var item in allcats)
{
x.Accepted = item.Count;
x.Rejected = item.Count;
dict.Add(item.Category, x);
}
}
Query:
public List<AllCategories> GetAllCats(int modId, DateTime startDate, DateTime endDate)
{
using (entities = new ModReportingEntities())
{
var query = (from c in entities.Content
where c.ModId == modId && c.CreatedTime >= startDate && c.CreatedTime <= endDate && c.Category != null
group c by new { c.Category, c.ResultCode } into g
orderby g.Count() ascending
select new AllCategories
{
Category = g.Key.Category,
ResultCode = g.Key.ResultCode,
AcceptCount = g.Count(),
RejectCount = g.Count()
});
return query.ToList();
}
}
What i would do is create a ResultCode class:
public class ResultCode
{
public int Ap { get; set; }
public int Rj { get; set; }
}
and then use a Dictionary<string, ResultCode> which maps each category to its report.
You could also take a different approach using a Tuple<T1, T2> (which personally i like less) which simply maps your key to two distinct values:
Dictionary<string, Tuple<int, int>> categoryToResultCode;
List<Tuple<string, string, int>> listOfTuples = new List<Tuple<string, string, int>>();
Tuple<string, string, int> tupleItem1 = new Tuple<string, string, int>("A", "AP", 1);
listOfTuples.Add(tupleItem1);
You can use Tuple. Please refer http://msdn.microsoft.com/en-us/library/system.tuple%28v=vs.110%29.aspx
My colleague and I realized that we can keep track of the Category's and if same Category occurs, it means that only a field of it should be changed(either AcceptCount or RejectCount). So we've created a lambda expression like this:
foreach(var item in allcats)
{
if (model.AllCategories.Select(m => m).Where(x => x.Category == item.Category).ToList().Count == 0)
{
if (item.ResultCode == "AP") {
model.AllCategories.Add(new ModReportingDM.ReportObjects.AllCategories()
{
Category = item.Category,
AcceptCount = item.Count
});
}
else
{
model.AllCategories.Add(new ModReportingDM.ReportObjects.AllCategories()
{
Category = item.Category,
RejectCount = item.Count
});
}
}
else
{
ModReportingDM.ReportObjects.AllCategories x = model.AllCategories.Select(n => n).Where(y => y.Category == item.Category).ToList().First();
if (item.ResultCode == "AP")
{
x.AcceptCount = item.Count;
}
else
{
x.RejectCount = item.Count;
}
}
}
If same Category occurs, go ahead and change its AcceptCount or RejectCount accordingly. That's the way I solved the problem.

Linq to objects - Search collection with value from other collection

I've tried to search SO for solutions and questions that could be similar to my case.
I got 2 collections of objects:
public class BRSDocument
{
public string IdentifierValue { get; set;}
}
public class BRSMetadata
{
public string Value { get; set;}
}
I fill the list from my datalayer:
List<BRSDocument> colBRSDocuments = Common.Instance.GetBRSDocuments();
List<BRSMetadata> colBRSMetadata = Common.Instance.GetMessageBRSMetadata();
I now want to find that one object in colBRSDocuments where x.IdentifierValue is equal to the one object in colBRSMetadata y.Value. I just need to find the BRSDocument that matches a value from the BRSMetadata objects.
I used a ordinary foreach loop and a simple linq search to find the data and break when the value is found. I'm wondering if the search can be done completely with linq?
foreach (var item in colBRSMetadata)
{
BRSDocument res = colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value);
if (res != null)
{
//Do work
break;
}
}
Hope that some of you guys can push me in the right direction...
Why not do a join?
var docs = from d in colBRSDocuments
join m in colBRSMetadata on d.IdentiferValue equals m.Value
select d;
If there's only meant to be one then you can do:
var doc = docs.Single(); // will throw if there is not exactly one element
If you want to return both objects, then you can do the following:
var docsAndData = from d in colBRSDocuments
join m in colBRSMetadata on d.IdentiferValue equals m.Value
select new
{
Doc = d,
Data = m
};
then you can access like:
foreach (var dd in docsAndData)
{
// dd.Doc
// dd.Data
}
Use Linq ?
Something like this should do the job :
foreach (var res in colBRSMetadata.Select(item => colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value)).Where(res => res != null))
{
//Do work
break;
}
If you are just interested by the first item, then the code would be :
var brsDocument = colBRSMetadata.Select(item => colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value)).FirstOrDefault(res => res != null);
if (brsDocument != null)
//Do Stuff

Categories

Resources