Joining specific 2 rows of existing list using linq - c#

I have a question I have a list let us say:
List<string> list = new List<string> { A, B, C, D, E, F, G}
And I need to combine specific two rows of that list to new new list for example:
List<string> rebuildList = new List<string> { A, B, CD, E, FG}
I already made a code that would work:
var joinDictionary = new Dictionary<int, int> { { 3, 4 }, { 7, 8 } };
foreach (var value in list)
{
var index = list.IndexOf(value);
if (joinDictionary.ContainsKey(index))
{
rebuildList.Add(string.Format("{0} {1}", value, list.ElementAt(index + 1)));
}
if (!joinDictionary.ContainsKey(index) && !joinDictionary.ContainsValue(index))
{
rebuildList.Add(value);
}
}
But is there more elegant way to do this? Sole linq lambda query maybe?

If you change your dictionary to be zero based, you can use something like:
list.
Select((str, ind) => joinDictionary.ContainsKey(ind) ? str + list[joinDictionary[ind]] : str).
Where((str, ind) => !joinDictionary.ContainsValue(ind)).
ToList();
It's a one-liner, but I'm not sure if that's more readable than your solution.
If you don't want to switch to zero based dictionary, you'll have to play with the indices in the LINQ expression.

Related

How to add add items near to next list

I'm trying to make a program like wordlist generator.
I want to add the items on the 2nd list next to each item on the 1st list.
`
List<string> list1 = new List<string>() {"tomato", "ball", "icecream", "blue"};
List<string> list2 = new List<string>() { "123", "yellow", "green" };
`
//Values ​​to be added to Listing 3: tomato123, tomatoyellow, tomatogreen, ball123, ballyellow, ballgreen bla bla bla
To solve your problem, we will iterate over one of the lists, and for every item in it, we will create all the possible combinations with words from the other list. With LINQ, it would look something like this:
var list3 = list1.Select(w1 => list2.Select(w2 => w1 + w2)).ToList();
The problem is that now list3 is of type List<IEnumerable<string>> because we have a list of combinations for every word in list1. To flatten the result, all we need is to change the Select projection to a SelectMany flattened projection:
var list3 = list1.SelectMany(w1 => list2.Select(w2 => w1 + w2)).ToList();
Based on your requirement it might be useful. Please have a look.
static void AddLists()
{
List<string> list1 = new List<string>() { "tomato", "ball", "icecream", "blue" };
List<string> list2 = new List<string>() { "123", "yellow", "green" };
var resultList = from l1 in list1
from l2 in list2
select string.Concat(l1, l2);
}
My advice would be to create an extension method, instead of a LINQ statement that is difficult to understand: readers will immediately see what it does, it is easier to test and easier to change.
See extension methods demystified
public static IEnumerable<string> ConcatCombinations(
this.IEnumerable<string> sequenceA,
IEnumerable<string> sequenceB)
{
// TODO: invent a proper name
foreach (string textA in sequenceA)
foreach (string textB in sequenceB)
yield return textA + textB;
}
This code is way simpler than any solution using LINQ methods. Anyone will immediately see what it does.
Usage:
List<string> list1 = ...
string[] array1 = ...
List<string> concatenated = list1.ConcatCombinations(array1).ToList();
If you want to make a more generic method, consider this:
public static IEnumerable<TResult> MakeCombinations<TA, TB, TResult>(
this IEnumerable<TA> itemsA,
IEnumerable<TB> itemsB,
Func<TA, TB, TResult> resultSelector)
{
foreach (TA itemA in itemsA)
foreach (TB itemB in itemsB)
{
TResult result = resultSelector(itemA, itemB);
yield return result;
}
}
Usage:
List<string> list1 = ...
List<string> list2 = ...
List<string> concatenated = list1.ConcatCombinations(list2,
// parameter ResultSelector: concat every a and b:
(a, b) => a+b)
.ToList();
Or just change your ConcatCombinations:
public static IEnumerable<string> ConcatCombinations(
this.IEnumerable<string> sequenceA,
IEnumerable<string> sequenceB)
{
return sequenceA.MakeCombinations(sequenceB,
(a, b) => a + b);
}
Another completely different example, that shows you the reusability of the code:
var maleBallroomDancers = ...
var femaleBallroomDancers = ...
var danceCombinations = maleBallroomDancers.MakeCombinations(femaleBallroomDancers,
(male, female) => new
{
Male = male,
Female = female,
})
.ToList();

List<List<string>> unique permutations using LINQ [duplicate]

