Linq equivalent of for in foreach - c#

I'm borrowing code from this question as I went there for inspiration. I have a list of objects, the object has an integer property and I want to foreach the list and the loop the number of integers.
It's a very basic for inside a foreach but I suspect I could use a SelectMany but can't get it working. The following code works but I would like a linq version.
//set up some data for our example
var tuple1 = new { Name = "Tuple1", Count = 2 };
var tuple2 = new { Name = "Tuple2", Count = 3 };
//put the tuples into a collection
var tuples = new [] { tuple1, tuple2 };
foreach(var item in tuples)
{
for(int i = 0; i < item.Count; i++)
Console.WriteLine(item.Name);
}

var flattened = tuples.SelectMany(t => Enumerable.Repeat(t.Name, t.Count));
foreach(var word in flattened)
{
Console.WriteLine(word);
}

You can use SelectMany; you simply need to generate sequences:
tuples.SelectMany(t => Enumerable.Repeat(t.Name, t.Count))

There is no Values property in your anonymous type. But i assume that you mean the Count property instead and you want to repeat the name this number. You can either use Enumerable.Range or Enumerable.Repeat:
IEnumerable<String> tupleNames = tuples
.Select(t => string.Join(Environment.NewLine, Enumerable.Repeat(t.Name, t.Count)));
Console.Write(string.Join(Environment.NewLine, tupleNames));
Output:
Tuple1
Tuple1
Tuple2
Tuple2
Tuple2

There is no linq equivalent of a foreach. You should use an actual foreach to iterate an IEnumerable and perform an action on each item.

Related

Convert ordered comma separated list into tuples with ordered element number (a la SQL SPLIT_STRING) using C# 6.0/.Net Framework 4.8

