Can I conditionally create an IEnumerable with LINQ? - c#

I have to following code:
List<Obj> coll = new List<Obj>();
if (cond1) coll.Add(new Obj { /*...*/ });
if (cond2) coll.Add(new Obj { /*...*/ });
if (cond3) coll.Add(new Obj { /*...*/ });
Is there a way to use LINQ or collection initializers for that?
EDIT:
The reason I want to use a collection initializer here is because I have an object tree which I do completely initialize with initialiers and LINQ. This spot is the only one which doesn't follow this principle.
var myobj = new MyBigObj
{
Prop1 = from .. select ..,
Prop2 = from .. select ..,
...
Prop3 = new MySmallerObj
{
PropSmall1 = from .. select ..,
PropSmall2 = from .. select ..,
...
}
};
And now this simply doesn't fit in my scheme:
List<Obj> coll = new List<Obj>();
if (cond1) coll.Add(new Obj { /*...*/ });
if (cond2) coll.Add(new Obj { /*...*/ });
if (cond3) coll.Add(new Obj { /*...*/ });
myobj.Prop4 = coll;
Sure I could put this code in a separate function that returns IEnumerable and call that.. :)
EDIT2:
It looks like I have to code some extension method which I would call like:
new Obj[0]
.ConditionalConcat(cond1, x=>new Obj { /*...*/ })
.ConditionalConcat(cond2, x=>new Obj { /*...*/ })
.ConditionalConcat(cond3, x=>new Obj { /*...*/ })

One fairly horrible option:
var conditions = new[] { cond1, cond2, cond3 };
var values = new[] { new Obj {...}, // First value
new Obj {...}, // Second value
new Obj { ...} // Third value
};
var list = conditions.Zip(values, (condition, value) => new { condition, value })
.Where(pair => pair.condition)
.Select(pair => pair.value)
.ToList();
It's not exactly simpler than the original code though ;) (And also it unconditionally creates all the values - it's only conditionally including them in the collection.)
EDIT: An alternative which only constructs the values when it needs to:
var conditions = new[] { cond1, cond2, cond3 };
var valueProviders = new Func<Obj>[] {
() => new Obj {...}, // First value
() => new Obj {...}, // Second value
() => new Obj { ...} // Third value
};
var list = conditions.Zip(valueProviders,
(condition, provider) => new { condition, provider })
.Where(pair => pair.condition)
.Select(pair => pair.provider())
.ToList();
EDIT: Given your requested syntax, this is a fairly easy option:
new List<Obj>()
.ConditionalConcat(cond1, x=>new Obj { /*...*/ })
.ConditionalConcat(cond2, x=>new Obj { /*...*/ })
.ConditionalConcat(cond3, x=>new Obj { /*...*/ })
with an extension method:
public static List<T> ConditionalConcat<T>(this List<T> source,
bool condition,
Func<T> provider)
{
if (condition)
{
source.Add(provider);
}
return source;
}

If your conditions depend on a single status object (or something that can reduced to),
you can create a method using yield, like the following:
IEnumerable<Obj> GetElemets(MyStatus currentStatus)
{
if(currentStatus.Prop1 == "Foo")
yield return new Obj {...};
if(currentStatus.IsSomething())
yield return new Obj {...};
if(currentStatus.Items.Any())
yield return new Obj {...};
// etc...
yield break;
}
In this way, you will separate the IEnumerable<Obj> generation logic, from the consumer logic.

Old question, but here is another approach using ternery operators ? :, .Concat() and Enumerable.Empty<T>()
var range1 = Enumerable.Range(1,10);
var range2 = Enumerable.Range(100,10);
var range3 = Enumerable.Range(1000,10);
var flag1 = true;
var flag2 = false;
var flag3 = true;
var sumOfCollections = (flag1 ? range1 : Enumerable.Empty<int>())
.Concat(flag2 ? range2 : Enumerable.Empty<int>())
.Concat(flag3 ? range3 : Enumerable.Empty<int>());

