Iterate and select over two dimensional string array with LINQ - c#

I did console application that must iterate over two dimensional array of strings and select values that contains in user input and show these values in set by "row".
Unfortunately I got error System.Collections.Generic.List '1[System.String]
Here is the code of application:
static void Main(string[] args)
{
string[,] words = new string[,]
{
{ "5", "" },
{ "10", "kare" },
{ "20", "kanojo" },
{ "1", "karetachi" },
{ "7", "korosu" },
{ "3", "sakura" },
{ "3", "" }
};
try
{
var pre = Console.ReadLine();
var r = Enumerable
.Range(0, words.GetLength(0))
.Where(i => words[i, 1] == pre)
.Select(i => words[i, 1])
.OrderBy(i => words[Int32.Parse(i), 0])
.ToList();
Console.Write(r);
}
catch (Exception ex)
{
TextWriter errorWriter = Console.Error;
errorWriter.WriteLine(ex.Message);
}
Console.ReadLine();
}

Your query is incorrect: you try to match each word from the list to the entirety of the user input, which means that you would always pick a single word (assuming there's no duplicates in the 2D array). Since you are sorting the results, however, it appears that you expect there to be more than one word.
To fix this, replace your selection criteria to use Contains, like this:
var r = Enumerable
.Range(0, words.GetLength(0))
.Where(i => pre.Contains(words[i, 1]))
.Select(i => new {i, w=words[i, 1]})
.OrderBy(p => Int32.Parse(words[p.i, 0]))
.Select(p=>p.w)
.ToList();
To display the results in a single line you could use string.Join:
Console.WriteLine("Results: {0}", string.Join(", ", r));
Note: I assume that the exercise requires you to use a 2D array. If there is no such requirement, you could use an array of tuples or anonymous types, letting you avoid parsing of the integer:
var words = new[] {
new { Priority = 5, Word = "" }
, new { Priority = 10, Word = "kare" }
, new { Priority = 20, Word = "kanojo" }
, ... // and so on
};
Demo.

That's not an error, that's what happens when you display the result of calling the ToString function of a List.
(i.e. your statement ran correctly, you just aren't displaying it the way you think.... see?)
Try:
Console.Write(r.Aggregate((a,b) => a + "," + b));
instead of
Console.Write(r);

The following code creates a 2D List as though we had this
myList[][], consisting of [0] = {0,1,2,3} and [1] = {4,5,6,7,8}
List<List<int>> a2DList = new List<List<int>>()
{
new List<int>()
{
0,1,2,3
},
new List<int>()
{
4,5,6,7,8
}
};
The LINQ code
a2DList.SelectMany(s => s).ToArray().Select(s => s))
returns a copy of the 2d array flattened into 1D form.
SelectMany takes each element and projects each member of each element sequentially.
You could then say
var myObj = a2DList.SelectMany(s => s).ToArray().Select(s => s));
IEnumerable myEnumerable = a2DList.SelectMany(s => s).ToArray().Select(s => s));
int [] myArray = a2DList.SelectMany(s => s).ToArray().Select(s => s)).ToArray();
List myList = a2DList.SelectMany(s => s).ToArray().Select(s => s)).ToList();
etc
This is "join"ed by the string operator for printing out to Console
Console.WriteLine(string.Join(",",a2DList.SelectMany(s => s).ToArray().Select(s => s)));
// Output will be "0,1,2,3,4,5,6,7,8"

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();
}

Removing strings with duplicate letters from string array

I have array of strings like
string[] A = { "abc", "cccc", "fgaeg", "def" };
I would like to obtain a list or array of strings where any letter appears only one time. I means that "cccc", "fgaeg" will be removed from input array.
I managed to do this but I feel that my way is very messy, unnecessarily complicated and not efficient.
Do you have any ideas to improve this algorythm (possibliy replacing with only one Linq query)?
My code:
var goodStrings = new List<string>();
int i = 0;
foreach (var str in A)
{
var tempArr = str.GroupBy(x => x)
.Select(x => new
{
Cnt = x.Count(),
Str = x.Key
}).ToArray();
var resultArr = tempArr.Where(g => g.Cnt > 1).Select(f => f.Str).ToArray();
if(resultArr.Length==0) goodStrings.Add(A[i]);
i++;
}
You can use Distinct method for every array item and get items with count of distinct items equals to original string length
string[] A = { "abc", "cccc", "fgaeg", "def" };
var result = A.Where(a => a.Distinct().Count() == a.Length).ToList();
You'll get list with abc and def values, as expected

