replace an element in a specific array with another value in C# - c#

There is one array for me. my array is as follows.
var Array = [["Dog","0","A","eat"],["cat","1","B","eat"]]
I want to replace the value in some indexes in this array with other values.
for example, it should be like this.
var newArray = [["Dog","big","house","eat"],["cat","small","forest","eat"]]
can be understood from the example, "0 = big, 1 = small" and "A=house, B=forest"
how can I solve this both with the for loop and using C# Linq.

Unsure if it qualifies as elegant but what you're describing is a matter of translation, a Dictionary is very good for this.
Loop through each string in each array and replace if the translation dictionary contains a key equal to the string.
var Array = new string[][] {
new string[] {"Dog", "0", "A", "eat" },
new string[] {"Cat", "1", "B", "eat" }
};
//Array: [["Dog","0","A","eat"],["Cat","1","B","eat"]]
var TranslationDict = new Dictionary<string, string>() {
{ "0", "big" },
{ "1", "small" },
{ "A", "house" },
{ "B", "forest" },
};
for (int y = 0; y < Array.Length; y++) {
for (int x = 0; x < Array[y].Length; x++) {
if(TranslationDict.ContainsKey(Array[y][x])) {
Array[y][x] = TranslationDict[Array[y][x]];
}
}
}
//Array: [["Dog","big","house","eat"],["Cat","small","forest","eat"]]

Do it with linq like that:
var testArray = array.
Select(x => x.
Select(y => y.Replace("0", "big").Replace("1","test")).ToArray())
.ToArray();

Related

Is there a LINQ operator to do this?