Though an old question, but I have an option to solve it in a somewhat clear way and without extensions or any other methods.
Assuming that conditions and initial collection of objects to be created are of same size, I used indexed Where overload approach, so it is not adding objects conditionally, but rather filtering them, with a use of funcs/lambdas we get also laziness, if we want.
The actual creation of objects is not relevant, so I put just boxing of ints (you could replace it with real creation, i.e. getting them from another collection using index), and list manipulation is for getting ints back - but values collection already has 2 elements, so all this could be thrown away (maybe except select with func call in case of using laziness).
Here is the all the code for running sample test right in MSVS
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests
{
[TestClass]
public class Tests
{
[TestMethod]
public void Test()
{
var conds = new[] { true, false, true };
var values = conds.Select((c, i) => new Func<object>(() => i)).Where((f, i) => conds[i]);
var list = values.Select(f => f()).Cast<int>().ToList();
Assert.AreEqual(list.Count, 2);
}
}
}
UPD.
Here also lazy and non-lazy one-liners with "getting object"
var lazy1line = new[] { true, false, true }.Select((c, i) => new Func<object>(() => (DayOfWeek)i)).Where((f, i) => conds[i]).Select(f => f());
var simple1line = new[] { true, false, true }.Select((c, i) => (DayOfWeek)i).Where((f, i) => conds[i]);
Assert.AreEqual(lazy1line.Count(), simple1line.Count());

Related

Can only enumerate once over IEnumerable

Given is the following code (a xUnit test):
[Fact]
public void SetFilePathTest()
{
// Arrange
IBlobRepository blobRepository = null;
IEnumerable<Photo> photos = new List<Photo>()
{
new Photo()
{
File = "1.jpg"
},
new Photo()
{
File = "1.jpg"
}
};
IEnumerable<CloudBlockBlob> blobs = new List<CloudBlockBlob>()
{
new CloudBlockBlob(new Uri("https://blabla.net/media/photos/1.jpg")),
new CloudBlockBlob(new Uri("https://blabla.net/media/photos/2.jpg"))
};
// Act
photos = blobRepository.SetFilePath2(photos, blobs);
// Assert
Assert.Equal(2, photos.Count());
Assert.Equal(2, photos.Count());
}
Here is the SetFilePath2 method:
public static IEnumerable<T> SetFilePath2<T>(this IBlobRepository blobRepository, IEnumerable<T> entities, IEnumerable<CloudBlockBlob> blobs) where T : BlobEntityBase
{
var firstBlob = blobs.FirstOrDefault();
if (firstBlob is null == false)
{
var prefixLength = firstBlob.Parent.Prefix.Length;
return entities
.Join(blobs, x => x.File, y => y.Name.Substring(prefixLength), (entity, blob) => (entity, blob))
.Select(x =>
{
x.entity.File = x.blob.Uri.AbsoluteUri;
return x.entity;
});
}
else
{
return Enumerable.Empty<T>();
}
}
As you can see, I assert 2 times the very same thing. But only the first assert succeeds. When I step through with the debugger then I can only enumerate the collection once. So at the second Assert it yields no items back.
Can anyone explain me why that happens? I really don't see any problem with this code than explains this behavior.
Every time you call .Count() you basically call
blobRepository.SetFilePath2(photos, blobs).Count() and you modify the entity while using Select.I would recommend using new in the Select statement if you don't mean to alter the original value. That's why you get different results.

How do I flatten an array of arrays?

