Merging List with same index? - c#

Example: if I have list 1, 2, 3 like this:
var list1 = new List<string> {"B", "S", "", "", "", "", ""};
var list2 = new List<string> {"", "", "B", "S", "", "", ""};
var list3 = new List<string> {"", "", "", "", "B", "S", ""};
So, I have found another question. i have try it.
getAct.AddRange(a.MatchedALLlist[j].AllNewActionList);
And result is
listAll = { "B","S","","","","","","","","B","S","","","","","","","","B","S","")
but I want to merge to one list like.
listAll = { "B","S","B","S","B","S","")
What should I do?

With extension method
public static IEnumerable<string> Merge<T(
this IEnumerable<string> first,
IEnumerable<string> second,
IEnumerable<string> third)
{
using (var eFirst = first.GetEnumerator())
using (var eSecond = second.GetEnumerator())
using (var eThird = third.GetEnumerator())
{
while (eFirst.MoveNext() && eSecond.MoveNext() && eThird.MoveNext())
{
var values = new[] { eFirst.Current, eSecond.Current, eThird.Current };
yield return values.Where(value => string.IsNullOrEmpty(value) == false)
.DefaultIfEmpty("")
.First();
}
}
}
By using while we ensure that merge will be done amount of time equal to amount of items in smallest collection.
Then use it
var merged = list1.Merge(list2, list3);
Another approach (actually the same) where you can use already existed LINQ extension functions:
Use FirstNonEmpty method (from #dasblinkenlight's answer) and Zip method
var merge =
list1.Zip(list2, (value1, value2) => FirstNonEmpty(value1, value2))
.Zip(list3, (value, value3) => FirstNonEmpty(value, value3));

I suppose you are sure that all lists have the same length and only one of the lists have non-empty element at each index, so you can get the expected result simply this way:
var result = list1.Select((x, i) => x + list2[i] + list3[i]).ToList();

Sometimes a simple answer is the best one.
Why not use the good old classic for loop:
var list1 = new List<string> { "B", "S", "", "", "", "", "" };
var list2 = new List<string> { "", "", "B", "S", "", "", "" };
var list3 = new List<string> { "", "", "", "", "B", "S", "" };
var list4 = new List<string>();
for (int i = 0; i < list1.Count; i++)
{
if (!string.IsNullOrWhiteSpace(list1[i]))
list4.Add(list1[i]);
else if (!string.IsNullOrWhiteSpace(list2[i]))
list4.Add(list2[i]);
else
list4.Add(list3[i]);
}
It should do what you want, as long as the lengths of all 3 lists are the same.

Related

Group array of string arrays with LINQ

I have array like this, values are string:
var arr1 = new [] { "H", "item1", "item2" };
var arr2 = new [] { "T", "thing1", "thing2" };
var arr3 = new [] { "T", "thing1", "thing2" };
var arr4 = new [] { "END", "something" };
var arr5 = new [] { "H", "item1", "item2" };
var arr6 = new [] { "T", "thing1", "thing2" };
var arr7 = new [] { "T", "thing1", "thing2" };
var arr8 = new [] { "END", "something" };
var allArrays = new [] { arr1, arr2, arr3, arr4, arr5, arr6, arr7, arr8 };
I need to group this in to a new array of arrays, so that one array has arrays that start with H or T. The END records (not included in the results) are the delimiters between each section; each new array starts after an END array.
In the end I would like to have somethng like this:
[
[ [H, item1, item2], [T, thing1, thing2], [T, thing1, thing2] ]
[ [H, item1, item2], [T, thing1, thing2], [T, thing1, thing2] ]
]
I know how I can do this with for each loop, but I'm looking for a cleaner way, possibly using linq. All suggestions are much valued, thank you!
you can try this
List<string[]> list = new List<string[]>();
var newArr = allArrays.Select(a => AddToArr(list, a)).Where(a => a != null);
and helper (this code can be put inline, but it easier to read this way)
private static string[][] AddToArr(List<string[]> list, string[] arr)
{
if (arr[0] != "END")
{
list.Add(arr);
return null;
}
var r = list.ToArray();
list.Clear();
return r;
}
result
[
[["H","item1","item2"],["T","thing1","thing2"],["T","thing1","thing2"]],
[["H","item3","item4"],["T","thing3","thing4"],["T","thing5","thing6"]]
]
So arr1, arr2, etc are string[].
allArrays is a string[][].
I hope you gave a meaningful example. From this example it seems that you want all string[] from allArrays, except the string[] that have a [0] that equals the word "END".
If this is what you want, your result is:
string[][] result = allArrays.Where(stringArray => stringArray[0] != "END");
I need to group this in to a new array of arrays, so that one array has arrays that start with H or T. The END records (not included in the results) are the delimiters between each section; each new array starts after an END array.
This is not exactly the same as I see in your example: what if one of the string arrays in allArrays is an empty array, or if it has the value null values. What if one of the the arrays of strings is empty (= length 0), and what if one of the string arrays doesn't start with "H", nor "T", nor "END"?
Literally you say that you only want the string arrays that start with "H" or "T", no other ones. You don't want string arrays that are null, nor empty string arrays. You also don't want string arrays that start with "END", nor the ones that start with String.Empty, or "A" or "B" or anything else than "H" or "T".
If I take your requirement literally, your code should be:
string[] requiredStringAtIndex0 = new string[] {"H", "T"};
string[][] result = allArrays.Where(stringArray => stringArray != null
&& stringArray.Length != 0
&& requiredStringAtIndex0.Contains(stringArray[0]));
In words: from allArrays, keep only those arrays of strings, that are not null, AND that have at least one element AND where the element at index 0 contains either "H" or "T"
Normally I would use an extension method for grouping runs of items based on a predicate, in this case GroupByEndingWith and then throw away the "END" record, like so:
var ans = allArrays.GroupByEndingWith(r => r[0] == "END")
.Select(g => g.Drop(1).ToArray())
.ToArray();
But, in general, you can use Aggregate to collect items based on a predicate at the expense of comprehension. It often helps to use a tuple to track an overall accumulator and a sub-accumulator. Unfortunately, there is no + operator or Append for List<T> that returns the original list (helpful for expression based accumulation) and since C# doesn't yet have a comma operator equivalent, you need an extension method again or you can use ImmutableList.
Using Aggregate and ImmutableList, you can do:
var ans = allArrays.Aggregate(
(ans: ImmutableList<ImmutableList<string[]>>.Empty, curr: ImmutableList<string[]>.Empty),
(ac, r) => r[0] == "END"
? (ac.ans.Add(ac.curr), ImmutableList<string[]>.Empty)
: (ac.ans, ac.curr.Add(r))
).ans
.Select(l => l.ToArray())
.ToArray();
NOTE: You can also do this with List if you are willing to create new Lists a lot:
var ans = allArrays.Aggregate(
(ans: new List<List<string[]>>(), curr: new List<string[]>()),
(ac, r) => r[0] == "END"
? (ac.ans.Concat(new[] { ac.curr }).ToList(), new List<string[]>())
: (ac.ans, ac.curr.Concat(new[] { r }).ToList())
).ans
.Select(l => l.ToArray())
.ToArray();
Here is a simple implementation.
public static void Main(string[] args)
{
var data = ConvertToArrayOfArray(arr1, arr2, arr3, arrr4, arr5, arr6, arr7, arr8);
}
private string[][] ConvertToArrayOfArray(params string[][] arrs)
{
List<string[]> yoList = new List<string[]>();
arrs.ToList().ForEach(x =>
{
if(!x[0] == "END") yoList.Add(x);
});
return yoList.ToArray();
}

Count occurrences of order dependent List<T> in List<List<T>>

There is a similar question that doesn't answer my question. --> Count number of element in List>
I have a list which contains sublists:
List<string> sublist1 = new List<string>() { "a", "b" };
List<string> sublist2 = new List<string>() { "a", "b" };
List<string> sublist3 = new List<string>() { "a", "c" };
Now I want to count the occurrences of each list.
a, b --> 2
a, c --> 1
I used distinct() from LINQ, but I got the output:
a, b --> 1
a, b --> 1
a, c --> 1
I assume that the hashcode is different.
Is there an alternative to distinct() which is looking at the list values instead?
I want to solve this in LINQ if possible.
Edit:
The order of list items has to be the same!
To use GroupBy() to do this, you will need a suitable IEqualityComparer<List<string>> that compares lists of strings. There is no built-in implementation, so you have to roll your own:
public sealed class StringListEqualityComparer : IEqualityComparer<List<string>>
{
public bool Equals(List<string> x, List<string> y)
{
if (ReferenceEquals(x, y))
return true;
if (x == null || y == null)
return false;
return x.SequenceEqual(y);
}
public int GetHashCode(List<string> strings)
{
int hash = 17;
foreach (var s in strings)
{
unchecked
{
hash = hash * 23 + s?.GetHashCode() ?? 0;
}
}
return hash;
}
}
Once you've got that, you can use it with GroupBy() as follows:
public static void Main()
{
var sublist1 = new List<string>{ "a", "b" };
var sublist2 = new List<string>{ "a", "b" };
var sublist3 = new List<string>{ "a", "c" };
var listOfLists = new List<List<string>> {sublist1, sublist2, sublist3};
var groups = listOfLists.GroupBy(item => item, new StringListEqualityComparer());
foreach (var group in groups)
{
Console.WriteLine($"Group: {string.Join(", ", group.Key)}, Count: {group.Count()}");
}
}
public JsonResult CountList(){
List<List<string>> d = new List<List<string>>(); //SuperList
d.Add(new List<string> { "a", "b" }); //List 1
d.Add(new List<string> { "a", "b" }); // List 2
d.Add(new List<string> { "a", "c" }); // List 3
d.Add(new List<string> { "a", "c", "z" }); //List 4
var listCount = from items in d
group items by items.Aggregate((a,b)=>a+""+b) into groups
select new { groups.Key, Count = groups.Count() };
return new JsonResult(listCount);
}
This will give the following Result as output in Post Man or Advanced REST Client
[{
"key": "ab",
"count": 2
},
{
"key": "ac",
"count": 1
},
{
"key": "acz",
"count": 1
}],
I think this will be helpful
var list = new List<List<string>>() { sublist1, sublist2, sublist3};
var result = list.GroupBy(x => string.Join(",",x)).ToDictionary(x => x.Key.Split(',').ToList(), x => x.Count());
You can try the below code:-
List<string> sublist1 = new List<string>() { "a", "b" };
List<string> sublist2 = new List<string>() { "a", "b" };
List<string> sublist3 = new List<string>() { "a", "c" };
List<List<string>> listOfLists = new List<List<string>> { sublist1, sublist2, sublist3 };
Dictionary<string, int> counterDictionary = new Dictionary<string, int>();
foreach (List<string> strList in listOfLists)
{
string concat = strList.Aggregate((s1, s2) => s1 + ", " + s2);
if (!counterDictionary.ContainsKey(concat))
counterDictionary.Add(concat, 1);
else
counterDictionary[concat] = counterDictionary[concat] + 1;
}
foreach (KeyValuePair<string, int> keyValue in counterDictionary)
{
Console.WriteLine(keyValue.Key + "=>" + keyValue.Value);
}
I think I will solve this with:
var equallists = list1.SequenceEqual(list2);
Therefore I compare distinct lists and lists with SequenceEquals() and counting them.
Better solutions welcome. :)