I would like to know if there's a LINQ operator to do this:
var one = new[] { "A", "B", "C" };
var two = new[] { "A", "B", "C", "D" };
var combined = new [] { one, two };
var result = Operator(combined);
Console.WriteLine(result.Should().BeEquivalentTo(new [] { "A", "A", "B", "B", "C", "C", null, "D" }));
If short, it should act like if every sequence was a row in a matrix. And after that, it should:
Transpose the matrix (rotate it)
It should push every "cell" in the matrix, returning the corresponding item, or default if the cell is empty.
I mean, graphically:
A, B, C
A, B, C, D
<Transpose>
A, A
B, B
C, C
D
result => A, A, B, B, C, C, D, null
NOTICE
Operator should work on IEnumerable<IEnumerable<T>>
As you can see, the Operator I'm interested in, uses combined, so it accepts should IEnumerable<IEnumerable<T>> (like SelectMany).
It's a bit hard keeping up with your changing specs. Originally, it was a pair of string arrays. I changed that to be a pair of arrays of T in my answer.
Then you wrote in a comment that "oh, no, I meant N sequences". Finally, after reading that, I noticed that you'd updated your question to ask about N collections expressed as IEnumerable<T>.
In the mean time, I pointed out that my original answer would work well for N arrays with minimal change. So, here goes:
For N Arrays
I use the params keyword to remove the need for your combined variable. The params keyword will compose some or all of the parameters of a method into an array.
Here's a method that can take N arrays:
public static IEnumerable<T> KnitArrays<T>(params T[][] arrays)
{
var maxLen = (from array in arrays select array.Length).Max();
for (var i = 0; i < maxLen; i++)
{
foreach( var array in arrays)
{
yield return array.Length > i ? array[i] : default(T);
}
}
}
It's pretty much the same logic as the original answer. The test code looks the same as well:
var one = new[] { "A1", "B1", "C1" };
var two = new[] { "A2", "B2", "C2", "D2" };
var three = new[] { "A3", "B3" };
var knittedArray = KnitArrays(one, two, three);
List<string> result = knittedArray.ToList();
WriteCollectionContents(result);
Where WriteCollectionContents spits out the contents of the collection. In this case:
"A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", null, null, "D2", null,
For N Lists and/or Arrays
It turns out that the same basic code can work with IList<T>, i.e., for both List<T> and T[]:
public static IEnumerable<T> KnitILists<T>(params IList<T>[] ilists)
{
var maxLen = (from ilist in ilists select ilist.Count).Max();
for (var i = 0; i < maxLen; i++)
{
foreach (var ilist in ilists)
{
yield return ilist.Count > i ? ilist[i] : default(T);
}
}
}
The test code for this also looks pretty similar - though note the mix or arrays and lists:
var one = new[] { "A1", "B1", "C1" };
var two = new[] { "A2", "B2", "C2", "D2" };
var list3 = new List<string> { "A3", "B3" };
var knittedLists = KnitILists(one, two, list3);
result = knittedLists.ToList();
WriteCollectionContents(result);
With exactly the same result:
"A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", null, null, "D2", null,
The reason it works with IList<T> is that that interface has a Count property and an Item indexer. If you go to ICollection<T> the Count property stays, but you lose the Item indexer.
Once you get to IEnumerable<T>, both the Count property and the Item indexer are gone. The only thing you can do with an IEnumerable is to iterate through it. As a result, the logic needs to be very different.
I might get around to coming up with a solution. However, it will likely look very similar to #gertarnold's answer.
I'm looking foreword to your upcoming comment about how you really meant for this to work with multi-dimensional arrays as well.
Original answer follows
How about something like this:
public static IEnumerable<T> KnitArrays<T>(T[] first, T[] second)
{
var maxLen = Math.Max(first.Length, second.Length);
for (var i = 0; i < maxLen; i++)
{
yield return first.Length > i ? first[i] : default(T);
yield return second.Length > i ? second[i] : default(T);
}
}
Testing this with:
var one = new[] { "A", "B", "C" };
var two = new[] { "A", "B", "C", "D" };
var knittedArray = KnitArrays(one, two);
List<string> result = knittedArray.ToList();
yields a list that looks like what you are asking. Note that I just return a non-materialized IEnumerable since you were asking about LINQ.
To make the result independent of the number of arrays the function should loop trough all arrays and keep returning until all enumerations are exhausted:
public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> source)
{
var enumerators = source.Select(e => e.GetEnumerator()).ToArray();
try
{
var next = false;
do
{
var results = enumerators.Select(enr =>
{
if (enr.MoveNext())
{
return enr.Current;
}
return default;
}).ToList();
next = results.Any(e => !Equals(default, e));
if (next)
{
yield return results;
}
}
while (next);
}
finally
{
Array.ForEach(enumerators, e => e.Dispose());
}
}
Now you can use any number of arrays:
var one = new[] { "A", "B", "C" };
var two = new[] { "A", "B", "C", "D" };
var three = new[] { "U", "V","W", "X", "Y", "Z" };
var combined = new[] { one, three, two };
var result = combined.Transpose().SelectMany(e => e).ToList();
This will result in
"A","U","A","B","V","B","C","W","C",null,"X","D",null,"Y",null,null,"Z",null
Filtering the null values is trivial.
(Courtesy this answer for the basic idea, but only working for arrays of equal length).
try this
var result = Enumerable.Range(0, Math.Max(one.Count(), two.Count()))
.SelectMany(n => new[] { one.ElementAtOrDefault(n), two.ElementAtOrDefault(n) });
result
["A","A","B","B","C","C",null,"D"]

How to compare two string array and find the index number of the common string array and apply it to the third string array?

