Basically I have a list containing all items. Then I have a string containing the IDs I want to grab out from the list, to do this I split the string into an Int array and later use this and LINQ to get the items I want from the list
Like this :
List<T> lstAllList = Model.getAllItems();
string stringIDs = "8,9,12,11,7";
int[] intArray = stringIDs.Split(',').Select(n => Convert.ToInt32(n)).ToArray();
List<T> lstLimitedList = (from r in lstAllList where intArray.Contains(r.id) select r).ToList();
Which works great.
But the issue here is that I want to have my list ordered in the same way as the string of IDs is, i.e 8,9,12,11,7 like in this example.
But the returned list from the LINQ sorts it by the id by default, so the returned list is 7,8,9,11,12.
Is there a way to prevent it from sorting it like this or is there a way to sort the new list with my int array?
Sure, just sort by the index of the ID in the array:
string stringIDs = "8,9,12,11,7";
int[] intArray = stringIDs.Split(',').Select(n => Convert.ToInt32(n)).ToArray();
var lstLimitedList = (
from r in lstAllList
where intArray.Contains(r.id)
orderby Array.IndexOf(intArray, r.id) // <--------
select r).ToList();
Simply getting the elements one at a time may be faster than trying to resort. (Anyone willing to calculate the O() costs?)
List<T> lstLimitedList = new List<T>();
foreach(int id in intArray)
{
lstLimitedList.Add(lstAllList.Where(item => item.id = id));
}
You could also use intArray.ForEach() if you're a LINQ maniac, but this is much easier to read. ;)
Try to rotate your query. Under word rotate I mean start with intArray and use join. Something like this:
List<T> lstLimitedList = (
from id in intArray
join item in lstAllList on id equals item.Id
select item).ToList();
Id use the intersect extension method with provided by LINQ!
int[] array ={ 8,9,12,11,7} // or your result from your split on string;
List<int> array2 = new List<int> { 8,9,12,11,7 } // your model.GetAllItems;
// Call Intersect extension method.
var intersect = array.Intersect(array2);
// Write intersection to screen.
foreach (int value in intersect)
{
Console.WriteLine(value); // Output: 8,9,12,11,7
}
Bit cleaner for me
Stop overusing LINQ guys. In this case linq is a total overkill. A much simpler and better performance-wise solution is the following:
string a = "8,9,12,11,7";
List<int> list = new List<int>();
string[] splitted = a.Split(',');
for (int i = 0; i < splitted.Length; i++)
{
list.Add(int.Parse(splitted[i]));
}
Which a single loop and without sorting etc.
Related
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.
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.
int[] OrderedListToFollow = {1,2,4,5}
int[] ListB = {2,3,4,8,9}
Based on the two lists above I need to sort ListB based on the order defined in OrderedListToFollow. Since 3,8,9 are not part of the OrderedListToFollow those can appear in any order, so the acceptable solutions could have any of the following :
int[] ListB = {2,4,3,8,9}
int[] ListB = {2,4,8,3,9}
int[] ListB = {2,4,9,3,8}
I tried doing this as follows but it does not order it :
ListB = ListB.OrderBy(id => OrderedListToFollow.ToList().IndexOf(id)).ToArray();
EDIT
The order above works but it places the items not present in OrderedListToFollow first and then the remaining.
The problem with your sorting method is that the IndexOf method returns -1 if the item is not found. Thus, all your items which exist "outside" the given ordering scheme are placed at the beginning of the collection because they get an index of -1.
You could try using a conditional to return the index if found and the current index otherwise:
var c = ListB.Count();
ListB = ListB
.OrderBy(id => OrderedListToFollow.Contains(id)
? OrderedListToFollow.ToList().IndexOf(id)
: c + 1 // This will always pace invalid objects at the end
);
As noted, it was already working apart from putting the results at the beginning. To fix this, I'd add an extension method:
public static int IndexOfOrMax(this IEnumerable<T> source, T item)
{
int index = source.IndexOf(item);
return index == -1 ? int.MaxValue : index;
}
Also note that you don't need to call ToList on OrderedListToFollow - currently you're calling that a lot, which is very inefficient. With the above extension method in place, you can use:
int[] orderedListToFollow = {1,2,4,5};
int[] listB = {2,3,4,8,9};
listB = listB.OrderBy(id => orderedListToFollow.IndexOfOrMax(id)).ToArray();
How about something like this:
int[] OrderedListToFollow = {1,2,4,5};
int[] ListB = {2,3,4,8,9};
List<int> ListC = new List<int>();
ListC.AddRange(OrderedListToFollow.Where(p => ListB.Contains(p)));
ListC.AddRange(ListB.Where(p => !OrderedListToFollow.Contains(p)));
This will give you a result like this :
2
4
3
8
9
You can do this using Join and Except, the benefit being that they're both ~O(n) operations (due to both using Hashtables in their implementations). This snippet hinges on the assumption that OrderedListToFollow is indeed ordered.
int[] OrderedListToFollow = new[]{1,2,4,5};
int[] ListB = new[]{3,4,8,2,9};
var existing = from o in OrderedListToFollow
join l in ListB on o equals l
select l;
var other = ListB.Except(OrderedListToFollow);
var result = existing.Concat(other);
I need an easy way to convert a List<int> to a string array.
I have:
var the_list = new List<int>();
the_list.Add(1);
the_list.Add(2);
the_list.Add(3);
string[] the_array = new string[the_list.Count];
for(var i = 0 ; i < the_array.Count; ++i)
the_array[i] = the_list[i].ToString();
...which looks to be very ugly to me.
Is there an easier way?
Note: I'm looking for an easier way - not necessarily a faster way.
Use LINQ:
string[] the_array = the_list.Select(i => i.ToString()).ToArray();
I know you have a good answer, but you don't need LINQ or Select. You can do it with a ConvertAll and an anonymous method. Like this:
var list = new List<int>();
....
var array = list.ConvertAll( x => x.ToString() ).ToArray();
Similar idea, but I think this is not linq. in case that matters.
Sorry, I don't have .NET installed on this machine, so totally untested:
var theList = new List<int>() { 1, 2, 3 };
var theArray = theList.Select(e => e.ToString()).ToArray(); // Lambda Form
var theArray = (from e in theList select e.ToString()).ToArray(); // Query Form
List has a ToArray() method. It will save you typing but probably won't be more efficient.
Because your list only has a number, you can easily convert them to a string. Just create a loop and convert its members to the string.
string[] the_array = new string[the_list.Count];
int i=0;
foreach(var item in the_list)
{
the_array[i] = item.ToString();
i++;
}
I have a DB table that contains a comma separated list of ID's (ints) that are stored as nvarchar.
I have a get method to return these in one hit as a list. At the moment I think I'll have to do something like this:
List<int> ids = new List<int>();
string[] pageids = experssion.Split(separators)
foreach (string number in pageids)
{
ids.Add(Convert.ToInt32(number));
}
Can anyone think of a nicer way to do this ? Can I do it all on the split somehow ?
I'd to it like this:
var ids = expression.Split(separator).Select(s => int.Parse(s));
It uses the Linq extensions of .NET 3.0. As an alternative (saves one lambda), you could also do this which is arguably less readable:
var ids = expression.Split(separator).Select((Func<string, int>)int.Parse);
If you're not using C# 3.0, or aren't a fan of LINQ, you could do it the C# 2.0 way:
// This gives you an int[]
int[] pageids = Array.ConvertAll(expression.Split(separator), new Converter<string,int>(StringToInt));
// Add each id to the list
ids.AddRange(pageids);
public static int StringToInt(string s)
{
return int.Parse(s);
}
EDIT:
Or, even simpler as per Konrad's suggestion:
int[] pageids = Array.ConvertAll<string,int>(expression.Split(separator),int.Parse);
ids.AddRange(pageids);
With LINQ you can do this:
List<int> ids
= expression
.Split(separators)
.Select(number => Convert.ToInt32(number))
.ToList()