Get all unique values out of strings

I am facing some problems while getting unique values out of strings.
Example:
string1 = "4,5"
string2 = "7,9"
string3 = "4,7,6,1"
string4 = "1"
After I need to get all unique values as an int. In this case result must be 6. But each time the number of strings can change.
Is this even possible?
Use Split and Distinct
var input = "1,3,1,2,3,43,23,54,3,4";
var result input.Split(',')
.Distinct();
Console.WriteLine(string.Join(",",result));
Output
1,3,2,43,23,54,4
Full Demo Here
Additional Resources
String.Split Method
Returns a string array that contains the substrings in this instance
that are delimited by elements of a specified string or Unicode
character array.
Enumerable.Distinct Method
Returns distinct elements from a sequence.
If "number of strings can change", let's organize them into a collection:
List<string> strings = new List<string> {
"4,5",
"7,9",
"4,7,6,1",
"1"
};
Then we can right a simple Linq:
var uniques = strings
.SelectMany(item => item.Split(',')) // split each item and flatten the result
.Select(item => int.Parse(item))
.Distinct()
.ToArray(); // let's have an array of distinct items: {4, 5, 7, 9, 6, 1}
If you want to obtain items which appears just once:
var uniques = strings
.SelectMany(item => item.Split(',')) // split each item and flatten the result
.Select(item => int.Parse(item))
.GroupBy(item => item)
.Where(item => item.Count() == 1)
.Select(group => group.Key)
.ToArray(); // let's have an array of items which appear once: {5, 9, 6}
Instead of using number of string variable you can you single instance of StringBuilder
Convert all element to array of integer.
Get Distinct/number which comes only one once by Linq
Something like this:
StringBuilder sb = new StringBuilder();
sb.Append("5,5");
sb.Append(",");
sb.Append("7,9");
sb.Append(",");
sb.Append("4,7,6,1");
sb.Append(",");
sb.Append("1");
string[] arr = sb.ToString().Split(',');
int[] test = Array.ConvertAll(arr, s => int.Parse(s));
var count = test
.GroupBy(e => e)
.Where(e => e.Count() == 1)
.Select(e => e.First()).ToList();
output:
9
4
6
POC: .netFiddler
A single line can do the job
string s = "1,3,1,2,3,43,23,54,3,4";
string[] StrArry = s.Split(',');
int[] IntArry = Array.ConvertAll(StrArry, int.Parse).Distinct().ToArray();
output
1,3,2,43,23,54,4
He can you try this out
var string1 = "4,5";
var string2 = "7,9";
var string3 = "4,7,6,1";
var string4 = "1";
var allStrings = string1 + ',' + string2 + ',' + string3 + ','+ string4;
var distinctNumbers = new List<string>(allStrings.Split(',').Distinct());
Output :
4
5
7
9
6
1
distinctNumbers = Count = 6
This is a longer one than the others, but it might be easier to understand how it works.
List<string> str = new List<string> {"1", "3", "1", "2", "3", "43", "23", "54", "3"," "4 };
List<string> foundstr = new List<string> { };
foreach (string check in str)
{
bool found = false;
//going through every single item in the list and checking if it is found in there
for (int i = 0; i < foundstr.Count; i++)
{
//if found then make found(bool) true so we don't put it in the list
if(check == foundstr[i])
{
found = true;
}
}
//checking if the string has been found and if not then add to list
if(found == false)
{
foundstr.Add(check);
}
}
foreach(string strings in foundstr)
{
Console.WriteLine(strings);
}
Console.ReadLine();

How do I return a list of the three lowest values in another list

