I have two arrays of student names and test scores.
Each array contains only distinct students (no duplicates) and is structured such that arrStudentGroup1[0][0] = "Bob" and arrStudentGroup1[0][1] = "98".
Given two arrays is it possible to use Intersect to create a third array of students that exists in both arrStudentGroup1 and arrStudentGroup2?
I'd like the third array to have their names and test scores. How do I do this?
If you want just the names students that are in both groups and not the associated test scores, just intersect on the name array element:
var students1 = arrStudentGroup1.Select(group => group[0]);
var students2 = arrStudentGroup2.Select(group => group[0]);
var studentsInBoth = students1.Intersect(students2);
If you also want the associated test scores you'll need to implement an IEqualityComparer<T> that compares the first element of each array.
If you want the associated test scores then join the two arrays:
var intersection = from s1 in arrStudentGroup1
join s2 in arrStudentGroup2 on s1[0] equals s2[0]
select new {Name = s1[0], Score1 = s1[1], Score2 = s2[1]}
foreach (var item in intersection)
{
Console.Writeline("{0}: s1={1}, s2={2}", Name, Score1, Score2);
}
First zip (in the general sense, rather than the Zip method) the arrays into a structure that groups students with scores:
Given:
string[][] arrStudentGroup1 = new string[][]{new string[]{"Bob","98"}, new string[]{"Alice","98"}, new string[]{"Charles","78"}, new string[]{"Dariah","99"}};
string[][] arrStudentGroup2 = new string[][]{new string[]{"Bob","98"}, new string[]{"Fiona","98"}, new string[]{"Eve","78"}, new string[]{"Dariah","99"}};
Then:
var zipped1 = arrStudentGroup1.Select(student => new {Name = student[0], Score = student[1]});
var zipped2 = arrStudentGroup2.Select(student => new {Name = student[0], Score = student[1]});
Now get the intersection. Note that if the same student name was in one but with a different score, it would not count as an intersection. That can be dealt with too, but I'm interpreting your question as not wanting that case. Let me know if I read you wrong:
var inter = zipped1.Intersect(zipped2);
Now, you can ideally work with this anyway, or even with new {Name = student[0], Score = int.Parse(student[1])} above and have a number instead of a string (more useful in most cases), which frankly is nicer than dealing with an array of arrays, along with being more typesafe. Still, if you really want it in the same format string[] format:
var interArray = inter.Select(st => new string[]{st.Name, st.Score});
And if you really, really want the whole thing in the same string[][] format:
var interArrays = interArray.ToArray();
Or for the one-line wonder (less good readability mostly, but sometimes it's nice to put a query on one line if there's other things going on in the same method):
var interArrays = arrStudentGroup1
.Select(student => new {Name = student[0], Score = student[1]})
.Intersect(
arrStudentGroup2
.Select(student => new {Name = student[0], Score = student[1]})
).Select(st => new string[]{st.Name, st.Score}).ToArray()
Output:
{"Bob", "98"},{"Dariah", "99"}
Edit: Alternatively, define an IEqualityComparer<string[]> like:
public class StudentComparer : IEqualityComparer<string[]>
{
public bool Equals(string[] x, string[] y)
{
if(ReferenceEquals(x, y))
return true;
if(x == null || y == null)
return false;
return x.SequenceEqual(y);
}
public int GetHashCode(string[] arr)
{
return arr == null ? 0 : arr.Select(s => s == null ? 0 : s.GetHashCode()).Aggregate((x, y) => x ^ y);
}
}
Then just use it directly:
var intersection = arrStudentGroup1.Intersect(arrStudentGroup2, new StudentComparer());
Gives the same output. Simpler really, but my instincts upon seeing arrays being used as objects was to get it into a real object as soon as I can, and really, it's not a bad instinct - it can make much else easier too.
Well, you could do somthing like this,
var studentsInGroup1 = arrStudentGroup1.Select(s => new
{
Name = s[0],
Score = s[1]
});
var studentsInGroup2 = arrStudentGroup2.Select(s => new
{
Name = s[0],
Score = s[1]
});
var studentsInBothGroups = studentsInGroup1.Join(
studentsInGroup2,
s => s.Name,
s => s.Name,
(one, two) => new
{
Name = one.Name,
Scores = new[] { one.Score, two.Score }
});
Which should give you a handy anonymous type you can access like this.
foreach(var student in studentsInBothGroups)
{
var Name = student.Name;
var Group1Score = student.Scores[0];
var Group2Score = student.Scores[1];
}
Related
Starter code: https://dotnetfiddle.net/lJhMyo
string[] names = { "Burke", "Laptop", "Computer",
"Mobile", "Ahemed", "Sania",
"Kungada", "David","United","Sinshia" };
var empList = new List<Employee> {
new Employee {Name = "Burke", ID = "IHED123"},
new Employee {Name = "David", ID = "QIUHD454"},
new Employee {Name = "Batman", ID = "OIWQE565"},
};
How do I construct a linq query (method syntax) that gets all Employee objects where employee name is in the "names" array?
If there is a string in "names" that is not in empList, throw exception.
EDIT: What if empList is large and I want case-insensitive match for Name?
You can use .Contains. ie:
var result = empList.Where(x => names.Contains(x.Name));
You can check if there is a missing name:
bool noneMissing = !names.Any(n => empList.Any(x => x.Name == n));
.Contains Tests if an array or list contains the item:
var Searched = emptList.Where(x => names.Contains(x.Name));
If Searched.Length == 0, so no Items
If you want performance use for loop (nothing is more performant) and for case insensitive use StringComparer.OrdinalIgnoreCase.
List<string> Searched = new List<string>;
for(int i = 0; i < names.Length; i++)
{
if(emptList.contains(name[i], StringComparer.OrdinalIgnoreCase)
Searched.Add(name[i]);
}
For large lists and case-insensitive comparison you can use HashSet with provided IEqualityComparer<string> :
var hashSet = new HashSet<string>(names, StringComparer.OrdinalIgnoreCase);
empList.Where(e => hashSet.Contains(e.Name));
And possibly moving from LINQ to for loop.
public Class Car
{
public string Name {get;set;}
public string Color {get;set;}
}
Car1 = new {"Ford", "White"}
Car2 = new {"Ford Fiesta", "Blue"}
Car3 = new {"Honda City", "Yellow"}
Car4 = new {"Honda", "White"}
var carObj = new List<Car>();
carObj.Add(Car1);
carObj.Add(Car2);
carObj.Add(Car3);
carObj.Add(Car4);
I need to filter output based on car names, so that if a name is a subset of any names present, it will remove them.
Output:
Car2 = new {"Ford Fiesta", "Blue"}
Car3 = new {"Honda City", "Yellow"}
Here is what I am writing to do this, but it is not giving me the desired output.
// Remove duplicare records.
var carObjNEW = new List<Car>();
carObjNEW = carObj;
carObj.RemoveAll(a => carObjNEW.Any(b => a.Name.Contains(b.Name)));
Any help on how to fix this.
As I pointed out in the comment you have a simple mistake. You need to remove item if list has another item that contains the first item as a substring:
carObj.RemoveAll(a => carObj.Any(b => b.Name.ToLower().Contains(a.Name.ToLower()) && b.Name.Length > a.Name.Length));
If you want to compare by StringComparison, just swap Contains to IndexOf(String value, StringComparison comparisonType) > 0
You don't need to work with two lists. Just put this:
carObj.RemoveAll(a =>
carObj.Any(b => b.Name.Contains(a.Name) && b.Name.Length > a.Name.Length));
project it and then filter
carObj.Select(c => new { c, Match = carObj.Any(cc => cc.Name != c.Name && cc.Name.Contains(c.Name))}).Where(n => !n.Match)
Let's say I have the following two lists..
var unscoredList = new List<string> { "John", "Robert", "Rebecca" };
var scoredList = new List<WordScore>
{
new WordScore("John", 10),
new WordScore("Robert", 40),
new WordScore("Rebecca", 30)
};
Is there a way I can sort the values in unscoredList by the values in scoredList where the word with the highest score appears first in unscoredList?
Below is the WordScore class if required..
public class WordScore {
public string Word;
public int Score;
public WordScore(string word, int score) {
Word = word;
Score = score;
}
}
If you don't need an in-place sort, you can do this:
var scoreLookup = scoredList.ToDictionary(l => l.Word, l => l.Score);
var result = unscoredList.OrderByDescending(l => scoreLookup[l]);
Alternatively, you can use:
unscoredList.Sort((l,r) => scoreLookup[r].CompareTo(scoreLookup[l]));
And of course, there should be some sanity checks done (duplicate values in scoredList, values in unscoredList which are not in scoredList, etc).
var test = unscoredList.OrderByDescending(x => scoredList
.Where(y => y.Word == x.ToString())
.Select(z => z.Word)
.FirstOrDefault()).ToList();
This returns another list, but not a big deal.
class MyClass
{
string identifier;
}
I have two lists of identical count
List<MyClass> myClassList;
List<string> identifierList;
I would like to know what is the best way of assigning identifier in the myClassList enumerating over identifierList?
I wrote this, but looks too long(and inefficient?). There must be a better way of doing this?
identifierList.Select((value,index)=>new {value, index}).ToList().ForEach(x=> myClassList[x.index].identifier = x.value);
Yes, you're approach is inefficient(you're creating a throwaway list) and not very readable and also throws an exception if both lists have a different size.
You're looking for the Enumerable.Zip extension method which joins by index:
var zipped = myClassList.Zip(identifierList, (c, s) => new { class = c, string = s});
foreach(var x in zipped)
x.class.identifier = x.string;
You can use Zip:
myClassList.Zip(identifierList, (klass, id) =>
{
klass.identifier = id;
return klass
});
Note that this will give you a mutating Linq expression.
However, I suspect this is probably clearer:
for(int i = 0; i < myClassList.Count; i++)
{
myClassList[i].identifier = identifierList[i];
}
It may not be as "hip" as using Linq, but it's easier to read and understand what's going on!
Well, how about simple query like this.
myClassList.Select((e, i) => { e.identifier = identifierList[i]; return e; }).ToList();
We need this .ToList() to execute the query, otherwise it'll just do nothing.
Example :
List<string> identifierList = new List<string>()
{
"abc", "def", "ghi"
};
List<MyClass> myClassList = new List<MyClass>()
{
new MyClass(), new MyClass(), new MyClass(),
};
myClassList.Select((e, i) => { e.Id = identifierList[i]; return e; }).ToList();
foreach (var item in myClassList)
Console.Write(item.Id + " ");
Output : abc def ghi
In case your collections are of different length you can use :
myClassList.Select((e, i) => { e.Id = i < identifierList.Count? identifierList[i] : e.Id; return e; }).ToList();
Scenario: I have a student list which comprise of name(distinct) and mark. Once user enters the name, I have to display the appropriate mark in console application. How to get the next element after fine the name.
Example:
// User enters like follows:
rice
50
john
60
pat
70
// If user enter "john" then--
60 // should display
Using LINQ I have found the key element in the list
var item = students.Find(x => x.Name == keyname);
Assuming that you have a class:
public class Student
{
public string Name {get;set;}
public int Mark {get;set;}
}
And you have a list that looks like:
List<Student> students = new List<Student>
{
new Student{Name = "Markus", Mark = 2},
new Student{Name = "Stephen", Mark = 1}
};
And you want to get the mark of Stephen its:
Student stephen = students.FirstOrDefault(s=> s.Name == "Stephen"); //if you have an unique property i you can use .SingleOrDefault in the same manner
if(stephen != null) //could be that the student is not even in the list
{
int markofStephen = stephen.Mark;
}
now to enter in this new DataType you can do:
List<Student> students = new List<Student>()
do
{
Console.Write("Please enter the Name of the Student(enter \"finish\" to exit):");
string name = Console.ReadLine();
if(name.ToLower() == "finish" )
break;
bool validMark = false;
do
{
Console.Write("Please enter the Mark of {0}:", name);
string markString = Console.ReadLine();
int mark;
validMark = int.TryParse(markString,out mark);
if(!validMark)
Console.WriteLine("Invalid Number Entered");
else
students.Add(new Student{Name=name, Mark = mark});
} while (!validMark)
} while (true)
Take a look at OOP
http://www.codeproject.com/Articles/22769/Introduction-to-Object-Oriented-Programming-Concep
Then you can create a Class or Struct that would define rice and 50.
i.e
public class Student
{
public string Name {get;set;}
public double Mark {get;set;}
}
Then via Console you can construct a new Student and then store it in a list
var item = students.Where(x => x.Name == keyname).Select(x=>x.Mark).FirstOrDefault();
Console.WriteLine(item);
//or
var item = students.Where(x => x.Name == keyname).FirstOrDefault();
if (item != null)
Console.WriteLine(item.Mark);
//or
var item = students.FirstOrDefault(s=> s.Name == keyname);
if (item != null)
Console.WriteLine(item.Mark);
Using classes or structures you can achieve better control that storing all values in a list or array
Do the below code:
var itemIndex = students.IndexOf(keyname);
And then the index of the value will be var value = students.ElementAt(itemIndex+1);.
You keep scores and names in the same list so I assume both are strings. In my implementation I take the list of names and scores, take each 2 items, create a tuple, and on a list of tuples I search for one where first item matches your key and return the second item (the score):
List<string> testList = new List<string> { "rice", "50", "john", "60", "pat", "70" };
string myKey = "rice";
var result =
testList.Zip(testList.Skip(1), (a, b) => Tuple.Create(a, b))
.Where(t => t.Item1 == myKey)
.First()
.Item2;
Or you can use anonymous types instead of a tuple:
var result =
testList.Zip(testList.Skip(1), (a, b) => new {Name = a, Score = b})
.Where(t => t.Name == myKey)
.First()
.Score;
Of course, the assumption is that each name always has a matching score next to it (as that was the condition described in your question).
This should work
var item = students.SkipWhile(x => x.Name != keyname).Skip(1).FirstOrDefault();