This question already has answers here:
Generating all Possible Combinations
(12 answers)
Closed 4 years ago.
I need an algorithm that will take any number of List inside a List and generate a unique set of permutations. I prefer to find a LINQ solution.
I actually have a Javascript function that works well and I'm trying to recreate it in C# (see code at bottom)
C# (my attempt) - Visual Studio does not like my second Aggregate(). It says the arguments cannot be inferred from usage
public static void testit()
{
List<List<string>> master = new List<List<string>>();
List<string> voltages = new string[] { "208", "230", "460" }.ToList();
List<string> sysConfigs = new string[] { "10205", "10210", "10215", "10220" }.ToList();
master.Add(voltages);
master.Add(sysConfigs);
var amp = master.Aggregate(
(a, b) => a.Aggregate(
(r, v) => r.Concat(
b.Select(w => new List<string>().Concat(v, w))
), new List<string>()
)
);
}
The output of this new collection should look like this:
/*
OUTPUT (displayed as arrays - but will be lists):
[
["208", "10205"],
["208", "10210"],
["208", "10215"],
["208", "10220"],
["230", "10205"],
["230", "10210"],
["230", "10215"],
["230", "10220"],
["460", "10205"],
["460", "10210"],
["460", "10215"],
["460", "10220"]
];
Here's a Javascript function that works well that I'm trying to mimic in C#:
function getPermutations(arr) {
return arr.reduce(
(a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), [])
);
}
var voltages = ["208", "230", "460"];
var sysConfigs = ["10205", "10210", "10215", "10220"];
var master = [];
master.push(voltages);
master.push(sysConfigs);
var newArr = getPermutations(master);
console.log(newArr);
As noted in other questions, this is the Cartesian product, not a permutation.
Short version: Just go to my blog:
https://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/
Long version:
Cartesian product of two lists is built-in in C# in the form of SelectMany or a query comprehension. Let's start with that.
Before we get into that though, please do not ever do this:
List<string> voltages = new string[] { "208", "230", "460" }.ToList()
Either do this:
IEnumerable<string> voltages = new string[] { "208", "230", "460" };
Or do this:
List<string> voltages = new List<string>() { "208", "230", "460" };
But do not make an array and then to-list it! Just make a list from the beginning.
OK, onward. We have two sequences:
IEnumerable<string> voltages = new string[] { "208", "230", "460" };
IEnumerable<string> sysConfigs = new string[] { "10205", "10210", "10215", "10220" };
We want their Cartesian product:
IEnumerable<IEnumerable<string>> master =
from v in voltages
from s in sysConfigs
select new List<string>() { v, s };
And we're done.
If you don't like "comprehension form" then you can use "fluent form":
IEnumerable<IEnumerable<string>> master =
voltages.SelectMany(
v => sysConfigs,
(s, v) => new List<string>() { v, s });
If you want a list of lists:
List<List<string>> master =
voltages.SelectMany(
v => sysConfigs,
(v, s) => new List<string>() { v, s })
.ToList();
Easy peasy.
The meaning of this operation should be clear, but if it is not: the general form is:
var zs =
from x in xs
from y in f(x) // f takes x and returns a collection of ys
g(x, y) // do something with every combination of x and y to make a z
In your case, f(x) is just "always produce the second collection", but it need not be; the collection could depend on x. The result is a sequence of z.
Now, what you need is the Cartesian product of arbitrarily many sequences.
Your intuition that this is an aggregation of concatenations is correct. We can solve it like this:
static IEnumerable<IEnumerable<T>> CartesianProduct<T>(
this IEnumerable<IEnumerable<T>> sequences)
{
IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() };
return sequences.Aggregate(
emptyProduct,
(accumulator, sequence) =>
from accseq in accumulator
from item in sequence
select accseq.Concat(new[] {item}));
}
Notice how this combines the three operations: a select-many query, a concatenation, and an aggregation. Study this carefully to see how it works.
This is the correct version of your code
var amp = master.Aggregate(
new List<List<string>>(){new List<string>()},
(a, b) => a.Aggregate(
new List<List<string>>(new List<List<string>>()),
(r, v) => r.Concat(
b.Select(w => v.Concat(new List<string>{w}).ToList())
).ToList()));

LINQ: Collapsing a series of strings into a set of "ranges"

I have an array of strings similar to this (shown on separate lines to illustrate the pattern):
{ "aa002","aa003","aa004","aa005","aa006","aa007", // note that aa008 is missing
"aa009"
"ba023","ba024","ba025"
"bb025",
"ca002","ca003",
"cb004",
...}
...and the goal is to collapse those strings into this comma-separated string of "ranges":
"aa002-aa007,aa009,ba023-ba025,bb025,ca002-ca003,cb004, ... "
I want to collapse them so I can construct a URL. There are hundreds of elements, but I can still convey all the information if I collapse them this way - putting them all into a URL "longhand" (it has to be a GET, not a POST) isn't feasible.
I've had the idea to separate them into groups using the first two characters as the key - but does anyone have any clever ideas for collapsing those sequences (without gaps) into ranges? I'm struggling with it, and everything I've come up with looks like spaghetti.
So the first thing that you need to do is parse the strings. It's important to have the alphabetic prefix and the integer value separately.
Next you want to group the items on the prefix.
For each of the items in that group, you want to order them by number, and then group items while the previous value's number is one less than the current item's number. (Or, put another way, while the previous item plus one is equal to the current item.)
Once you've grouped all of those items you want to project that group out to a value based on that range's prefix, as well as the first and last number. No other information from these groups is needed.
We then flatten the list of strings for each group into just a regular list of strings, since once we're all done there is no need to separate out ranges from different groups. This is done using SelectMany.
When that's all said and done, that, translated into code, is this:
public static IEnumerable<string> Foo(IEnumerable<string> data)
{
return data.Select(item => new
{
Prefix = item.Substring(0, 2),
Number = int.Parse(item.Substring(2))
})
.GroupBy(item => item.Prefix)
.SelectMany(group => group.OrderBy(item => item.Number)
.GroupWhile((prev, current) =>
prev.Number + 1 == current.Number)
.Select(range =>
RangeAsString(group.Key,
range.First().Number,
range.Last().Number)));
}
The GroupWhile method can be implemented like so:
public static IEnumerable<IEnumerable<T>> GroupWhile<T>(
this IEnumerable<T> source, Func<T, T, bool> predicate)
{
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
yield break;
List<T> list = new List<T>() { iterator.Current };
T previous = iterator.Current;
while (iterator.MoveNext())
{
if (!predicate(previous, iterator.Current))
{
yield return list;
list = new List<T>();
}
list.Add(iterator.Current);
previous = iterator.Current;
}
yield return list;
}
}
And then the simple helper method to convert each range into a string:
private static string RangeAsString(string prefix, int start, int end)
{
if (start == end)
return prefix + start;
else
return string.Format("{0}{1}-{0}{2}", prefix, start, end);
}
Here's a LINQ version without the need to add new extension methods:
var data2 = data.Skip(1).Zip(data, (d1, d0) => new
{
value = d1,
jump = d1.Substring(0, 2) == d0.Substring(0, 2)
? int.Parse(d1.Substring(2)) - int.Parse(d0.Substring(2))
: -1,
});
var agg = new { f = data.First(), t = data.First(), };
var query2 =
data2
.Aggregate(new [] { agg }.ToList(), (a, x) =>
{
var last = a.Last();
if (x.jump == 1)
{
a.RemoveAt(a.Count() - 1);
a.Add(new { f = last.f, t = x.value, });
}
else
{
a.Add(new { f = x.value, t = x.value, });
}
return a;
});
var query3 =
from q in query2
select (q.f) + (q.f == q.t ? "" : "-" + q.t);
I get these results:

.NET LINQ Call Method with Out Parameters Within Query and use Out Values

I have a list of objects, which has a method that has a couple of out parameters. How do i call this method on each object, get the out parameter values and use them later on in the query, perhaps for checking in a where clause?
Is this possible and if so can someone please demonostrate through sample code.
Thanks!
Maybe you should use a for each loop and then use your query?
(Actually, it's hard to say what to do best in this situation without knowing your code)
Here is one way of accessing the values of out parameters in your LINQ query. I dont think that you can use the out-values from say a where in a later select: list.Where(...).Select(...)
List<MyClass> list; // Initialize
Func<MyClass, bool> fun = f =>
{
int a, b;
f.MyMethod(out a, out b);
return a == b;
};
list.Where(fun);
Where MyClass is implemented something like this;
public class MyClass
{
public void MyMethod(out int a, out int b)
{
// Implementation
}
}
This uses Tuple<T1,T2> from .NET 4.0, but can be adapted for earlier versions:
//e.g., your method with out parameters
void YourMethod<T1,T2,T3>(T1 input, out T2 x, out T3 y) { /* assigns x & y */ }
//helper method for dealing with out params
Tuple<T2,T3> GetTupleOfTwoOutValues<T1,T2,T3>(T1 input)
{
T2 a;
T3 b;
YourMethod(input, out a, out b);
return Tuple.Create(a,b);
}
IEnumerable<Tuple<T2,T3>> LinqQuery<T1,T2,T3>(IEnumerable<T1> src, T2 comparisonObject)
{
return src.Select(GetTupleOfTwoOutValues)
.Where(tuple => tuple.Item1 == comparisonObject);
}
You can use tuples (without any helper methods):
var text = "123,456,abc";
var items = text.Split(',')
.Select(x => (long.TryParse(x, out var v), v))
.Where(x => x.Item1)
.Select(x => x.Item2);
foreach (var item in items)
{
Console.WriteLine(item);
}
Output
123
456
This article has some additional solutions: https://mydevtricks.com/linq-gems-out-parameters
You could use anonymous objects and the let keyword:
var texts = new[] { "dog", "2", "3", "cat" };
var checks = from item in texts
let check = new
{
Word = item,
IsNumber = int.TryParse(item, out var n),
Value = n,
}
where check.IsNumber
select check;
foreach(var item in checks)
{
Console.WriteLine($"'{item.Word}' is the number {item.Value}");
}

