I have a list of items. For example (although the list could be any length):
var inputList = new List<Input1>()
{
new Input1() { Test = "a" },
new Input1() { Test = "b" }
};
What I want to do is create a new list of:
a1, a2, b8, b9
That is the value of Test (i.e. a) with a suffix based on the value of Test.
In that order. Obviously, this is a minimum workable example, not the actual problem. So I'd like to use something like the .Select to split the data - something like this:
var outputList = inputList.Select(x =>
{
if (x.Test == "a")
{
return new Input1() { Test = "a1" };
//return new Input1() { Test = "a2" };
}
else if (x.Test == "b")
{
return new Input1() { Test = "b8" };
//return new Input1() { Test = "b9" };
}
else
{
return x;
}
});
Input1 for completeness:
class Input1
{
public string Test { get; set; }
}
That is, to return a list that contains items that were not in the original list.
I realise I can use a foreach, but I'm interested if there's a better / more concise way.
Suppose you have a method that transforms your single input into multiple inputs:
public static Input1[] Transform(Input1 x)
{
if (x.Test == "a") return new[] {new Input1("a1"), new Input1("a2")};
if (x.Test == "b") return new[] {new Input1("b8"), new Input1("b9")};
return new[] {x};
}
(This is just from your toy example - I guess you actually need a transformation that is more meaningful.)
Then you can just use SelectMany to get your desired result in the correct order:
inputList
.SelectMany(Transform);
If you're using C# 8.0 or above, you may use switch expression as follows:
var outputList =
inputList.SelectMany(x => x.Test switch
{
"a" => new[] { new Input1() { Test = "a1" }, new Input1() { Test = "a2" } },
"b" => new[] { new Input1() { Test = "b8" }, new Input1() { Test = "b9" } },
_ => new[] { x }
})
.ToList();
I have a class like the following:
public class MyData
{
public int Key { get; set; }
public string MyString { get; set; }
public bool MyFlag { get; set; }
}
I have a list of these classes:
var data = new List<MyData>();
Which is populated as follows:
data.Add(new MyData() { Key = 1, MyFlag = true, MyString = "Hello" });
data.Add(new MyData() { Key = 1, MyFlag = false, MyString = "Goodbye" });
data.Add(new MyData() { Key = 2, MyFlag = true, MyString = "Test" });
data.Add(new MyData() { Key = 2, MyFlag = false, MyString = "Merge" });
data.Add(new MyData() { Key = 3, MyFlag = false, MyString = "Data" });
What I want is a list as follows:
Key true false
1 Hello Goodbye
2 Test Merge
3 Data
I need an anonymous type that reflects the three values above. I found a few posts and articles that seemed to suggest GroupJoin, but I'm unsure how I could use that in this case as it seems to allow joining two separate lists.
I suggest grouping (by Key property). If you want true and false properties we have to put it as #true and #false since true and false are keywords:
var result = data
.GroupBy(item => item.Key)
.Select(chunk => new {
Key = chunk.Key,
#true = chunk.FirstOrDefault(item => item.MyFlag)?.MyString,
#false = chunk.FirstOrDefault(item => !item.MyFlag)?.MyString,
});
Please note that I am not in front of a PC right now (so I could not test it), but this should give you an idea at the very least.
data
.GroupBy(x => x.Key)
.Select(
x => new {
Key = x.Key,
True = data.SingleOrDefault(y1 => y1.Key == x.Key && y1.MyFlag)?.MyString,
False = data.SingleOrDefault(y2 => y2.Key == x.Key && !y2.MyFlag)?.MyString
});
Given the follow class structure:
int Id;
string[] Codes;
And the following data:
Foo { Id = 1, Codes = new[] { "01", "02" } }
Foo { Id = 2, Codes = new[] { "02", "03" } }
Foo { Id = 3, Codes = new[] { "04", "05" } }
I would like to end up with the following structure.
Code = "01", Id = 1
Code = "02", Id = 1
Code = "02", Id = 2
Code = "03", Id = 2
Code = "04", Id = 3
Code = "05", Id = 3
I've got the following query, but it's giving me a collection as the Id rather than the flat structure I am after.
collection.GroupBy(f => f.Codes.SelectMany(c => c), f => f.Id,
(code, id) => new { Code = code, Id = id })
.ToArray()
What am I missing?
SelectMany can return multiple elements for each item as a single list
items
.SelectMany(foo => foo.Codes.Select(code => new { Id = foo.Id, Code = code }));
The answer of Diego Torres is correct; I would only add to it that this query is particularly concise and readable in the comprehension form:
var q = from foo in foos
from code in foo.Codes
select new { Code = code, foo.Id };
When I use this comparer in Distinct() it always returns false. Can't see a reason why.
public class IdEqualityComparer : IEqualityComparer<Relationship>
{
public bool Equals(Relationship x, Relationship y)
{
if (x == null && y == null)
return true;
else if (x == null || y == null)
return false;
else if (x.ID == y.ID && x.RelatedID == y.RelatedID)
return true;
else
return false;
}
public int GetHashCode(Relationship obj)
{
unchecked
{
int hash = (obj.ID ?? "").GetHashCode() ^ (obj.RelatedID ?? "").GetHashCode();
return hash;
}
}
}
The hash seems correct to me, but the ID and RelatedID comparison never returns true.
It fails, as I can check the result afterward and the output is not distinct using those two properties.
Apologies to all! It is working fine.
I was comparing objects defined similarly to Marc's answer. But, I did this:
var relators = relationships.Distinct(new RelationshipEqualityComparer());
and then sent relationships, rather than realtors, to the next method where the items were reviewed. Sometimes it takes another pair of eyes!
Thanks!
Seems to work fine here;
static void Main()
{
var objs = new[]
{
new Relationship { ID = "a", RelatedID = "b" }, // <----\
new Relationship { ID = "a", RelatedID = "c" }, // |
new Relationship { ID = "a", RelatedID = "b" }, // dup--/
new Relationship { ID = "d", RelatedID = "b" }, // <------\
new Relationship { ID = "d", RelatedID = "c" }, // |
new Relationship { ID = "d", RelatedID = "b" }, // dup ---/
new Relationship { ID = "b", RelatedID = "c" }, //
};
var count = objs.Distinct(new IdEqualityComparer()).Count();
System.Console.WriteLine(count);
}
gives 5, not 7 (which we would expect if it always returned false). Tested with:
public class Relationship
{
public string ID { get; set; }
public string RelatedID { get; set; }
}
To illustrate this more clearly:
var a = new Relationship { Id = "x", RelatedID = "foo" };
var b = new Relationship { Id = "y", RelatedID = "foo" };
var c = new Relationship { Id = "x", RelatedID = "foo" };
we can now demonstrate that the comparer returns true and false appropriately:
var comparer = new IdEqualityComparer();
Console.WriteLine(comparer.Equals(a, b)); // False
Console.WriteLine(comparer.Equals(a, c)); // True
we can also show that the hash-code is working appropriately:
Console.WriteLine(comparer.GetHashCode(a));
Console.WriteLine(comparer.GetHashCode(b));
Console.WriteLine(comparer.GetHashCode(c));
note that the numbers will change, but for me on this run this gives:
-789327704
1132350395
-789327704
The numbers don't matter - what matters is that the first and last are equal, and (ideally) different from the middle one.
So: the comparer is working fine, and the premise of the question is incorrect. You need to identify in your code what is different, and fix it.
In C#,I have List of Employee object. Employee class is
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
}
In List objected are sorted based on Employee.ID. I have an array of int which is basically Employee.ID which I want on top of the list and in list,order must remain same as in array.
If I hava input like this
List:
[
{ID:1,Name:A},
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
and Array: {2,3,1}
Then I want Output List:
[
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:1,Name:A},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
And I have done this
foreach (int i in a)
{
list = list.OrderBy(x => x.ID != i).ToList();
}
//a is array
//list is List
Any better Solution.Thanks in advance.
After you got your list sorted based on the ID just iterate the array and move the elements. In order to do this you need to first remove and then insert the item at the correct position.
for(int i = 0; i < myArray.Length; i++)
{
var e = myList.Single(x => x.Id == myArray[i]);
myList.Remove(e);
myList.Insert(i, e);
}
You may also want to use SingleOrDefault instead of Single to verify that myList even contains the element with the current id, e.g. when your array contains [2, 3, 101]
To add another version to the mix. The complete sorting can be done in one go:
list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? int.MaxValue : i; }).ToList();
where list is the EmployeeList and a the indices array. (NB, the for loop is not needed, the above should do both sortings).
Inside the OrderBy callback, if the id is not inside a, int.MaxValue is returned to place it after the ones inside the array (a.Length would work as well). OrderBy should maintain the original order of the enumeration (list) for those elements that return the same value.
PS, if you want to sort first by index inside a and the rest on the ids (not necessarily the original order), you can use the following (as long as a.Length + largest ID < int.MaxValue) : list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? a.Length + e.ID : i; }).ToList();
Here's a way to do it in pure LINQ, without changing the original sequence.
Broken into steps to see what's going on.
public static void Main()
{
var employeeList = new List<Employee>()
{
new Employee(){ ID= 1,Name= "A"},
new Employee() { ID= 2,Name= "B"},
new Employee() { ID= 3,Name= "AA"},
new Employee() { ID= 4,Name= "C"},
new Employee() { ID= 5,Name= "CD"},
new Employee() { ID= 100,Name= "Z"}
};
var orderByArray = new int[] { 2, 3, 1, 100, 5, 4 };
var sortPos = orderByArray.Select((i, index) => new { ID = i, SortPos = index });
var joinedList = employeeList.Join(sortPos, e => e.ID, sp => sp.ID, (e, sp) => new { ID = e.ID, Name = e.Name, SortPos = sp.SortPos });
var sortedEmployees = joinedList.OrderBy(e => e.SortPos).Select(e => new Employee { ID = e.ID, Name = e.Name });
}
Try this using LINQ:
List<Employee> employees = ...
int[] ids = ...
var orderEmployees = ids.Select(id => employees.Single(employee => employee.ID == id))
.Concat(employees.Where(employee => !ids.Contains(employee.ID)).ToList();
Foreach id in ids array we will grab the matching employee and we will concat to it all the employees that their id does not exist in ids array.
I like to use a special Comparer for that, it seems clearer to me, though a bit more code. It hides the complexity of the sort in the comparer class, and then you can just call it with :
theList.OrderBy(x => x.id, new ListOrderBasedComparer(sortList));
It will sort according to any list passed to the comparer when instantiating, and will put elements not in the "known sort list" at the end.
You can of course adapt it to your special needs.
public class ListOrderBasedComparer: Comparer<int>
{
private List<int> sortList;
public ListOrderBasedComparer(List<int> sortList)
{
// if you want you can make constructor accept arrays and convert it
// (if you find that more convenient)
this.sortList = sortList;
}
public override int Compare(int x, int y)
{
var indexOfX = sortList.FindIndex(a => a == x);
var indexOfY = sortList.FindIndex(a => a == y);
// handle elements not in sortArray : if not in sort array always assume they should be "less than the others" and "equal between them".
if (indexOfX == -1 && indexOfY == -1) return 0;
if (indexOfY == -1) return -1;
if (indexOfX == -1) return 1;
// if elements are in sortArray (FindIndex returned other than -1), use usual comparison of index values
return indexOfX.CompareTo(indexOfY);
}
}
Example on how to use it, with Linq :
public class TestCompare
{
public void test ()
{
var myArray = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var myArray2 = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 0, name = "X" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 23, name = "Z"},
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var sortList = new List<int> { 2, 3, 1, 4, 5, 6 };
// good order
var mySortedArray = myArray.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
// good order with elem id 0 and 23 at the end
var mySortedArray2 = myArray2.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
}
}
public class MyClass
{
public int id;
public string name;
}