Set subtraction while keeping duplicates

I need to get the set subtraction of two string arrays while considering duplicates.
Ex:
var a = new string[] {"1", "2", "2", "3", "4", "4"};
var b = new string[] {"2", "3"};
(a - b) => expected output => string[] {"1", "2", "4", "4"}
I already tried Enumerable.Except() which returns the unique values after subtract: { "1", "4" } which is not what I'm looking for.
Is there a straightforward way of achieving this without a custom implementation?
You can try GroupBy, and work with groups e.g.
var a = new string[] {"1", "2", "2", "3", "4", "4"};
var b = new string[] {"2", "3"};
...
var subtract = b
.GroupBy(item => item)
.ToDictionary(chunk => chunk.Key, chunk => chunk.Count());
var result = a
.GroupBy(item => item)
.Select(chunk => new {
value = chunk.Key,
count = chunk.Count() - (subtract.TryGetValue(chunk.Key, out var v) ? v : 0)
})
.Where(item => item.count > 0)
.SelectMany(item => Enumerable.Repeat(item.value, item.count));
// Let's have a look at the result
Console.Write(string.Join(", ", result));
Outcome:
1, 2, 4, 4
By leveraging the undersung Enumerable.ToLookup (which allows you to create dictionary-like structure with multi-values per key) you can do this quite efficiently. Here, because key lookups on non-existent keys in an ILookup return empty IGrouping (rather than null or an error), you can avoid a whole bunch of null-checks/TryGet...-boilerplate. Because Enumerable.Take with a negative value is equivalent to Enumerable.Take(0), we don't have to check our arithmetic either.
var aLookup = a.ToLookup(x => x);
var bLookup = b.ToLookup(x => x);
var filtered = aLookup
.SelectMany(aItem => aItem.Take(aItem.Count() - bLookup[aItem.Key].Count()));
Try the following:
var a = new string[] { "1", "2", "2", "3", "4", "4" }.ToList();
var b = new string[] { "2", "3" };
foreach (var element in b)
{
a.Remove(element);
}
Has been tested.