Overlay/Join two collections with Linq

I have the following scenario:
List 1 has 20 items of type TItem, List 2 has 5 items of the same type. List 1 already contains the items from List 2 but in a different state. I want to overwrite the 5 items in List 1 with the items from List 2.
I thought a join might work, but I want to overwrite the items in List 1, not join them together and have duplicates.
There is a unique key that can be used to find which items to overwrite in List 1 the key is of type int
You could use the built in Linq .Except() but it wants an IEqualityComparer so use a fluid version of .Except() instead.
Assuming an object with an integer key as you indicated:
public class Item
{
public int Key { get; set; }
public int Value { get; set; }
public override string ToString()
{
return String.Format("{{{0}:{1}}}", Key, Value);
}
}
The original list of objects can be merged with the changed one as follows:
IEnumerable<Item> original = new[] { 1, 2, 3, 4, 5 }.Select(x => new Item
{
Key = x,
Value = x
});
IEnumerable<Item> changed = new[] { 2, 3, 5 }.Select(x => new Item
{
Key = x,
Value = x * x
});
IEnumerable<Item> result = original.Except(changed, x => x.Key).Concat(changed);
result.ForEach(Console.WriteLine);
output:
{1:1}
{4:4}
{2:4}
{3:9}
{5:25}
LINQ isn't used to perform actual modifications to the underlying data sources; it's strictly a query language. You could, of course, do an outer join on List2 from List1 and select List2's entity if it's not null and List1's entity if it is, but that is going to give you an IEnumerable<> of the results; it won't actually modify the collection. You could do a ToList() on the result and assign it to List1, but that would change the reference; I don't know if that would affect the rest of your application.
Taking your question literally, in that you want to REPLACE the items in List1 with those from List2 if they exist, then you'll have to do that manually in a for loop over List1, checking for the existence of a corresponding entry in List2 and replacing the List1 entry by index with that from List2.
As Adam says, LINQ is about querying. However, you can create a new collection in the right way using Enumerable.Union. You'd need to create an appropriate IEqualityComparer though - it would be nice to have UnionBy. (Another one for MoreLINQ perhaps?)
Basically:
var list3 = list2.Union(list1, keyComparer);
Where keyComparer would be an implementation to compare the two keys. MiscUtil contains a ProjectionEqualityComparer which would make this slightly easier.
Alternatively, you could use DistinctBy from MoreLINQ after concatenation:
var list3 = list2.Concat(list1).DistinctBy(item => item.Key);
Here's a solution with GroupJoin.
List<string> source = new List<string>() { "1", "22", "333" };
List<string> modifications = new List<string>() { "4", "555"};
//alternate implementation
//List<string> result = source.GroupJoin(
// modifications,
// s => s.Length,
// m => m.Length,
// (s, g) => g.Any() ? g.First() : s
//).ToList();
List<string> result =
(
from s in source
join m in modifications
on s.Length equals m.Length into g
select g.Any() ? g.First() : s
).ToList();
foreach (string s in result)
Console.WriteLine(s);
Hmm, how about a re-usable extension method while I'm at it:
public static IEnumerable<T> UnionBy<T, U>
(
this IEnumerable<T> source,
IEnumerable<T> otherSource,
Func<T, U> selector
)
{
return source.GroupJoin(
otherSource,
selector,
selector,
(s, g) => g.Any() ? g.First() : s
);
}
Which is called by:
List<string> result = source
.UnionBy(modifications, s => s.Length)
.ToList();

Categories