I have three string arrays
string[] stringArray= {"AAA", "BBB", "CCC","DDD", "EEE","FFF","GGG","HHH" };
string[] stringArray1 = { "A", "B", "C","D", "E","F","G","H" };
string[] stringArray2 = { "BBB", "DDD","FFF","HHH" };
How could I compare stringArray2 with stringArray and find the index number stringArray that matches.
After finding the index ,I need to apply it to stringArray1
and display the results.
I have tried the following code ,but failed to get the match string
int j=0;
string line = null;
List<string> finalstring = new List<string>();
for (int i = 0; i < stringArray.Count; i++)
{
while(stringArray[i] == stringArray2[j])
{
line = stringArray2[i];
finalstring.Add(line);
j++;
}
}
I think you have to try like this:
var result = stringArray1.Where(c => // iterating stringArray1
stringArray2.Where(x => stringArray.Contains(x)) // filtering stringArray2 elements
.Any(y=>y.Contains(c))).ToList(); // collect the final result
A Working example for your reference
int j=0;
string line = null;
List<string> finalstring = new List<string>();
for (int i = 0; i < stringArray.Length; i++)
//for (int i = 0; i < stringArray.Count; i++) // <-- Count is not proper way to get total elements in an array
{
j = 0; // <-- you failed to reinitialize 'j'
//while(stringArray[i] == stringArray2[j]) // <-- while is not proper way to compare
if(stringArray[i] == stringArray2[j])
{
//line = stringArray2[i];
//if only first charracter is needed
finalstring.Add(new string(stringArray2[i][0], 1));
//if complete string is needed
//finalstring.Add(stringArray2[i]);
j++;
}
}
Above is not an optimized way to do the comparisons. You can try using HashSet class
string[] stringArray= {"AAA", "BBB", "CCC","DDD", "EEE","FFF","GGG","HHH" };
string[] stringArray1 = { "A", "B", "C","D", "E","F","G","H" };
string[] stringArray2 = { "BBB", "DDD","FFF","HHH" };
//convert string array to hashset
var hashSet = new HashSet<string>(stringArray);
int j=0;
string line = null;
List<string> finalstring = new List<string>();
for (int i = 0; i < stringArray2.Length; i++)
{
if(hashSet.Contains(stringArray2[i]))
{
//if only first charracter is needed
finalstring.Add(new string(stringArray2[i][0], 1));
}
}
List<string> finalList = new List<string>();
stringArray.Select((value,index) => new { value, index })
.Where(num => stringArray2.Contains(num.value)).ToList()
.ForEach(num => finalList.Add(stringArray1[num.index]));
you could do it with selecting the index via Linq (where >= 0 filters out all non matches):
string[] stringArray = { "AAA", "BBB", "CCC", "DDD", "EEE", "FFF", "GGG", "HHH" };
string[] stringArray1 = { "A", "B", "C", "D", "E", "F", "G", "H" };
string[] stringArray2 = { "BBB", "DDD", "FFF", "HHH" };
var matches = stringArray2
.Select(s => Array.FindIndex(stringArray, s2 => s2 == s))
.Where(i => i >= 0).Select(i => stringArray1[i])
.ToList();
Here a dotnetfiddle
You are trying to compare the elements on same index but you have to search the element of stringArray2 in stringArray. You can use Array.IndexOf to find in Array the required element.
string[] stringArray= {"AAA", "BBB", "CCC","DDD", "EEE","FFF","GGG","HHH" };
string[] stringArray1 = { "A", "B", "C","D", "E","F","G","H" };
string[] stringArray2 = { "BBB", "DDD","FFF","HHH" };
int j=0;
string line = null;
List<string> finalstring = new List<string>();
for (int i = 0; i < stringArray2.Count(); i++)
{
if(Array.IndexOf(stringArray, stringArray2[i]) != -1)
{
int idx = Array.IndexOf(stringArray1, stringArray2[i][0].ToString());
if(idx!=-1)
finalstring.Add(stringArray1[idx]);
}
}
Edit using Linq
var result2 = stringArray1.Where(c=>
stringArray2.Intersect(stringArray)
.Any(i=>i.Contains(c)));

Finding differences within 2 Lists of string arrays