I have an array consisting of following elements:
var schools = new [] {
new object[]{ new[]{ "1","2" }, "3","4" },
new object[]{ new[]{ "5","6" }, "7","8" },
new object[]{ new[]{ "9","10","11" }, "12","13" }
};
The real object that i try to flatten is from importing data into array of arrays from CSV and then joining it on values of fields:
var q =
from c in list
join p in vocatives on c.Line[name1].ToUpper() equals p.first_name.ToUpper() into ps
from p in ps.DefaultIfEmpty()
select new object[] { c.Line, p == null ? "(No vocative)" : p.vocative, p == null ? "(No sex)" : p.sex };
I want to flatten that array of strings to get:
string[] {
new string[]{ "1","2","3","4" },
new string[]{ "5","6","7","8" },
new string[]{ "9","10","11","12","13" }
}
I already have an solution that does that in loop, its not so performance-wise, but it seems to work ok.
I've tried to use SelectMany but cannot make up a solution.
Thank you very much for feedback ;)
I've tried answer from npo:
var result = schools.Select(z => z.SelectMany(y=> y.GetType().IsArray
? (object[])y : new object[] { y })
);
But CSVwriter class method accepts only explicitly typed:
IEnumerable<string[]>
So how to do it in linq, I've tried to:
List<string[]> listOflists = (List<string[]>)result;
But no go, InvalidCastException arrises, unfortunately.
In a first step, you have to normalize the data to one kind of type. Then you can iterate over them as you like. So at first create a method to flatten the values from a specific point to an arbitrary depth:
public static class Extensions
{
public static IEnumerable<object> FlattenArrays(this IEnumerable source)
{
foreach (var item in source)
{
if (item is IEnumerable inner
&& !(item is string))
{
foreach (var innerItem in inner.FlattenArrays())
{
yield return innerItem;
}
}
yield return item;
}
}
}
Now you can either iterate on the top level to get a single array of all values:
// Produces one array => ["1", "2", "3", "4", ...]
var allFlat = schools.FlattenArrays().OfType<string>().ToArray();
Or you can create individual array one depth deeper:
foreach (var item in schools)
{
// Produces an array for each top level e.g. ["5", "6", "7", "8"]
var flat = item.FlattenArrays().OfType<string>().ToArray();
}
As per the comments, because your inner array mixes elements of string[] and string, it likely won't be trivial to do this directly in Linq.
However, with the assistance of a helper function (I've called Flattener) you can branch the handling of both of the inner types manually to either return the elements in the array (if it's string[]), or to return the single element as an enumerable, if it's not. SelectMany can then be used to flatten the inner level, but the outer level seemingly you want to leave unflattened:
i.e.
var schools = new [] {
new object[]{new[]{"1","2"}, "3","4"},
new object[]{new[]{"5","6"}, "7","8"},
new object[]{new[]{"9","10","11"}, "12","13"}
};
var result = schools
.Select(s => s.SelectMany(o => Flattener(o)));
Which returns a type of IEnumerable<IEnumerable<string>>
Where the messy unpacking bit done by:
public IEnumerable<string> Flattener(object o)
{
if (o is IEnumerable<string> strings)
{
return strings;
}
if (o is string s)
{
return new[]{s};
}
return new[]{"?"};
}
Note the above uses the pattern matching capabilities of C#7.
Result screenshot courtesy of LinqPad:
If you want o do it via linq here is a sample
var schools = new[] {
new object[]{new[]{"1","2"}, "3","4"},
new object[]{new[]{"5","6"}, "7","8"},
new object[]{new[]{"9","10","11"}, "12","13"}
};
var result = schools.Select(z => z.SelectMany(y=> y.GetType().IsArray ? (object[])y : new object[] { y }));
The presented solution is devoted to convert any sort of int array, regular, jagged, or nested (these last are taken from javascript and its object notation, but they can also be implemented as complex jagged array of objects in C#), into a simple mono-dimensional integers array.
To adapt your request to it, you should have only change the string type elements of your objects jagged array into int type.
Here it is the C# function:
public static int[] getFlattenedIntArray(object jaggedArray)
{
var flattenedArray = new List<int>();
var jaggedArrayType = jaggedArray.GetType();
var expectedType = typeof(int);
if (jaggedArrayType.IsArray)
{
if (expectedType.IsAssignableFrom(jaggedArrayType.GetElementType()))
{
foreach (var el in jaggedArray as int[])
{
flattenedArray.Add(el);
}
}
else
{
foreach (var el in jaggedArray as object[])
{
foreach (var retIntEl in getFlattenedIntArray(el))
{
flattenedArray.Add(retIntEl);
}
}
}
}
else if (jaggedArrayType == expectedType)
{
flattenedArray.Add((int)jaggedArray);
}
else
{
return new int[0];
}
return flattenedArray.ToArray();
}
Try it in this fiddle: https://dotnetfiddle.net/5HGX96

How to assign "var" inside if statement

I need to do this:
var productsLocation = response.blah blah; //with linq query
var item; // even var item = null; //not valid
if(condition){
item = productsLocation.linq query
} else {
item = productsLocation.differentquery
}
var group = item.query;
Is this possible? If yes, how?
EDIT: here is my exact code:
var productLocation = response.productLocation.Select(p => ProductLocationStaticClass.DtoToModel(p));
var items;
if (condition)
{
items = productLocation.Select(s => new ProductClass(s)).Where(s => categories.Contains(s.CategoryName));
} else {
items = productLocation.Select(s => new ProductClass(s)).Where(s => categories.Contains(s.CategoryName) && stocks.Contains(s.Barcode));
}
If you look closely at the logic, you notice you don't actually even need the if block. The whole thing can be written in one expression as follows:
var items = productLocation
.Select(s => new ProductClass(s))
.Where(s => categories.Contains(s.CategoryName) && (condition || stocks.Contains(s.Barcode)));
First of all get your response variable type, then initialize 'item' variable as IEnumarable where T is same as response variable type
var productsLocation = response.productLocation.Select(p => ProductLocationStaticClass.DtoToModel(p));
IEnumerable<ProductClass> item;
if (condition)
{
items = productLocation.Select(s => new ProductClass(s)).Where(s => categories.Contains(s.CategoryName));
}
else
{
items = productLocation.Select(s => new ProductClass(s)).Where(s => categories.Contains(s.CategoryName) && stocks.Contains(s.Barcode));
}
var group = item.Where(condition);
You can do it with IEnumerable interface in this way:
using System.Collections;
using System.Collections.Generic;
List<string> products = new List<string>() { "First", "Second", "Third", "Fourth" };
IEnumerable item;
var condition = false;
if (condition)
{
item = products.Select(x=>x);
}
else
{
item = products.Where(x => x.StartsWith("F"));
}
var group = item.Cast<string>().Where(/*...Here your conditions...*/)

Multiple OfType Linq?

I have a linq query that selects all textboxes in a placeholder and adds them to a list using a struct. I need to expand this functionality to also take the selectedvalue of a DropDownList I am pretty sure I am doing this wrong, because when I debug the method the lists count is 0.
My own guess is that declaring 2 OfType<>() is wrong, but I am pretty new to linq and I have no idea of how else to do it.
Any help would be awesome! Thanks in advance.
Here's what I have so far:
public struct content
{
public string name;
public string memberNo;
public int points;
public string carclass;
}
List<content> rows = new List<content>();
protected void LinkButton_Submit_Attendees_Click(object sender, EventArgs e)
{
List<content> rows = PlaceHolder_ForEntries.Controls.OfType<TextBox>().OfType<DropDownList>()
.Select(txt => new
{
Txt = txt,
Number = new String(txt.ID.SkipWhile(c => !Char.IsDigit(c)).ToArray())
})
.GroupBy(x => x.Number)
.Select(g => new content
{
carclass = g.First(x => x.Txt.ID.StartsWith("DropDownlist_CarClass")).Txt.SelectedValue,
name = g.First(x => x.Txt.ID.StartsWith("TextBox_Name")).Txt.Text,
memberNo = g.First(x => x.Txt.ID.StartsWith("TextBox_MemberNo")).Txt.Text,
points = int.Parse(g.First(x => x.Txt.ID.StartsWith("TextBox_Points")).Txt.Text)
})
.ToList();
}
Here's the method that creates the controls.
protected void createcontrols()
{
int count = 0;
if (ViewState["count"] != null)
{
count = (int)ViewState["count"];
}
while (PlaceHolder_ForEntries.Controls.Count < count)
{
TextBox TextBox_Name = new TextBox();
TextBox TextBox_MemberNo = new TextBox();
TextBox TextBox_Points = new TextBox();
DropDownList DropDownList_CarClass = new DropDownList();
DropDownList_CarClass.Items.Add("Car1");
...
DropDownList_CarClass.Items.Add("Car2");
TextBox_Name.Attributes.Add("placeholder", "Navn");
TextBox_Name.ID = "TextBox_Name" + PlaceHolder_ForEntries.Controls.Count.ToString();
TextBox_Name.CssClass = "input-small";
TextBox_MemberNo.Attributes.Add("placeholder", "Medlemsnr.");
TextBox_MemberNo.ID = "TextBox_MemberNo" + PlaceHolder_ForEntries.Controls.Count.ToString();
TextBox_MemberNo.CssClass = "input-small";
TextBox_Points.Attributes.Add("placeholder", "Point");
TextBox_Points.ID = "TextBox_Points" + PlaceHolder_ForEntries.Controls.Count.ToString();
TextBox_Points.CssClass = "input-small";
PlaceHolder_ForEntries.Controls.Add(TextBox_Name);
PlaceHolder_ForEntries.Controls.Add(TextBox_MemberNo);
PlaceHolder_ForEntries.Controls.Add(DropDownList_CarClass);
PlaceHolder_ForEntries.Controls.Add(TextBox_Points);
PlaceHolder_ForEntries.Controls.Add(new LiteralControl("<br />"));
}
}
you can use the Where and check if the instance of object is of type!
List<content> rows = PlaceHolder_ForEntries.Controls.Cast<Control>().Where(c => c is TextBox || c is DropDownList)
.Select(txt => new
{
Txt = txt,
Number = new String(txt.ID.SkipWhile(c => !Char.IsDigit(c)).ToArray())
})
.GroupBy(x => x.Number)
.Select(g => new content
{
carclass = g.First(x => x.Txt.ID.StartsWith("DropDownlist_CarClass")).Txt.SelectedValue,
name = g.First(x => x.Txt.ID.StartsWith("TextBox_Name")).Txt.Text,
memberNo = g.First(x => x.Txt.ID.StartsWith("TextBox_MemberNo")).Txt.Text,
points = int.Parse(g.First(x => x.Txt.ID.StartsWith("TextBox_Points")).Txt.Text)
})
.ToList();
AppDeveloper is right. OfType<T> filters out all objects of types other than T; so by filtering twice, you effectively eliminate all objects in the list.
If you wanted to wrap this logic (filtering all but two types from a list) into something reusable, nothing's stopping you from implementing your own extension method:
using System.Collections;
public static class EnumerableExtensions
{
public static IEnumerable OfType<T1, T2>(this IEnumerable source)
{
foreach (object item in source)
{
if (item is T1 || item is T2)
{
yield return item;
}
}
}
}
Including the above class in your project will allow you to write code like this in your application:
var textBoxesAndDropDowns = controls.OfType<TextBox, DropDownList>();
To learn more about extension methods, see the MSDN article on the subject.
Note that since the extension method above "lets in" two different types, the result is still a non-generic IEnumerable sequence. If you wanted to treat the result as a generic sequence (e.g., an IEnumerable<Control>), I would recommend using the Cast<T> extension method:
var filteredControls = controls.OfType<TextBox, DropDownList>().Cast<Control>();
I haven't read the question thoroughly, but from what the title implies, you can achieve the behavior with:
var collection = new object[] { 5, "4545", 'd', 54.5 , 576 };
var allowedTypes = new[] { typeof(string), typeof(int) };
var result = collection
.Where(item => allowedTypes.Contains(item.GetType()));
See it in action here.
Took #Shimmys answer for an extension method:
/// <param name="wantedTypes">--- Sample: --- new Type[] { typeof(Label), typeof(Button) }</param>
public static IEnumerable OfTypes(this IEnumerable collection, Type[] wantedTypes)
{
if (wantedTypes == null)
return null;
else
return collection.Cast<object>().Where(element => wantedTypes.Contains(element.GetType()));
}
Usage:
// List of 3 different controls
List<object> controls = new List<object>(new object[] { new Label(), new Button(), new TextBox() });
// Get all labels and buttons
var labelsAndButtons = controls.OfTypes(new Type[] { typeof(Label), typeof(Button) });
Your problem is in the OfType<>().OfType<>() you filter twice with different types

Elegant way to check if a list contains an object where one property is the same, and replace only if the date of another property is later

I have a class as follows :
Object1{
int id;
DateTime time;
}
I have a list of Object1. I want to cycle through another list of Object1, search for an Object1 with the same ID and replace it in the first list if the time value is later than the time value in the list. If the item is not in the first list, then add it.
I'm sure there is an elegant way to do this, perhaps using linq? :
List<Object1> listOfNewestItems = new List<Object1>();
List<Object1> listToCycleThrough = MethodToReturnList();
foreach(Object1 object in listToCycleThrough){
if(listOfNewestItems.Contains(//object1 with same id as object))
{
//check date, replace if time property is > existing time property
} else {
listOfNewestItems.Add(object)
}
Obviously this is very messy (and that's without even doing the check of properties which is messier again...), is there a cleaner way to do this?
var finalList = list1.Concat(list2)
.GroupBy(x => x.id)
.Select(x => x.OrderByDescending(y=>y.time).First())
.ToList();
here is the full code to test
public class Object1
{
public int id;
public DateTime time;
}
List<Object1> list1 = new List<Object1>()
{
new Object1(){id=1,time=new DateTime(1991,1,1)},
new Object1(){id=2,time=new DateTime(1992,1,1)}
};
List<Object1> list2 = new List<Object1>()
{
new Object1(){id=1,time=new DateTime(2001,1,1)},
new Object1(){id=3,time=new DateTime(1993,1,1)}
};
and OUTPUT:
1 01.01.2001
2 01.01.1992
3 01.01.1993
This is how to check:
foreach(var object in listToCycleThrough)
{
var currentObject = listOfNewestItems
.SingleOrDefault(obj => obj.Id == object.Id);
if(currentObject != null)
{
if (currentObject.Time < object.Time)
currentObject.Time = object.Time
}
else
listOfNewestItems.Add(object)
}
But if you have large data, would be suggested to use Dictionary in newest list, time to look up will be O(1) instead of O(n)
You can use LINQ. Enumerable.Except to get the set difference(the newest), and join to find the newer objects.
var listOfNewestIDs = listOfNewestItems.Select(o => o.id);
var listToCycleIDs = listToCycleThrough.Select(o => o.id);
var newestIDs = listOfNewestIDs.Except(listToCycleIDs);
var newestObjects = from obj in listOfNewestItems
join objID in newestIDs on obj.id equals objID
select obj;
var updateObjects = from newObj in listOfNewestItems
join oldObj in listToCycleThrough on newObj.id equals oldObj.id
where newObj.time > oldObj.time
select new { oldObj, newObj };
foreach (var updObject in updateObjects)
updObject.oldObj.time = updObject.newObj.time;
listToCycleThrough.AddRange(newestObjects);
Note that you need to add using System.Linq;.
Here's a demo: http://ideone.com/2ASli
I'd create a Dictionary to lookup the index for an Id and use that
var newItems = new List<Object1> { ...
IList<Object1> itemsToUpdate = ...
var lookup = itemsToUpdate.
Select((i, o) => new { Key = o.id, Value = i }).
ToDictionary(i => i.Key, i => i.Value);
foreach (var newItem in newitems)
{
if (lookup.ContainsKey(newitem.ID))
{
var i = lookup[newItem.Id];
if (newItem.time > itemsToUpdate[i].time)
{
itemsToUpdate[i] = newItem;
}
}
else
{
itemsToUpdate.Add(newItem)
}
}
That way, you wouldn't need to reenumerate the list for each new item, you'd benefit for the hash lookup performance.
This should work however many times an Id is repeated in the list of new items.

Categories