I can't seem to find a ready answer to this, or even if the question has ever been asked before, but I want functionality similar to the SQL STRING_SPLIT functions floating around, where each item in a comma separated list is identified by its ordinal in the string.
Given the string "abc,xyz,def,tuv", I want to get a list of tuples like:
<1, "abc">
<2, "xyz">
<3, "def">
<4, "tuv">
Order is important, and I need to preserve the order, and be able to take the list and further join it with another list using linq, and be able to preserve the order. For example, if a second list is <"tuv", "abc">, I want the final output of the join to be:
<1, "abc">
<4, "tuv">
Basically, I want the comma separated string to determine the ORDER of the end result, where the comma separated string contains ALL possible strings, and it is joined with an unordered list of a subset of strings, and the output is a list of ordered tuples that consists only of the elements in the second list, but in the order determined by the comma separated string at the beginning.
I could likely figure out all of this on my own if I could just get a C# equivalent to all the various SQL STRING_SPLIT functions out there, which do the split but also include the ordinal element number in the output. But I've searched, and I find nothing for C# but splitting a string into individual elements, or splitting them into tuples where both elements of the tuple are in the string itself, not generated integers to preserve order.
The order is the important thing to me here. So if an element number isn't readily possible, a way to inner join two lists and guarantee preserving the order of the first list while returning only those elements in the second list would be welcome. The tricky part for me is this last part: the result of a join needs a specific (not easy to sort by) order. The ordinal number would give me something to sort by, but if I can inner join with some guarantee the output is in the same order as the first input, that'd work too.
That should work on .NET framework.
using System.Linq;
string str = "abc,xyz,def,tuv";
string str2 = "abc,tuv";
IEnumerable< PretendFileObject> secondList = str2.Split(',').Select(x=> new PretendFileObject() { FileName = x}); //
var tups = str.Split(',')
.Select((x, i) => { return (i + 1, x); })
.Join(secondList, //Join Second list ON
item => item.Item2 //This is the filename in the tuples
,item2 => item2.FileName, // This is the filename property for a given object in the second list to join on
(item,item2) => new {Index = item.Item1,FileName = item.Item2, Obj = item2})
.OrderBy(JoinedObject=> JoinedObject.Index)
.ToList();
foreach (var tup in tups)
{
Console.WriteLine(tup.Obj.FileName);
}
public class PretendFileObject
{
public string FileName { get; set; }
public string Foo { get; set; }
}
Original Response Below
If you wanted to stick to something SQL like here is how to do it with linq operators. The Select method has a built in index param you can make use of. And you can use IntersectBy to perform an easy inner join.
using System.Linq;
string str = "abc,xyz,def,tuv";
string str2 = "abc,tuv";
var secondList = str2.Split(',');
var tups = str.Split(',')
.Select((x, i) => { return (i + 1, x); })
.IntersectBy(secondList, s=>s.Item2) //Filter down to only the strings found in both.
.ToList();
foreach(var tup in tups)
{
Console.WriteLine(tup);
}
This will get you list of tuples
var input = "abc,xyz,def,tuv";
string[] items = input.Split(',');
var tuples = new List<(int, string)>();
for (int i = 0; i < items.Length)
{
tuples.Add(((i + 1), items[i]));
}
if then you want to add list of "tuv" and "abc" and keep 1, you probably want to "Left Join". But I am not sure, how you can do using LINQ because you first need to iterate the original list of tuples and assign same int. Then join. Or, you can join first and then assign int but technically, order is not guaranteed. However, if you assign int first, you can sort by it in the end.
I am slightly confused by "and be able to take the list and further join it with another list using linq". Join usually means aggregate result. But in your case it seem you demanding segment, not joined data.
--
"I want to remove any items from the second list that are not in the first list, and then I need to iterate over the second list IN THE ORDER of the first list"
var input2 = "xxx,xyz,yyy,tuv,";
string[] items2 = input2.Split(',');
IEnumerable<(int, string)> finalTupleOutput =
tuples.Join(items2, t => t.Item2, i2 => i2, (t, i2) => (t.Item1, i2)).OrderBy(tpl => tpl.Item1);
This will give you what you want - matching items from L2 in the order from L1
with LINQ
string inputString = "abc,xyz,def,tuv";
var output = inputString.Split(',')
.Select((item, index) => { return (index + 1, item); });
now you can use the output list as you want to use.
Not 100% sure what you're after, but here's an attempt:
string[] vals = new[] { "abc", "xyz", "dev", "tuv"};
string[] results = new string[vals.Length];
int index = 0;
for (int i = 0; i < vals.Length; i++)
{
results[i] = $"<{++index},\"{vals[i]}\">";
}
foreach (var item in results)
{
Console.WriteLine(item);
}
This produces:
<1,"abc">
<2,"xyz">
<3,"dev">
<4,"tuv">
Given the example
For example, if a second list is <"tuv", "abc">, I want the final
output of the join to be:
<1, "abc"> <4, "tuv">
I think this might be close?
List<string> temp = new List<string>() { "abc", "def", "xyz", "tuv" };
List<string> temp2 = new List<string>() { "dbc", "ace", "zyw", "tke", "abc", "xyz" };
var intersect = temp.Intersect(temp2).Select((list, idx) => (idx+1, list));
This produces an intersect result that has the elements from list 1 that are also in list 2, which in this case would be:
<1, "abc">
<2, "xyz">
If you want all the elements from both lists, switch the Intersect to Union.

How to return substring of linq results

