Linq select Objects from List which have empty String - c#

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

Related

Looking for LINQ expression for foreach loop

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

Linq query with All rule in array

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?

Merge two or more T in List<T> based on condition

I have the below class:
public class FactoryOrder
{
public string Text { get; set; }
public int OrderNo { get; set; }
}
and collection holding the list of FactoryOrders
List<FactoryOrder>()
here is the sample data
FactoryOrder("Apple",20)
FactoryOrder("Orange",21)
FactoryOrder("WaterMelon",42)
FactoryOrder("JackFruit",51)
FactoryOrder("Grapes",71)
FactoryOrder("mango",72)
FactoryOrder("Cherry",73)
My requirement is to merge the Text of FactoryOrders where orderNo are in sequence and retain the lower orderNo for the merged FactoryOrder
- so the resulting output will be
FactoryOrder("Apple Orange",20) //Merged Apple and Orange and retained Lower OrderNo 20
FactoryOrder("WaterMelon",42)
FactoryOrder("JackFruit",51)
FactoryOrder("Grapes mango Cherry",71)//Merged Grapes,Mango,cherry and retained Lower OrderNo 71
I am new to Linq so not sure how to go about this. Any help or pointers would be appreciated
As commented, if your logic depends on consecutive items so heavily LINQ is not the easiest appoach. Use a simple loop.
You could order them first with LINQ: orders.OrderBy(x => x.OrderNo )
var consecutiveOrdernoGroups = new List<List<FactoryOrder>> { new List<FactoryOrder>() };
FactoryOrder lastOrder = null;
foreach (FactoryOrder order in orders.OrderBy(o => o.OrderNo))
{
if (lastOrder == null || lastOrder.OrderNo == order.OrderNo - 1)
consecutiveOrdernoGroups.Last().Add(order);
else
consecutiveOrdernoGroups.Add(new List<FactoryOrder> { order });
lastOrder = order;
}
Now you just need to build the list of FactoryOrder with the joined names for every group. This is where LINQ and String.Join can come in handy:
orders = consecutiveOrdernoGroups
.Select(list => new FactoryOrder
{
Text = String.Join(" ", list.Select(o => o.Text)),
OrderNo = list.First().OrderNo // is the minimum number
})
.ToList();
Result with your sample:
I'm not sure this can be done using a single comprehensible LINQ expression. What would work is a simple enumeration:
private static IEnumerable<FactoryOrder> Merge(IEnumerable<FactoryOrder> orders)
{
var enumerator = orders.OrderBy(x => x.OrderNo).GetEnumerator();
FactoryOrder previousOrder = null;
FactoryOrder mergedOrder = null;
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (mergedOrder == null)
{
mergedOrder = new FactoryOrder(current.Text, current.OrderNo);
}
else
{
if (current.OrderNo == previousOrder.OrderNo + 1)
{
mergedOrder.Text += current.Text;
}
else
{
yield return mergedOrder;
mergedOrder = new FactoryOrder(current.Text, current.OrderNo);
}
}
previousOrder = current;
}
if (mergedOrder != null)
yield return mergedOrder;
}
This assumes FactoryOrder has a constructor accepting Text and OrderNo.
Linq implementation using side effects:
var groupId = 0;
var previous = Int32.MinValue;
var grouped = GetItems()
.OrderBy(x => x.OrderNo)
.Select(x =>
{
var #group = x.OrderNo != previous + 1 ? (groupId = x.OrderNo) : groupId;
previous = x.OrderNo;
return new
{
GroupId = group,
Item = x
};
})
.GroupBy(x => x.GroupId)
.Select(x => new FactoryOrder(
String.Join(" ", x.Select(y => y.Item.Text).ToArray()),
x.Key))
.ToArray();
foreach (var item in grouped)
{
Console.WriteLine(item.Text + "\t" + item.OrderNo);
}
output:
Apple Orange 20
WaterMelon 42
JackFruit 51
Grapes mango Cherry 71
Or, eliminate the side effects by using a generator extension method
public static class IEnumerableExtensions
{
public static IEnumerable<IList<T>> MakeSets<T>(this IEnumerable<T> items, Func<T, T, bool> areInSameGroup)
{
var result = new List<T>();
foreach (var item in items)
{
if (!result.Any() || areInSameGroup(result[result.Count - 1], item))
{
result.Add(item);
continue;
}
yield return result;
result = new List<T> { item };
}
if (result.Any())
{
yield return result;
}
}
}
and your implementation becomes
var grouped = GetItems()
.OrderBy(x => x.OrderNo)
.MakeSets((prev, next) => next.OrderNo == prev.OrderNo + 1)
.Select(x => new FactoryOrder(
String.Join(" ", x.Select(y => y.Text).ToArray()),
x.First().OrderNo))
.ToList();
foreach (var item in grouped)
{
Console.WriteLine(item.Text + "\t" + item.OrderNo);
}
The output is the same but the code is easier to follow and maintain.
LINQ + sequential processing = Aggregate.
It's not said though that using Aggregate is always the best option. Sequential processing in a for(each) loop usually makes for better readable code (see Tim's answer). Anyway, here's a pure LINQ solution.
It loops through the orders and first collects them in a dictionary having the first Id of consecutive orders as Key, and a collection of orders as Value. Then it produces a result using string.Join:
Class:
class FactoryOrder
{
public FactoryOrder(int id, string name)
{
this.Id = id;
this.Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
}
The program:
IEnumerable<FactoryOrder> orders =
new[]
{
new FactoryOrder(20, "Apple"),
new FactoryOrder(21, "Orange"),
new FactoryOrder(22, "Pear"),
new FactoryOrder(42, "WaterMelon"),
new FactoryOrder(51, "JackFruit"),
new FactoryOrder(71, "Grapes"),
new FactoryOrder(72, "Mango"),
new FactoryOrder(73, "Cherry"),
};
var result = orders.OrderBy(t => t.Id).Aggregate(new Dictionary<int, List<FactoryOrder>>(),
(dir, curr) =>
{
var prevId = dir.SelectMany(d => d.Value.Select(v => v.Id))
.OrderBy(i => i).DefaultIfEmpty(-1)
.LastOrDefault();
var newKey = dir.Select(d => d.Key).OrderBy(i => i).LastOrDefault();
if (prevId == -1 || curr.Id - prevId > 1)
{
newKey = curr.Id;
}
if (!dir.ContainsKey(newKey))
{
dir[newKey] = new List<FactoryOrder>();
}
dir[newKey].Add(curr);
return dir;
}, c => c)
.Select(t => new
{
t.Key,
Items = string.Join(" ", t.Value.Select(v => v.Name))
}).ToList();
As you see, it's not really straightforward what happens here, and chances are that it performs badly when there are "many" items, because the growing dictionary is accessed over and over again.
Which is a long-winded way to say: don't use Aggregate.
Just coded a method, it's compact and quite good in terms of performance :
static List<FactoryOrder> MergeValues(List<FactoryOrder> dirtyList)
{
FactoryOrder[] temp1 = dirtyList.ToArray();
int index = -1;
for (int i = 1; i < temp1.Length; i++)
{
if (temp1[i].OrderNo - temp1[i - 1].OrderNo != 1) { index = -1; continue; }
if(index == -1 ) index = dirtyList.IndexOf(temp1[i - 1]);
dirtyList[index].Text += " " + temp1[i].Text;
dirtyList.Remove(temp1[i]);
}
return dirtyList;
}

Combine two list values into one

is there a way to combine these to list items into one list item ? i am sorry if this is a begginer mistake
List<string> values = new List<string>();
foreach (Feature f in allFeatures)
{
if (f.ColumnValues.ContainsKey(layercode)&& f.ColumnValues.ContainsKey(layercode2))
{
if (!values.Contains(f.ColumnValues[layercode].ToString()) && !values.Contains(f.ColumnValues[layercode2].ToString()))
{
values.Add(f.ColumnValues[layercode].ToString());
values.Add(f.ColumnValues[layercode2].ToString());
}
}
}
You can use a List of Tuples, a Dictionary, or create a class. I will not go into depth explaining these as you should be able to easily search and find other questions all about these. Some of this is from memory so syntax might be a bit off.
List of Tuples
List<Tuple<string,string>> values = new List<Tuple<string,string>>();
//...
if ( !values.Any(v=>v.Item1 == f.ColumnValues[layercode].ToString()) && !values.Any(v=>v.Item2 == f.ColumnValues[layercode2].ToString()) )
{
values.Add( Tuple.Create(f.ColumnValues[layercode].ToString(),
f.ColumnValues[layercode2].ToString()) );
}
Dictionary
Dictionary<string,string> values = new Dictionary<string,string> ();
//...
if (!values.ContainsKey(f.ColumnValues[layercode].ToString()) && !values.ContainsValue(f.ColumnValues[layercode2].ToString()))
{
values[f.ColumnValues[layercode].ToString()] = f.ColumnValues[layercode2].ToString();
}
List of class instances
public class LayerCodePair {
public string Code1 {get;set;}
public string Code2 {get;set;}
} // declared outside of method
...
List<LayerCodePair> values = new List<LayerCodePair>();
//...
if (!values.Any(v=> v.Code1 == f.ColumnValues[layercode].ToString()) && !values.Any(v=>v.Code2 == f.ColumnValues[layercode2].ToString()))
{
values.Add(new LayerCodePair{
Code1 = f.ColumnValues[layercode].ToString(),
Code2 = f.ColumnValues[layercode2].ToString()
});
}
It should work for you, using ";" character as a separator:
List<string> values = new List<string>();
foreach (Feature f in allFeatures)
{
var columnValues = f.ColumnValues;
var firstLayerCode = columnValues[layercode].ToString();
var secondLayerCode = columnValues[layercode2].ToString();
if (columnValues.ContainsKey(layercode) && columnValues.ContainsKey(layercode2))
{
if (!values.Contains(firstLayerCode) && !values.Contains(secondLayerCode))
{
var combinedValue = firstLayerCode + ";" + secondLayerCode;
values.Add(combinedValue);
}
}
}

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