I am looking to find the differences between two Lists of string arrays using the index 0 of the array as the primary key.
List<string[]> original = new List<string[]>();
List<string[]> web = new List<string[]>();
//define arrays for List 'original'
string[] original_a1 = new string[3]{"a","2","3"};
string[] original_a2 = new string[3]{"x","2","3"};
string[] original_a3 = new string[3]{"c","2","3"};
//define arrays for List 'web'
string[] web_a1 = new string[3]{"a","2","3"};
string[] web_a2 = new string[3]{"b","2","3"};
string[] web_a3 = new string[3]{"c","2","3"};
//populate Lists
original.Add(original_a1);
original.Add(original_a2);
original.Add(original_a3);
web.Add(web_a1);
web.Add(web_a2);
web.Add(web_a3);
My goal is to find what is in List 'original' but NOT in 'web' by using index 0 as the primary key
This is what I tried.
List<string> differences = new List<string>(); //differences go in here
string tempDiff = ""; // I use this to try and avoid duplicate entries but its not working
for(int i = 0; i < original.Count; i++){
for(int j = 0; j< web.Count; j++){
if(!(original[i][0].Equals(web[j][0]))){
tempDiff = original[i][0];
}
}
differences.Add(tempDiff);
}
OUTPUT:
foreach(string x in differences){
Console.WriteLine("SIZE " + differences.Count);
Console.WriteLine(x);
ConSole.ReadLine();
}
SIZE 3
SIZE 3
x
SIZE 3
x
Why is it reporting the mismatch 3 times instead of once?
Using linq you can just go:
var differences = orignal.Except(web).ToList();
Reference here
This will give you the values that are in original, that don't exist in web
Sorry didn't read your question properly, to answer your question:
You have a nested for-loop. So for each value of original (3) it will loop through all values of web (3), which is 9 loops total.
In 3 cases it doesn't match and therefore outputs 3 times.
I think this is what you want. I use Linq to grab the primary keys, and then I use Except to do original - web. By the way, you can use == instead of Equals with strings in C# because C# does a value comparison as opposed to a reference comparison.
List<string[]> original = new List<string[]>
{
new string[3] { "a", "2", "3" },
new string[3] { "x", "2", "3" },
new string[3] { "c", "2", "3" }
};
List<string[]> web = new List<string[]>
{
new string[3] { "a", "2", "3" },
new string[3] { "b", "2", "3" },
new string[3] { "c", "2", "3" }
};
var originalPrimaryKeys = original.Select(o => o[0]);
var webPrimaryKeys = web.Select(o => o[0]);
List<string> differences = originalPrimaryKeys.Except(webPrimaryKeys).ToList();
Console.WriteLine("The number of differences is {0}", differences.Count);
foreach (string diff in differences)
{
Console.WriteLine(diff);
}
And here it is without Linq:
var differences = new List<string>();
for (int i = 0; i < original.Count; i++)
{
bool found = false;
for (int j = 0; j < web.Count; j++)
{
if (original[i][0] == web[j][0])
{
found = true;
}
}
if (!found)
{
differences.Add(original[i][0]);
}
}
To answer your question: It is a nested for loop as stated in JanR's answer. This approach will make you reiterate to your web count 9 times, thus listing your mismatched key three times.
What could be a better way to do is this:
//Check for originals not introduced in web.
if(original.Count > web.Count)
{
for(int y = web.Count; y < original.Count; y++)
{
differences.Add(original[y][0]);
}
}
//Check if Web has value, if not, everything else is done on the first for loop
if(web.Count > 0)
{
for(int i = 0; i < original.Count; i++)
{
if(!original[i][0].Equals(web[i][0]))
differences.Add(original[i][0]);
}
}
Also, the output is in a for loop, when you just need one result, the length of the mismatch. You can do that without a loop.
Console.WriteLine("SIZE " + differences.Count);
This is, of course to make it kinda simpler if you're not used to using LINQ statements, but if you can do so with LINQ, then by all means, use LINQ as it's more efficient.
You can get the difference by using Except extension method like this:
var originalDic = original.ToDictionary(arr => arr.First());
var webDic = web.ToDictionary(arr => arr.First());
var differences =
originalDic
.Except(webDic, kvp => kvp.Key)
.Select(kvp => kvp.Value)
.ToList();
The trick here is to first convert your original and web lists into a Dictionary using the first element of each array as key and then perform Except.

Linq: Sum() non-integer values