Let's say I have a list of strings:
originalList = { "XX.one", "XX.two", "YY.three" }
I want to use linq to select and return a list with {"one", "two"}.
if I do for example
resultList = originalList.FindAll(o => o.StartsWith("XX")));
I will get resultList = { "XX.one", "XX.two" } but what I want is resultList = { "one", "two" }
Any way to solve this?
EDIT: Thanks for all who answered, I've chosen the split function of #er-mfahhgk since it does the minimum of manipulation and doesn't depend on size of the prefix.
You can use SelectWith your desired string and then using Split function on Dot (.) you can select the second part like
var resultList = originalList.Where(o => o.StartsWith("XX"))
.Select(x => x.Split('.')[1])
.ToList();
And finally your output will be,
foreach (var item in resultList)
{
Console.WriteLine(item);
}
Console.ReadLine();
Output:
result = originalList.Where(o => o.StartsWith("XX"))
.Select(x=>x.Replace("XX.,""))
.ToList();
You could try this:
resultList = originalList.Where(o => o.StartsWith("XX"))
.Select(x=>x.Substring(3))
.ToList();
( edited to correct wording of Substring )

Linq Where condition to match array

I have a linq list obtained from database in my Model. Now I have a string array obtained from my controller. I want to construct a statement
pseudo-code
List<object> objlist = db.objects.tolist();
string[] strarray; // obtained from a long code.
var k = objlist.Where(u => u.somecol == strarray[0] || u.somecol == strarray[1]........strarray[n]).toList();
I am little bit confused how to accomplish this since my strarray[] is variable length and can contain upto 1000 words.
You can check if an array contains some item using the Array.IndexOf<T> Method:
bool strarrayContainsX = Array.IndexOf<string>(strarray, "X") >= 0;
However, I'd recommend you use a HashSet<string> instead of a string array for anything more than a few items. The HashSet<T> Class provides a Contains Method to check if a set contains some item:
HashSet<string> strset = new HashSet<string>(strarray);
bool strsetContainsX = strset.Contains("X");
The resulting query then looks like this:
var k = objlist.Where(u => strset.Contains(u.somecol)).ToList();
Use Contains:
var k = objlist.Where(u => strarray.Contains(u.somecol)).toList();
Try this:
List<object> objlist = db.objects.tolist();
string[] strarray; // obtained from a long code.
var k = objlist.Where(u => strarray.Contains(u.somecol)).toList();
var k = objlist.Where(u => strarray.Any(x=>x == u.somecol)).ToList();

Can we use multiple variables in foreach

Can we use multiple variables in foreach
foreach (var item1 in collection1;var items2 in collection2)
{
}
I want to do this because I need to fetch two collections from a database and append both of them to a ComboBox.
Use LINQ to join the arrays putting the result into an anonymous type, then iterate over the resulting collection.
var col = collection1.Join(collection2, x => x, y => y, (x, y) => new { X = x, Y = y });
foreach (var entry in col) {
// entry.X, entry.Y
}
Edit:
When posting the answer I assumed that collection1 and collection2 contained different types. If they contain both the same type or share a common base type, there are alternatives:
If you want to allow duplicates:
collection1.Concat(collection2); // same type
collection1.select(x => (baseType)x).Concat(collection2.select(x => (baseType)x)); // shared base type
No duplicates:
collection1.Union(collection2); // same type
collection1.select(x => (baseType)x).Union(collection2.select(x => (baseType)x)); // shared base type
Form framework 4.0 onwards Zip can replace the original solution:
collection1.Zip(collection2, (x, y) => new { X = x, Y = y });
For an overview over most of the available LINQ funktions please refer to 101 LINQ Samples.
Without LINQ use two hierarchical foreach loops (increasing the number of interations) or one foreach loop to create an inermediate type and a second to iterate over the collection of intermediates or if the types in the collections are the same add them to a list (using AddRange) and then iterate over this new list.
Many roads lead to one goal ... its up to you to chose one.
You can Zip the collections
foreach (var item in collection1.Zip(collection2, (a, b) => new { A = a, B = b }))
{
var a = item.A;
var b = item.B;
// ...
}
This assumes that the elements match at the same position (e.g. the first element from collection1 joins the first element of collecion2). It is quite efficient.
No, you cannot use multiple variables in a foreach, in loop. Check the language reference. What would happen if each collection had a different number of items?
If you want to iterate over both collections, try using a union:
foreach (var item1 in collection1.Union(collection2))
{
...
}
foreach is used to enumerate individual items in a collection. So no you can't. You have to use it one after the other.
It would be better to use:
void myfunc()
{}
foreach(var item1 in collection1){myfunc();}
foreach(var item2 in collection2){myfunc();}
than
foreach(var item1 in collection1)
foreach(var item2 in collection2)
{
myfunc();
}
This would run for n*m times. Whereas previous example would run for only n+m times.
Judging by your comments I think what you're really trying to do is not get the Cartesian product of the two collections, but a [SQL] UNION of the two sets. You have two options:
Concat the two collections:
foreach(var items in collection1.Concat(collection2)) {}
Just add them both separately, assuming you don't need to do anything fancy by iterating (probably the best/simplest):
myComboBox.Items.AddRange(collection1);
myComboBox.Items.AddRange(collection2);
If, however, you do want the n*m Cartesian product of [SQL pseudocode] collection1 CROSS JOIN collection2, you would use two nested foreach statements:
foreach(var item1 in collection1)
foreach(var item2 in collection2)
{
}
Or, you can join the two in LINQ and iterate over the joined collection:
foreach(var items in (from i1 in collection1
from i2 in collection2
select Tuple.Create(i1, i2)))
{
}
You can do it using a newer syntax:
var collection1 = new List<int>(){1,2,3,4};
var collection2 = new List<int>(){5,6,7,8};
var zip = collection1.Zip(collection2, (i,j) => (i,j));
foreach (var (item1, item2) in zip)
{
Console.WriteLine($"item1:{item1} item2:{item2}");
}
// outputs:
//item1:1 item2:5
//item1:2 item2:6
//item1:3 item2:7
//item1:4 item2:8
Do you want to pair the items from one collection with corresponding items of the other?
foreach (var pair in col1.Zip(col2, (Item1, Item2) => new { Item1, Item2 })
{
//do something with pair.Item1 and pair.Item2
}
Note: if the first collection has 10 items and the second has 8, you will get 8 pairs; the last two items in the first collection will be dropped because there's nothing to match them with in the second collection. More generally, the number of iterations will be Min(col1.Count(), col2.Count()).
Do you want to iterate all of the items in one collection and then all of the items in the second?
foreach (var element in col1.Concat(col2))
{
//do something with element
}
Note: if the first collection has 10 elements and the second has 8, this loop will execute 18 times, or, more generally, the number of iterations will be col1.Count() + col2.Count().
Do you want to pair each item in one collection with each item in the other?
foreach (var item1 in col1)
foreach (var item2 in col2)
{
//do something with item1 and item2
}
Note: this is the cartesian product, so, not surprisingly, the number of iterations is the product of the collections' sizes. If we have 10 and 8 items, the loop will execute 80 times. For consistency's sake, that's col1.Count() * col2.Count().
You could use an iterator:
IEnumerator <Item2Type> item2Itt = Collection2.GetEnumerator();
item2Itt.MoveNext(); // The iterator returned above is BEFORE the first element in the collection.
foreach (Item1Type item1 in collection1)
{
item1.blahblahblah;
item2Itt.Current.blahBlahBlah;
item2Itt.MoveNext();
}
I think this way can be used:
List<Type> allOfThem = new List<Type>(); //use proper type for collection
allOfThem.AddRange(collection1);
allOfThem.AddRange(collection2);
foreach (Type item in allOfThem)
{
...
}
Using enumerator is the simplest way to do it. You can use either of the two collection to get the enumerator and run foreach on the other one.
public void MatchSentences() {
string[] OrigSentences = { "hello you", "what are you doing", "hope things are fine" };
string[] CompareSentences = { "hello you", "what are you doing", "hope things are fine" };
// Get enumerator on the second collection
var outputStrEnum = CompareSentences.GetEnumerator();
// Run foreach on the first collection
foreach (var sentence in OrigSentences) {
outputStrEnum.MoveNext();
string testAgainst = outputStrEnum.Current.ToString();
bool result = sentence.Equals(testAgainst);
Assert.IsTrue(result,
String.Format(" Expected for '{0}': {1}; Actual: '{2}'",
testAgainst, result,sentence) );
}
}

Use ToDictionary to create a dictionary

I have two string lists which have same size.
I want to create a dictionary, the key is from listA, the value is from listB.
What is the fast way?
I used the code:
List<string> ListA;
List<string> ListB;
Dictionary<string,string> dict = new Dictionary<string,string>();
for(int i=0;i<ListA.Count;i++)
{
dict[key] = listA[i];
dict[value]= listB[i];
}
I don't like this way, can I use ToDictionary method?
Starting with .NET 4.0, you can do it using LINQ's Zip method, like this:
var res = ListA.Zip(ListB, (a,b) => new {a, b})
.ToDictionary(p=>p.a, p=>p.b);
[Zip] method merges each element of the first sequence with an element that has the same index in the second sequence.
You could create an anonymous type with the index which you can use to get the B at this index.
Dictionary<string, string> dict = ListA
.Select((a, i) => new { A = a, Index = i })
.ToDictionary(x => x.A, x => ListB.ElementAtOrDefault(x.Index));
Note that the value would be null in case ListB is smaller than ListA.
I would not bother (if it is possible) as your version is readable, easy to debug and quicker than any other LINQ solutions (especially if you are working with big list).
I wouldn't change your version.
The following piece of code is more readable than LINQ stuff in your case, IMHO.
var ListA = new List<string>();
var ListB = new List<string>();
var dict = new Dictionary<string, string>();
for (int i = 0; i < ListA.Count; i++)
{
dict.Add(ListA[i], ListB[i]);
}

Categories

Resources