I was just wondering whether it is posisble to concat list by entry so;
List One Concat List Two Concat List Three
Entry One -> Entry Two -> Entry Three
Entry One -> Entry Two -> Entry Three
Entry One -> Entry Two -> Entry Three
The reason I ask is I have three list that loop the values I need off diferent parts of the program. I need a way on concat the entries in the order they appear across the three list. The code for looping each list is;
List<string> QualificationList = new List<string>();
List<string> SubjectList = new List<string>();
List<string> GradeList = new List<string>();
foreach (XmlNode QualificationNode in GetQualification)
{
GetQualificationType = QualificationNode.InnerText;
QualificationList.Add(GetQualificationType);
}
foreach (XmlNode SubjectNode in GetSubject)
{
GetSubjects = SubjectNode.InnerText;
SubjectList.Add(GetSubjects);
}
foreach (XmlNode node in GetGrade)
{
GetGrades = node.InnerText;
GradeList.Add(GetGrades);
}
I was just wondering whether this is possible if not how could I gather the information which I need to concat or stringbuilder properly. Thanks for any help which you can provide
I think you want Zip:
var concatList = QualificationList.Zip(SubjectList, (q,s) => new {q, s})
.Zip(GradeList, (qs, g) => new {qs.q, qs.s, qs.g})
That will give you an anonymous type with three properties: q (qualification), s (subject), and g (grade). If you want to concatenate them into one string just project it with Select:
.Select( qsg => string.Format("{0} {1} {2}", q, s, g);
You could also replace each xxxList with a projection if you want; it just adds more complexity to the Linq statement (and may make it harder to debug):
var concatList = GetQualification.Select(node => node.InnerText)
.Zip(GetSubject.Select(node => node.InnerText),
(q,s) => new {q, s})
.Zip(GetGrade.Select(node => node.InnerText),
(qs, g) => new {qs.q, qs.s, qs.g})
Your code can be simplified to this:
QualificationList.AddRange(GetQualification.Select(item => item.InnerText));
SubjectList.AddRange(GetSubject.Select(item => item.InnerText));
GradeList.AddRange(GetGrade.Select(item => item.InnerText));
Using AddRange will save you from the loops and Select method will return the texts inside the order they appear.
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I have a list of strings
e.g.{"apple.txt", "orange.sd.2.txt", "apple.2.tf.txt", "orange.txt"}
and another list of strings to group the first list
e.g. {"apple", "orange"}
so that the first list is split into a list of lists and looks like this:
{{"apple.txt", "apple.2.tf.txt"},{"orange.txt", "orange.sd.2.txt"}}
How can I achieve this with linq?
How about this:
var groupedList = firstList.GroupBy(x => secondList.Single(y => x.Name.Contains(y)));
You could group the elements of each of the original list by all possible keys using Split, SelectMany, and GroupBy with an anonymous type:
var list = new List<string> { "apple.txt", "orange.sd.2.txt", "apple.2.tf.txt", "orange.txt" };
var groups = list
.SelectMany(element => element
.Split('.')
.Select(part => new { Part = part, Full = element }))
.GroupBy(entry => entry.Part);
Now you can select the groups you want to keep using Where, and convert the results into the nested lists using Select and ToList:
var keys = new List<string> { "apple", "orange" };
var result = group
.Where(group => keys.Contains(group.Key))
.Select(group => group
.Select(entry => entry.Full)
.ToList())
.ToList();
N.B. Elements of the original list which do not contain any of the specified keys will not appear in the results, and elements which contain more than one of the specified keys will appear more than once in the result.
Edit: As #NetMage noted, I've made an incorrect assumption about splitting strings - here's another version, although it's O(m * n):
var result = keys
.Select(key => list.Where(element => element.Contains(key)).ToList())
.ToList();
This is one simple way to do it. There is many ways and this will include duplicated key as the comment i made on your question. If many key match the same data the grouping will include the copies.
// have the list of keys (groups)
var keyList = new List<string>() {"apple", "orange"};
// have the list of all the data to split
var dataToSplit = new List<string>()
{
"apple.txt",
"apple.2.tf.txt",
"orange.txt",
"orange.sd.2.txt"
};
// now split to get just as desired you select what you want for each keys
var groupedData = keyList.Select(key => dataToSplit.Where(data => data.Contains(key)).ToList()).ToList();
// groupedData is a List<List<string>>
A second option to get the values maybe in a more "object" fashion is to use anonymous. specially good if you will do lots of manipulation and it's more "verbiose" in the code. But if you are new to this i do NOT recommend that approach but anyhow this is it.
// have the list of keys (groups)
var keyList = new List<string>() {"apple", "orange"};
// have the list of all the data to split
var dataToSplit = new List<string>()
{
"apple.txt",
"apple.2.tf.txt",
"orange.txt",
"orange.sd.2.txt"
};
// create the anonymous
var anonymousGroup = keyList.Select(key =>
{
return new
{
Key = key,
Data = dataToSplit.Where(data => data.Contains(key)).ToList()
}
});
// anonymousGroup is a List<A> where keeping the order you should access all data for orange like this
var orangeGroup = anonymousGroup.FirstOfDefault(o=> o.Key = "orange"); // get the anonymous
var orangeData = orangeGroup.Data; // get the List<string> for that group
A third way with less complexity than O(m*n). The trick is to remove from the collection the data as you go to reduce the chance to recheck over item already processed. This is from my codebase and it's an extension for List that simply remove item from a collection based on a predicate and return what has been removed.
public static List<T> RemoveAndGet<T>(this List<T> list, Func<T, bool> predicate)
{
var itemsRemoved = new List<T>();
// iterate backward for performance
for (int i = list.Count - 1; i >= 0; i--)
{
// keep item pointer
var item = list[i];
// if the item match the remove predicate
if (predicate(item))
{
// add the item to the returned list
itemsRemoved.Add(item);
// remove the item from the source list
list.RemoveAt(i);
}
}
return itemsRemoved;
}
Now with that extension when you have a list you can use it easily like this :
// have the list of keys (groups)
var keyList = new List<string>() {"apple", "orange"};
// have the list of all the data to split
var dataToSplit = new List<string>()
{
"apple.txt",
"apple.2.tf.txt",
"orange.txt",
"orange.sd.2.txt"
};
// now split to get just as desired you select what you want for each keys
var groupedData = keyList.Select(key => dataToSplit.RemoveAndGet(data => data.Contains(key))).ToList();
In that case due to the order in both collection the first key is apple so it will iterate the 4 items in dataToSplit and keep only 2 AND reducing the dataToSplit collection to 2 items only being the one with orange in them. On the second key it will iterate only over 2 items which will make it faster for this case. Typically this method will be as fast or faster than the first 2 ones i provided while being as clear and still make use of linq.
You can achieve this using this simple code:
var list1 = new List<string>() {"apple.txt", "orange.sd.2.txt", "apple.2.tf.txt", "orange.txt"};
var list2 = new List<string>() {"apple", "orange"};
var result = new List<List<string>>();
list2.ForEach(e => {
result.Add(list1.Where(el => el.Contains(e)).ToList());
});
Tuples to the rescue!
var R = new List<(string, List<string>)> { ("orange", new List<string>()), ("apple", new List<string>()) };
var L = new List<string> { "apple.txt", "apple.2.tf.txt", "orange.txt", "orange.sd.2.txt" };
R.ForEach(r => L.ForEach(l => { if (l.Contains(r.Item1)) { r.Item2.Add(l); } }));
var resultString = string.Join("," , R.Select(x => "{" + string.Join(",", x.Item2) + "}"));
You can build R dynamically trivially if you need to.
I have 2 lists. First is a list of objects that has an int property ID. The other is a list of ints.
I need to compare these 2 lists and copy the objects to a new list with only the objects that matches between the two lists based on ID. Right now I am using 2 foreach loops as follows:
var matched = new list<Cars>();
foreach(var car in cars)
foreach(var i in intList)
{
if (car.id == i)
matched.Add(car);
}
This seems like it is going to be very slow as it is iterating over each list many times. Is there way to do this without using 2 foreach loops like this?
One slow but clear way would be
var matched = cars.Where(car => intList.Contains(car.id)).ToList();
You can make this quicker by turning the intList into a dictionary and using ContainsKey instead.
var intLookup = intList.ToDictionary(k => k);
var matched = cars.Where(car => intLookup.ContainsKey(car.id)).ToList();
Even better still, a HashSet:
var intHash = new HashSet(intList);
var matched = cars.Where(car => intHash.Contains(car.id)).ToList();
You could try some simple linq something like this should work:
var matched = cars.Where(w => intList.Contains(w.id)).ToList();
this will take your list of cars and then find only those items where the id is contained in your intList.
I'm trying to filter users by department. The filter may contain multiple departments, the users may belong to multiple departments (n:m). I'm fiddling around with LINQ, but can't find the solution. Following example code uses simplified Tuples just to make it runnable, of course there are some real user objects.
Also on CSSharpPad, so you have some runnable code: http://csharppad.com/gist/34be3e2dd121ffc161c4
string Filter = "Dep1"; //can also contain multiple filters
var users = new List<Tuple<string, string>>
{
Tuple.Create("Meyer", "Dep1"),
Tuple.Create("Jackson", "Dep2"),
Tuple.Create("Green", "Dep1;Dep2"),
Tuple.Create("Brown", "Dep1")
};
//this is the line I can't get to work like I want to
var tuplets = users.Where(u => u.Item2.Intersect(Filter).Any());
if (tuplets.Distinct().ToList().Count > 0)
{
foreach (var item in tuplets) Console.WriteLine(item.ToString());
}
else
{
Console.WriteLine("No results");
}
Right now it returns:
(Meyer, Dep1)
(Jackson, Dep2)
(Green, Dep1;Dep2)
(Brown, Dep1)
What I would want it to return is: Meyer,Green,Brown. If Filter would be set to "Dep1;Dep2" I would want to do an or-comparison and find *Meyer,Jackson,Green,Brown" (as well as distinct, as I don't want Green twice). If Filter would be set to "Dep2" I would only want to have Jackson, Green. I also played around with .Split(';'), but it got me nowhere.
Am I making sense? I have Users with single/multiple departments and want filtering for those departments. In my output I want to have all users from the specified department(s). The LINQ-magic is not so strong on me.
Since string implements IEnumerable, what you're doing right now is an Intersect on a IEnumerable<char> (i.e. you're checking each letter in the string). You need to split on ; both on Item2 and Filter and intersect those.
var tuplets = users.Where(u =>
u.Item2.Split(new []{';'})
.Intersect(Filter.Split(new []{';'}))
.Any());
string[] Filter = {"Dep1","Dep2"}; //Easier if this is an enumerable
var users = new List<Tuple<string, string>>
{
Tuple.Create("Meyer", "Dep1"),
Tuple.Create("Jackson", "Dep2"),
Tuple.Create("Green", "Dep1;Dep2"),
Tuple.Create("Brown", "Dep1")
};
//I would use Any/Split/Contains
var tuplets = users.Where(u => Filter.Any(y=> u.Item2.Split(';').Contains(y)));
if (tuplets.Distinct().ToList().Count > 0)
{
foreach (var item in tuplets) Console.WriteLine(item.ToString());
}
else
{
Console.WriteLine("No results");
}
In addition to the other answers, the Contains extension method may also be a good fit for what you're trying to do if you're matching on a value:
var result = list.Where(x => filter.Contains(x.Value));
Otherwise, the Any method will accept a delegate:
var result = list.Where(x => filter.Any(y => y.Value == x.Value));
My problem just got more complicated than I thought and I've just wiped out my original question... So I'll probably post multiple questions depending on how I get on with this.
Anyway, back to the problem. I need to find the index position of duplicate entries in string that contains csv data. For example,
FirstName,LastName,Address,Address,Address,City,PostCode,PostCode, Country
As you can see the Address is duplicated and I need to find out the index of each duplicates assuming first index position starts at 0.
If you have a better suggestion on how to do this, let me know, but assuming it can be done, could we maybe have with a dicitionary>?
So if I had to code this, you would have:
duplicateIndexList.Add(2);
duplicateIndexList.Add(3);
duplicateIndexList.Add(4);
myDuplicateList.Add("Address", duplicateIndexList);
duplicateIndexList.Add(6);
duplicateIndexList.Add(7);
myDuplicateList.Add("PostCode", duplicateIndexList);
Obviously I don't want to do this but is it possible to achieve the above using Linq to do this? I could probably write a function that does this, but I love seeing how things can be done with Linq.
In case you're curious as to why I want to do this? Well, in short, I have an xml definition which is used to map csv fields to a database field and I want to first find out if there are any duplicate columns, I then want to append the relevant values from the actual csv row i.e. Address = Address(2) + Address(3) + Address(4), PostCode = PostCode(6) + PostCode(7)
The next part will be how to remove all the relevant values from the csv string defined above based on the indexes found once I have appended their actual values, but that will be the next part.
Thanks.
T.
UPDATE:
Here is the function that does what I want but as I said, linq would be nice. Note that in this function I'm using a list instead of the comma separated string as I haven't converted that list yet to a csv string.
Dictionary<string, List<int>> duplicateEntries = new Dictionary<string, List<int>>();
int indexPosition = 0;
foreach (string fieldName in Mapping.Select(m=>m.FieldName))
{
string key = fieldName.ToUpper();
if (duplicateEntries.ContainsKey(key))
{
List<int> indexes = duplicateEntries[fieldName];
indexes.Add(indexPosition);
duplicateEntries[key] = indexes;
indexes = null;
}
else
{
duplicateEntries.Add(key, new List<int>() { indexPosition });
}
indexPosition += 1;
}
Maybe this will help clarify what I'm trying to achieve.
You need to do the following:
Use .Select on the resulting array to project a new IEnumerable of objects that contains the index of the item in the array along with the value.
Use either ToLookup or GroupBy and ToDictionary to group the results by column value.
Seems like an ILookup<string, int> would be appropriate here:
var lookup = columnArray
.Select((c, i) => new { Value = c, Index = i })
.ToLookup(o => o.Value, o => o.Index);
List<int> addressIndexes = lookup["Address"].ToList(); // 2, 3, 4
Or if you wanted to create a Dictionary<string, List<int>>:
Dictionary<string, List<int>> dictionary = columnArray
.Select((c, i) => new { Value = c, Index = i })
.GroupBy(o => o.Value, o => o.Index)
.ToDictionary(grp => grp.Key, grp => grp.ToList());
List<int> addressIndexes = dictionary["Address"]; // 2, 3, 4
Edit
(in response to updated question)
This should work:
Dictionary<string, List<int>> duplicateEntries = Mapping
.Select((m, i) => new { Value = m.FieldName, Index = i })
.GroupBy(o => o.Value, o => o.Index)
.ToDictionary(grp => grp.Key, grp => grp.ToList());
You could do something like :
int count = 0;
var numbered_collection =
from line in File.ReadAllLines("your_csv_name.csv").Skip(1)
let parts = line.Split(',')
select new CarClass()
{
Id = count++,
First_Field = parts[0],
Second_Field = parts[1], // rinse and repeat
};
This gives you Id's per item. (and also skip the first line which has the header). You could put it in a method if you want to automatically map the names from the first csv line to the fields).
From there, you can do:
var duplicates = (from items in numbered_collection
group items by items.First_Field into g
select g)
.Where(g => g.Count() > 1);
Now you have all the groups where you actually have duplicates, and you can just get the 'Id' from the object to know which one is the duplicated.
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) );
}
}