How do I return a list of the 3 lowest values in another list. For example, I want to get the 3 lowest values like this:
in_list = [2, 3, 4, 5, 6, 1]
To this:
out_list: [2, 3, n, n, n, 1]
Maybe a function like this:
out_list = function(in_list, 3)?
in_list and ouput list is declared like this:
List<string> in_list = new List<string>();
List<string> out_list = new List<string>();
Can you help me developing a C# code for this? Further explanation can be given.
If you really want those weird n, there's this simple solution:
public static List<string> Function(List<string> inputList, int max)
{
var inputIntegers = inputList
.Select(z => int.Parse(z))
.ToList();
var maxAuthorizedValue = inputIntegers
.OrderBy(z => z)
.Take(max)
.Last();
return inputIntegers
.Select(z => z <= maxAuthorizedValue ? z.ToString() : "n")
.ToList();
}
public static void Main(string[] args)
{
List<string> in_list = new List<string> { "2", "3", "4", "6", "1", "7" };
var res = Function(in_list, 3);
Console.Read();
}
For your new requirement about duplicates, you could limit the max number of integer your return:
public static List<string> Function(List<string> inputList, int max)
{
var inputIntegers = inputList.Select(z => int.Parse(z)).ToList();
var maxAuthorizedValue = inputIntegers
.OrderBy(z => z)
.Take(max)
.Last();
// I don't really like that kind of LINQ query (which modifies some variable
// inside the Select projection), so a good old for loop would probably
// be more appropriated
int returnedItems = 0;
return inputIntegers.Select(z =>
{
return (z <= maxAuthorizedValue && ++returnedItems <= max) ? z.ToString() : "n";
}).ToList();
}
You need two queries, one to determine the lowest items and one to fill the result-list. You can use a HashSet for faster loookups:
var lowest = new HashSet<String>(in_list
.Select(s => new { s, val = int.Parse(s) })
.OrderBy(x => x.val)
.Take(3)
.Select(x => x.s));
List<string> out_list = in_list.Select(s => lowest.Contains(s) ? s : "n").ToList();
If you actually only want 3 and duplicates are possible this is the best i've come up with:
var lowest = new HashSet<String>(in_list
.Select(s => new { s, val = int.Parse(s) })
.Distinct()
.OrderBy(x => x.val)
.Take(3)
.Select(x => x.s));
List<string> out_list = in_list
.Select((str, index) => new { str, index, value = int.Parse(str) })
.GroupBy(x => x.str)
.SelectMany(g => lowest.Contains(g.Key)
? g.Take(1).Concat(g.Skip(1).Select(x => new { str = "n", x.index, x.value }))
: g.Select(x => new { str = "n", x.index, x.value }))
.OrderBy(x => x.index)
.Select(x => x.str)
.ToList();
You could use Aggregate to grab a Dictionary of each element with its corresponding number of allowed occurrences which you could then use to grab your values from the input list:
public static List<string> GetList(List<string> in_list, int max)
{
Dictionary<string, int> occurrences = new Dictionary<string, int>();
int itemsAdded = 0;
in_list.OrderBy(x => x).Aggregate(occurrences, (list, aggr) =>
{
if (itemsAdded++ < max)
{
if (occurrences.ContainsKey(aggr))
occurrences[aggr]++;
else
occurrences.Add(aggr, 1);
}
return list;
});
//occurrences now contains only each required elements
//with the number of occurrences allowed of that element
List<string> out_list = in_list.Select(x =>
{
return (occurrences.ContainsKey(x) && occurrences[x]-- > 0 ? x : "n");
}).ToList();
return out_list;
}

Sort a list by alpha values first, numeric values next

