I have a list of bool, and a list of strings. I want to use IEnumerable.Zip to combine the lists, so if the value at each index of the first list is true, the result contains the corresponding item from the second list.
In other words:
List<bool> listA = {true, false, true, false};
List<string> listB = {"alpha", "beta", "gamma", "delta"};
IEnumerable<string> result = listA.Zip(listB, [something]);
//result contains "alpha", "gamma"
The simplest solution I could come up with is:
listA.Zip(listB, (a, b) => a ? b : null).Where(a => a != null);
...but I suspect there's a simpler way to do this. Is there?
I think this is simpler:
listA
.Zip(listB, (a, b) => new { a, b } )
.Where(pair => pair.a)
.Select(pair => pair.b);
That logically separates the steps. First, combine the lists. Next, filter. No funky conditionals, just read it top to bottom and immediately get it.
You can even name it properly:
listA
.Zip(listB, (shouldIncludeValue, value) => new { shouldIncludeValue, value } )
.Where(pair => pair.shouldIncludeValue)
.Select(pair => pair.value);
I love self-documenting, obvious code.
This is as short as I could get it:
var items = listB.Where((item, index) => listA[index]);
Where has an overload that provides the index. You can use that to pull the corresponding item in the bool list.
listA.Zip(listB, (a, b) => new { a, b }).Where(x => x.a).Select(x => x.b);
It uses anonymous type to handle Zip method subresults.
You don't need to use Zip if you can index into listA:
var res = listB.Where((a, idx) => listA[idx]);
Related
I'm new to lambda expressions but I think they look great in code, but I'm having problems understanding how to convert a foreach loop into a lambda. I can't understand the other examples I've seen here.
The code I'm trying to convert is:
{
var a = arr.ToList();
var b = a.Distinct().ToList();
if(a.SequenceEqual(b)){return -1;}
foreach(int i in b)
{
a.Remove(i);
}
return a.First();
}
Specifically the
foreach(int i in b)
{
a.Remove(i);
}
Also as I'm here, is if(a.SequenceEqual(b)){return -1;} an okay thing to do? I felt bad using like 4 lines when it could be only 1.
If the goal is to find the first item in the collection that has a duplicate, or to return -1 when there are no duplicates, then it can be done with two lines
int FindFirstDuplicate(IEnumerable<int> arr)
{
HashSet<int> seen = new();
return arr.SkipWhile(e => seen.Add(e)).DefaultIfEmpty(-1).First();
}
But these two lines cannot be written as expression-bodied function.
If there is a goal to write expression-bodied function then the easiest the way is to use GroupBy
int FindFirstDuplicate(IEnumerable<int> arr) =>
arr.GroupBy(e => e)
.Where(g => g.Count() > 1)
.Select(g => g.First())
.DefaultIfEmpty(-1)
.First();
But this option has a very suboptimal memory consumption.
If collection always has a very small number of elements then function can be written without GroupBy
int FindFirstDuplicate(IEnumerable<int> arr) =>
arr.SkipWhile((e, i) => arr.Skip(i + 1).All(x => x != e))
.DefaultIfEmpty(-1)
.First();
But such implementation has quadratic complexity, as it has nested loop. So, it will be extremely slow for thousands records.
There is one more option that has similar performance and memory consumption as the very first function.
int FindFirstDuplicate(IEnumerable<int> arr) =>
Enumerable.Repeat(new HashSet<int>(), arr.Count())
.Zip(arr)
.SkipWhile(e => e.First.Add(e.Second))
.Select(e => e.Second)
.DefaultIfEmpty(-1)
.First();
Repeat and Zip are needed to pass the HashSet object to SkipWhile method.
And the most straightforward way with foreach loop for comparison with other options
int FindFirstDuplicate(IEnumerable<int> arr)
{
HashSet<int> seen = new();
foreach (int item in arr)
{
if (!seen.Add(item))
{
return item;
}
}
return -1;
}
Just to restate what your code does in English: you what to remove 1 of each unique value from a list, leaving duplicate values in place with the order unchanged.
For example if your input list was: [ 4, 7, 1, 3, 7, 4, 8, 7 ] you expect the [contents of a] to be [ 7, 4, 7 ], and therefore the result would be 7 (<-- edited thanks to mjwills).
This is probably bad form because it uses a mutable accumulator, but it would work:
arr.Aggregate(
new { Seen = new HashSet<Int32>(), Result = new List<Int32>() },
(acc, val) => {
if (!acc.Seen.Add(val)) {
// If we've seen this value before, add it to the list
acc.Result.Add(val);
}
return acc;
}).Result
Obviously this does not remove elements from the original array, which is because it is not valid to remove elements from an array while you are using an enumerator with it.
If you don't care about the original order of the items then this is super simple:
List<string> result = arr.GroupBy(x => x).SelectMany(xs => xs.Skip(1)).ToList();
Done. That removes one of each instance from the list.
If the original order is important then this is what you need:
List<string> result =
arr
.Select((x, n) => new { x, n })
.GroupBy(y => y.x)
.SelectMany(ys => ys.Skip(1))
.OrderBy(y => y.n)
.Select(y => y.x)
.ToList();
If I run that on var arr = new string[] { "x", "y", "z", "z", "x", }; then I get "z", "x" as expected.
Well, I think Except operator is more suitable here:
return a.Except(b).FirstOrDefault(-1);
Try this-
a.GroupBy(v => v).Where(c => c.Count() > 1).FirstOrDefault().Key;
I have two lists of strings:
List<string> a;
List<string> b;
With a.RemoveAll() I remove empty elements or specific values in list a.
Now I want to remove elements at the same index in list b related to the indexes that were removed with a.RemoveAll()
How can I accomplish that?
I already have coded a workaround with for ()... loops but the code looks more than awkward. There should be a better solution
Let me try to rephrase your question. You want to remove the elements at certain indices of b. Which indices exactly? Those indices of a that would have been removed if a.RemoveAll(somePredicate) were called. Did I understand correctly?
You can use LINQ:
a.Select((x, y) => (element: x, index: y))
.Where(x => somePredicate(x.element) && x.index < b.Count)
.OrderByDescending(x => x.index)
.ToList().ForEach(x => b.RemoveAt(x.index));
If you don't mind creating a new list, it can be shorter:
var newList = b.Select((x, y) => (element: x, index: y))
.Where(x => somePredicate(a[x.index])).ToList();
var myList = new List<string> { "A", "x", "x", "B" };
if you want to find indexes of "x"
var indexes = Enumerable.Range(0, myList.Count).Where(x => myList[x] == "x").ToList();
I have the following object:
List<List<MyObj>> lst;
I need to find a list of all the objects (List< MyObj >) in the inner list, that has ID equal 1.
I tried:
lst.Where(x => x.FindAll(y=> y.ID== "1"));
lst.FindAll(x => x.FindAll(y=> y.ID== "1"));
and also tried to use Any() but no luck.
You can use SelectMany() to flatten the lists and then filter the elements:
var result = lst.SelectMany(x => x).Where(y => y.ID == "1").ToList();
List<MyObj> list1 = lst.SelectMany(x => x.Where(y=> y.ID== "1")).ToList();
or
List<List<MyObj>> list2 = lst.Where(x => x.Any(y=> y.ID== "1")).ToList();
depending on what it is you want as a result..
SelectMany is your friend. Example:
var listOfListsOfStrings = new List<List<string>>();
listOfListsOfStrings.Add(new List<string>() {"a", "b"});
listOfListsOfStrings.Add(new List<string>() {"c", "d"});
var allStrings = listOfListsOfStrings.SelectMany(s => s);
Console.WriteLine(string.Join(", ", allStrings.ToArray())); //prints: a, b, c, d
So in your case you just need:
lst.SelectMany(x => x).Where(y => y.ID == "1")
Let me add another option to the already good set of options. It is using Hashset<T> for search, by converting the internal List<T>, this would help when data size is more, since Hashset<T> has O(1) search instead of O(N) for List<T>
List<List<MyObj>> lst;
var result = lst.where(x =>
{
// Assuming ID to be string type
var hashset = new Hashset<string>(x.Select(y => y.ID));
return hashset.Contains("1");
}
);
In case you are not keen to do conversion, then following code would do:
var result = lst.where(x => x.Any(y => y.ID == "1"));
result will be of type List<List<MyObj>>, which will be filtered, currently we are are supplying Func<T,bool> to the Enumerable.Where, when data is supplied at run-time, then its easier to construct Expression<Func<T,bool>>, which gets compiled at run-time into correct Func<T,bool> delegate to filter actual list
Given:
class C
{
public string Field1;
public string Field2;
}
template = new [] { "str1", "str2", ... }.ToList() // presents allowed values for C.Field1 as well as order
list = new List<C> { ob1, ob2, ... }
Question:
How can I perform Linq's
list.OrderBy(x => x.Field1)
which will use template above for order (so objects with Field1 == "str1" come first, than objects with "str2" and so on)?
In LINQ to Object, use Array.IndexOf:
var ordered = list
.Select(x => new { Obj = x, Index = Array.IndexOf(template, x.Field1)})
.OrderBy(p => p.Index < 0 ? 1 : 0) // Items with missing text go to the end
.ThenBy(p => p.Index) // The actual ordering happens here
.Select(p => p.Obj); // Drop the index from the result
This wouldn't work in EF or LINQ to SQL, so you would need to bring objects into memory for sorting.
Note: The above assumes that the list is not exhaustive. If it is, a simpler query would be sufficient:
var ordered = list.OrderBy(x => Array.IndexOf(template, x.Field1));
I think IndexOf might work here:
list.OrderBy(_ => Array.IndexOf(template, _.Field1))
Please note that it will return -1 when object is not present at all, which means it will come first. You'll have to handle this case. If your field is guaranteed to be there, it's fine.
As others have said, Array.IndexOf should do the job just fine. However, if template is long and or list is long, it might be worthwhile transforming your template into a dictionary. Something like:
var templateDict = template.Select((item,idx) => new { item, idx })
.ToDictionary(k => k.item, v => v.idx);
(or you could just start by creating a dictionary instead of an array in the first place - it's more flexible when you need to reorder stuff)
This will give you a dictionary keyed off the string from template with the index in the original array as your value. Then you can sort like this:
var ordered = list.OrderBy(x => templateDict[x.Field1]);
Which, since lookups in a dictionary are O(1) will scale better as template and list grow.
Note: The above code assumes all values of Field1 are present in template. If they are not, you would have to handle the case where x.Field1 isn't in templateDict.
var orderedList = list.OrderBy(d => Array.IndexOf(template, d.MachingColumnFromTempalate) < 0 ? int.MaxValue : Array.IndexOf(template, d.MachingColumnFromTempalate)).ToList();
I've actually written a method to do this before. Here's the source:
public static IOrderedEnumerable<T> OrderToMatch<T, TKey>(this IEnumerable<T> source, Func<T, TKey> sortKeySelector, IEnumerable<TKey> ordering)
{
var orderLookup = ordering
.Select((x, i) => new { key = x, index = i })
.ToDictionary(k => k.key, v => v.index);
if (!orderLookup.Any())
{
throw new ArgumentException("Ordering collection cannot be empty.", nameof(ordering));
}
T[] sourceArray = source.ToArray();
return sourceArray
.OrderBy(x =>
{
int index;
if (orderLookup.TryGetValue(sortKeySelector(x), out index))
{
return index;
}
return Int32.MaxValue;
})
.ThenBy(x => Array.IndexOf(sourceArray, x));
}
You can use it like this:
var ordered = list.OrderToMatch(x => x.Field1, template);
If you want to see the source, the unit tests, or the library it lives in, you can find it on GitHub. It's also available as a NuGet package.
I have a string like string strn = "abcdefghjiklmnopqrstuvwxyz" and want a dictionary like:
Dictionary<char,int>(){
{'a',0},
{'b',1},
{'c',2},
...
}
I've been trying things like
strn.ToDictionary((x,i) => x,(x,i)=>i);
...but I've been getting all sorts of errors about the delegate not taking two arguments, and unspecified arguments, and the like.
What am I doing wrong?
I would prefer hints over the answer so I have a mental trace of what I need to do for next time, but as per the nature of Stackoverflow, an answer is fine as well.
Use the .Select operator first:
strn
.Select((x, i) => new { Item = x, Index = i })
.ToDictionary(x => x.Item, x => x.Index);
What am I doing wrong?
You're assuming there is such an overload. Look at Enumerable.ToDictionary - there's no overload which provides the index. You can fake it though via a call to Select:
var dictionary = text.Select((value, index) => new { value, index })
.ToDictionary(pair => pair.value,
pair => pair.index);
You could try something like this:
string strn = "abcdefghjiklmnopqrstuvwxyz";
Dictionary<char,int> lookup = strn.ToCharArray()
.Select( ( c, i ) => new KeyValuePair<char,int>( c, i ) )
.ToDictionary( e => e.Key, e => e.Value );