Linq: Filter a list with a different IEnumerable<bool>

I cannot find out how to filter an array/list from another list array:
I was looking for something like this:
IEnumerable<bool> Filter = new[] { true, true, false,true };
IEnumerable<string> Names = new[] { "a", "B", "c", "d" };
List<string> NameFiltered = Filter
.Where(c => c == true)
.Select(x => Names)
.ToList();
In general case (both Names and Filter are IEnumerable<T> only) I suggest Zip:
List<string> NameFiltered = Names
.Zip(Filter, (name, filter) => new {
name = name,
filter = filter, })
.Where(item => item.filter)
.Select(item => item.name)
.ToList();
If Filter is in fact an array (..Filter = new[]...) Where will do:
List<string> NameFiltered = Names
.Where((name, index) => Filter[index])
.ToList();
var NameFiltered = Enumerable.Range(0, Names.Count)
.Where(n => Filter[n])
.Select(n => Names[n])
.ToList();
There's Zip method for union of corresponding pairs of two sequences:
Filter.Zip(Names, (flag, name) => new { flag, name })
.Where(x => x.flag)
.Select(x => x.name)
Zip makes this:
IEnumerable<bool> Filter = new[] { true, true, false, true };
IEnumerable<string> Names = new[] { "a", "B", "c", "d" };
Filter.Zip(Names, (flag, name) => new { flag, name }) =
{
{ flag = true, name = "a" },
{ flag = true, name = "B" },
{ flag = false, name = "c" },
{ flag = true, name = "d" },
}
Not the best solution, but try this:
List<string> NameFiltered = Filter
.Select((x, i) => new { flag = x, index = i })
.Where(item => item.flag)
.Select(item => Names.ElementAt(item.index))
.ToList();
// Output: a, B, d
OP wants to filter Names using Filter, which is made up of bool values. If you notice that they have the same number of elements, so if filter is true. it is required to be in the resulting set. There are a couple of ways you can achieve this. I would suggest using Linq.Zip, which combines two sequences.
IEnumerable<bool> Filter = new bool[] { true, true, false, true };
IEnumerable<string> Names = new string[] { "a", "B", "c", "d" };
var merged= Filter.Zip(Names,(bo,str)=> new {bo, str});
var selected = merged.Where(x=> x.bo).Select(y=> y.str);
How about this
used Where with index
var nameFiltered = Names.Where((x, index) => Filter.ToArray()[index]);
Or
var nameFiltered= Names.Where((x, index) => Filter.ElementAt(index));
First of all there is no connection between your collections, so NameFiltered list does not know what to return.
I suggest you to make class
Names
{
}
public string Name{get;set;}
public bool Fikter{get;set;}
var NameFiltered = Names.Where(x=>x.Fikter == true).toArray;