This is a continuation from my previos question:
Linq (GroupBy and Sum) over List<List<string>>
I have a query like so:
var content = new List<List<string>>
{
new List<string>{ "book", "code", "columnToSum" },
new List<string>{ "abc", "1", "10" },
new List<string>{ "abc", "1", "5" },
new List<string>{ "cde", "1", "6" },
};
var headers = content.First();
var result = content.Skip(1)
.GroupBy(s => new { Code = s[headers.IndexOf("code")], Book = s[headers.IndexOf("book")]})
.Select(g => new
{
Book = g.Key.Book,
Code = g.Key.Code,
Total = g.Select(s => int.Parse(s[headers.IndexOf("columnToSum")])).Sum()
});
This works fine but I'm just wondering how I can handle the case there the columnToSum is empty? So for example this gives me the error "Input string was not in a correct format" as the int.Parse fails
var content = new List<List<string>>
{
new List<string>{ "book", "code", "columnToSum" },
new List<string>{ "abc", "1", "10" },
new List<string>{ "abc", "1", "" },
new List<string>{ "cde", "1", "6" },
};
How can I handle this scenario gracefully?
Why don't you just add a zero onto the front of the string?
s => int.Parse("0" + s[headers.IndexOf("columnToSum")])
Of course, it's a big hack. But it will solve your problem quickly and (quite) readably if the only exceptional case you're really worried about is the empty string.
I wonder where you're getting these empty strings from. If it's something you have control over like a SQL query, why don't you just change your query to give "0" for no value? (As long as the empty column isn't used in a different sense somewhere else in your code.)
One option, use string.All(Char.IsDigit) as pre-check:
Total = g.Select(s => !string.IsNullOrEmpty(s[headers.IndexOf("columnToSum")]) &&
s[headers.IndexOf("columnToSum")].All(Char.IsDigit) ?
int.Parse(s[headers.IndexOf("columnToSum")]) : 0).Sum())
another would be to use int.TryParse:
int val = 0;
// ...
Total = g.Select(s => int.TryParse(s[headers.IndexOf("columnToSum")], out val) ?
int.Parse(s[headers.IndexOf("columnToSum")]) : 0).Sum())
That code assumes that empty string is 0:
Total = g.Where(s => !String.IsNullOrEmpty(s)).Select(s => int.Parse(s[headers.IndexOf("columnToSum")])).Sum()
Unfortunately, this isn't going to look very nice...
g.Select(s => !s[headers.IndexOf("columnToSum")].Any(Char.IsDigit) ?
0 : Int32.Parse(s[headers.IndexOf("columnToSum")])).Sum()
However, you could wrap this up in a nice extension method
public static class StrExt
{
public static int IntOrDefault(this string str, int defaultValue = 0)
{
return String.IsNullOrEmpty(str) || !str.Any(Char.IsDigit) ? defaultValue : Int32.Parse(str);
}
}
...
g.Select(s => s[headers.IndexOf("columnToSum")].IntOrDefault()).Sum();
The extension method give you the flexibility to set whatever default value you want if the str is not a number - it defaults to 0 if the parameter is ommitted.
Using lists here is problematic, and I would parse this into a proper data structure (like a Book class), which I think will clean up the code a bit. If you're parsing CSV files, take a look at FileHelpers, it's great library for these types of tasks, and it can parse into a data structure for you.
That being said, if you'd still like to continue using this paradigm, I think you can get the code fairly clean by creating two custom methods: one for dealing with the headers (one of the few places I'd use dynamic types to get rid of ugly strings in your code) and one for parsing the ints. You then get something like this:
var headers = GetHeaders(content.First());
var result = from entry in content.Skip(1)
group entry by new {Code = entry[headers.code], Book = entry[headers.book] } into grp
select new {
Book = grp.Key.Book,
Code = grp.Key.Code,
Total = grp.Sum(x => ParseInt(x[headers.columnToSum]))
};
public dynamic GetHeaders(List<string> headersList){
IDictionary<string, object> headers = new ExpandoObject();
for (int i = 0; i < headersList.Count; i++)
headers[headersList[i]] = i;
return headers;
}
public int ParseInt(string s){
int i;
if (int.TryParse(s, out i))
return i;
return 0;
}
You can use multiple lines in a lambda expression and return a value at end.
So, instead of
Total = g.Select(s => int.Parse(s[headers.IndexOf("columnToSum")])).Sum()
I would write
Total = g.Select(s => {
int tempInt = 0;
int.TryParse(s[headers.IndexOf("columnToSum")], out tempInt);
return tempInt;
}).Sum()
t = new List<List<string>>
{
new List<string>{ "book", "code", "columnToSum" },
new List<string>{ "abc", "1", "10" },
new List<string>{ "abc", "1", "5" },
new List<string>{ "cde", "1", "6" },
};
var headers = content.First();
var result = content.Skip(1)
.GroupBy(s => new { Code = s[headers.IndexOf("code")], Book = s[headers.IndexOf("book")]})
.Select(g => new
{
Book = g.Key.Book,
Code = g.Key.Code,
Total = g.Select(s => int.Parse(s[headers.IndexOf("columnToSum")]!=""?s[headers.IndexOf("columnToSum")]:0)).Sum()
});