class ListSort
{
static void Main(string[] args)
{
string[] partNumbers = new string[]
{
"India", "US","UK", "Australia","Germany", "1", "7", "9"
};
var result = partNumbers.OrderBy(x => x).ToList();
}
}
I tried the above code, and expected the following output:
Australia
Germany
India
UK
US
1
7
9
EDIT:
Numbers should be ordered numerically (1, 7, 9, 70, ...), while the non-numbers should always be ordered lexically, even if there's a number inside ("A3stralia", "Australia", "Germany").
Try this:
string[] partNumbers = new string[]
{
"India", "US","UK", "Australia","Germany", "1", "7", "9"
};
var result = partNumbers.OrderBy(x => char.IsNumber(x.FirstOrDefault())).ThenBy(x => x).ToList().Dump();
Note that this will only work if your data is either numeric or text, not if there's a value like "U2S". I can change this to work for those cases too, if you need that, though. Also, the numeric strings are still gettings sorted as strings, so "10" comes before "2".
How would you want the result to be when you add A3stralia and 70 to the list?
EDIT: Changed for the new constraints:
string[] partNumbers = new string[]
{
"India", "US","UK", "Australia","Germany", "1", "7", "9", "70", "A3stralia"
};
var result =
partNumbers
.Select(x => { int p; bool isNumber = int.TryParse(x, out p); return new { IsNumber = isNumber, NumericValue = isNumber ? p : int.MinValue, StringValue = x }; })
.OrderBy(x => x.IsNumber)
.ThenBy(x => x.NumericValue)
.ThenBy(x => x.StringValue)
.Select(x => x.StringValue)
.ToList();
If it doesn't matter that the digit part is ordered lexicographically:
var result = partNumbers.OrderBy(s => s.All(Char.IsDigit)).ThenBy(s => s).ToList();
This just simply checks whether all characters are digits or not. If you want the digits first use Enumerable.OrderByDescending instead.
As commented to one of the answers you want to order the digits numerically, then you need to parse them first:
result = partNumbers
.Select(s => new { s, num = s.TryGetInt() } )
.GroupBy(x => x.num.HasValue) // two groups: one can be parsed to int the other not
.SelectMany (xg =>
{
if (xg.Key) // can be parsed to int, then order by int-value
return xg.OrderBy(x => x.num.Value).Select(x => x.s);
else // can not be parsed to int, order by the string
return xg.OrderBy(x => x.s).Select(x => x.s);
})
.ToList();
I'm using this extension to parse strings to Nullable<int> in LINQ queries:
public static class NumericExtensions
{
public static int? TryGetInt(this string item)
{
int i;
bool success = int.TryParse(item, out i);
return success ? (int?)i : (int?)null;
}
}
So far it seems all the responses are roughly the same, but all too complicated. My attempt:
string[] partNumbers = { "US", "1", "UK", "Australia", "Germany", "70", "9" };
partNumbers.OrderBy(x =>
{
int parseResult;
return int.TryParse(x, out parseResult)
? parseResult
: null as int?;
})
.ThenBy(x => x);
Or, with extracted helper method:
partNumbers.OrderBy(TryParseNullableInt).ThenBy(x => x);
private static int? TryParseNullableInt(string source)
{
int parseResult;
return int.TryParse(x, out parseResult)
? parseResult
: null as int?;
}
string[] partNumbers = new string[]
{
"India", "US","UK", "Australia","Germany", "1", "7", "9"
};
var result = partNumbers.OrderBy(x =>
{
int i;
return int.TryParse(x, out i);
}).ThenBy(x=>x);
Following solution projects string sequence into anonymous type { isNumber, s, value }, which contain current item from sequence, information whether item is integer number, and possible parse result. Then new sequence if grouped into two groups - one for numbers and one for other strings. Each group is sorted - numbers by numeric comparison, strings alphabetically. And original item is selected from flattened groups:
string[] partNumbers = { "US", "1", "UK", "Australia", "Germany", "70", "9" };
int value;
var result = partNumbers
.Select(s => new { isNumber = Int32.TryParse(s, out value), s, value })
.GroupBy(x => x.isNumber)
.OrderBy(g => g.Key)
.SelectMany(g => g.Key ? g.OrderBy(x => x.value) : g.OrderBy(x => x.s))
.Select(x => x.s)
.ToList();
Returns:
"Australia",
"Germany",
"UK",
"US",
"1",
"9",
"70"

Categories