Sort string array in special format

It need to sort a string array like this to a special format. Our Array is:
input1 = new string[12]{"Active1","12","mm","Active2","17","mm","Width","25","mil","Height","20","mil"}
and our desired sort list is:
sort = new string[6]{"Serial","Width","Height","Active1","Active2","Time"}
My valid format for output is this:
Output = [{Serial,null,null},{Width,25,mil},{Height,20,mil},{Active1,12,mm},{Active2,17,mm},{Time,null,null}]
It is necessary to set null value for data that don't exist in Input Array.
I'm using this code for my purpose:
var Output = (from i in Enumerable.Range(0, input.Length / 3)
let index = Array.IndexOf(sort, input[i * 3])
where index >= 0
select ne3w string[] { input[i * 3], input[i * 3 + 1] , input[i * 3 + 2]})
.OrderBy(a => Array.IndexOf(sort, a[0])).ToArray();
but it doesn't show the values that don't exist in input Array.
I would put this into a separate method:
private static IEnumerable<string[]> TransformInput(string[] input)
{
return from key in new[] { "Serial", "Width", "Height", "Active1", "Active2", "Time" }
let keyIndex = Array.IndexOf(input, key)
let hasData = keyIndex > 1
select new[]
{
key,
hasData ? input[keyIndex + 1] : null,
hasData ? input[keyIndex + 2] : null
};
}
And then use it as follows:
var input1 = new string[12]
{ "Active1", "12", "mm", "Active2", "17", "mm", "Width", "25", "mil", "Height", "20", "mil" };
var sorted = TransformInput(input1);
You can do it with the method below:
private string[][] Sort(string[] input)
{
List<string> inputList = new List<string> ();
inputList = input.ToList<string> ();
List<string[]> sortedList = new List<string[]> ();
string[] sort = new string[]{"Serial", "Width", "Height", "Active1", "Active2", "Time"};
foreach(string key in sort)
{
if (inputList.Contains<string> (key)) {
int i = inputList.IndexOf (key);
string[] t = new string[]{inputList[i],inputList[i+1],inputList[i+2]};
sortedList.Add (t);
}
else
{
string[] t = new string[]{key,null, null};
sortedList.Add (t);
}
}
return sortedList.ToArray<string[]> ();
}
Hope it help you out!
Given the two sets of input data:
var input1 = new string[12]
{
"Active1","12","mm",
"Active2","17","mm",
"Width","25","mil",
"Height","20","mil"
};
var sort = new string[6]
{
"Serial","Width","Height","Active1","Active2","Time"
};
This worked for me:
var lookup =
input1
.Select((x, n) => new { x, n })
.ToLookup(xn => xn.n / 3)
.ToLookup(
z => z.ElementAt(0).x,
z => z.Skip(1).Select(w => w.x));
var result =
sort
.Select(x =>
new [] { x }
.Concat(lookup[x].SelectMany(z => z))
.Concat(new string[] { null, null })
.Take(3)
.ToArray())
.ToArray();
I got this result:
Building on Nitesh, but removing the need to scan input repeatedly by using a dictionary
using System.Linq; //at top of file
private static string[][] TransformInput(string[] input)
{
var sortOrder = new[] { "Serial", "Width", "Height", "Active1", "Active2", "Time" };
//dictionary pointing words to position in input
var inputDict = Enumerable.Range(0, input.Length/3)
.Select(i => i*3).ToDictionary(i => input[i]);
//Try to read position from dictionary; return nulls if fail
return sortOrder.Select(x => {
int i;
return (inputDict.TryGetValue(x, out i))
? new[]{x, input[i+1], input[i+2]}
: new[]{x, null, null};
}).ToArray();
}
I think this code is good for your problem.
static List<string[]> SortedList(string[] input)
{
var sort = new string[6] { "Serial", "Width", "Height", "Active1", "Active2", "Time" };
List<string[]> output = new List<string[]>();
for (int i = 0; i < sort.Length; i++)
{
var findIndex = input.ToList().IndexOf(sort[i]);
if (findIndex != -1)
output.Add(new string[3]
{
input[findIndex],
input[findIndex + 1],
input[findIndex + 2]
});
else
output.Add(new string[3]
{
sort[i],
null,
null
});
}
return output;
}
And now you call that method:
var input = new string[12] { "Active1", "12", "mm", "Active2", "17", "mm", "Width", "25", "mil", "Height", "20", "mil" };
var output = SortedList(input);

Categories