How to filter a jagged array by using the where function

I have a jagged array that looks like this:
string[][] list = new string[d.Rows.Count + 1][];
int c = 0;
while (c < d.Rows.Count)
{
list[c] = new string[]
{
d.Rows[c].ItemArray[2].ToString(),
d.Rows[c].ItemArray[1].ToString(),
d.Rows[c].ItemArray[4].ToString(),
d.Rows[c].ItemArray[5].ToString(),
d.Rows[c].ItemArray[7].ToString(),
d.Rows[c].ItemArray[3].ToString(),
d.Rows[c].ItemArray[14].ToString()
};
c += 1;
}
return list;
Now, for a new requirement, i need only the items from this array whose value at this location: list[x][0] are equal to any of the following strings: "Text", "FullText", "FullMatch"
I got started with a regular array i could do this: but it obvioulsy won't work for a jagged array.
string[][] newlist = list.where(item => item.equals("Text");
Does any one know how to extend this for my situation?
You can do a where on list which will iterate over each one-dimensional array, then compare element 0 to the strings given.
string[][] newlist = list
.Where(item => item[0].Equals("Text")
|| item[0].Equals("FullText")
|| item[0].Equals("FullMatch"))
.ToArray();
Tested this on some sample data as shown below:
var list = new string[][]
{
new string[] { "Text", "A", "B", "C", "D" },
new string[] { "None", "Z", "C" },
new string[] { "FullText", "1", "2", "3" },
new string[] { "FullMatch", "0", "A", "C", "Z" },
new string[] { "Ooops", "Nothing", "Here" },
};
string[][] newlist = list.Where(item => item[0].Equals("Text")
|| item[0].Equals("FullText")
|| item[0].Equals("FullMatch")).ToArray();
// now display all data...
foreach (string[] row in newlist)
{
Console.Write("Row: ");
foreach (string item in row)
{
Console.Write(item + " ");
}
Console.WriteLine();
}
This worked correctly with output being:
Row: Text A B C D
Row: FullText 1 2 3
Row: FullMatch 0 A C Z
If you wanted a fully LINQ-based solution, then I think the following should do the trick (although I haven't tested it, because I'm not usre what the variable d refers to):
var res =
(from c in Enumerable.Range(0, d.Rows.Count)
let list = new string[] {
d.Rows[c].ItemArray[2].ToString(),
d.Rows[c].ItemArray[1].ToString(),
d.Rows[c].ItemArray[4].ToString(),
d.Rows[c].ItemArray[5].ToString(),
d.Rows[c].ItemArray[7].ToString(),
d.Rows[c].ItemArray[3].ToString(),
d.Rows[c].ItemArray[14].ToString()
}
where list[0] == "Text" || list[0] == "FullText" || list[0] == "FullMatch"
select list).ToArray();
A jagged array is just an array of arrays, so you can process it using LINQ. The only trick is that individual items will be arrays (representing your columns). After using Where, you can turn the result back to an array using ToArray:
string[][] newlist = list.Where(item => item[0] == "Text" || ... ).ToArray();

Categories

Resources