Split array into another array - c#

I have a string array like this:
string[] Array = new string[3] {"Man(21)", "Woman(33)", "Baby(4)"};
Now I want to split this array into this scheme:
Array = new string[6] {"Man", "21", "Woman", "33", "Baby", "4"};
Anybody have idea?

you can use Split and SelectMany
var result = Array.SelectMany(x => x.Split(new[]
{
'(', ')'
}, StringSplitOptions.RemoveEmptyEntries)).ToArray();

var result = from str in Array
let items = str.Split('(')
from item in items
select item.Replace(")", string.Empty);

You can give a try to regular expressions:
var pattern = #"(?<person>\w+)\((?<age>\d+)\)";
var Array = new string[3] { "Man(21)", "Woman(33)", "Baby(4)" };
Array = Array.SelectMany(item =>
{
var match = Regex.Match(item, pattern, RegexOptions.IgnoreCase);
var person = match.Groups["person"].Value;
var age = match.Groups["age"].Value;
return new List<string>{person, age};
}).ToArray();

Depending on the use case you might find it more useful to output a list of objects with Name and Age properties, or a dictionary. Here is an example of the former:
string[] arr = new[] { "Man(21)", "Woman(33)", "Baby(4)", /* test case */ "NoAge" };
var result = arr.Select(s => s.Split(new[] { '(', ')' }, StringSplitOptions.RemoveEmptyEntries)).Select(r => new
{
Name = r.First(),
Age = r.Skip(1).SingleOrDefault()
}).ToList();
The result is:
Name Age
Man 21
Woman 33
Baby 4
NoAge null
Credit to dotctor for the Split command.

Related

c# get new list of elements grouped between certain element in list

in c# how do you get a new list of elements grouped by falling in between a certain element. for example if my list was ['visit', 'houston', 'and', 'san', 'antonio', 'and', 'austin', 'and', 'corpus', 'christi']
and i wanted to extract the cities between "and" into a new list grouped between the "ands" so the two word names cities are in a group together
In python you can use itertools but how can you accomplish this in c#?
import itertools as itt
List =['visit', 'houston', 'and', 'san', 'antonio', 'and', 'austin', 'and', 'corpus', 'christi']
>>> [list(g) for k, g in itt.groupby(L, key=lambda word: word=='and') if not k]
results-
[['visit', 'houston'], ['san', 'antonio'], ['austin'], ['corpus', 'christi']]
Combine them into a single string (or leave them that way if that's how they started), then split it by and and split each substring again:
var words = new[] { "visit", "houston", "and", "san", "antonio", "and", "austin", "and", "corpus", "christi" };
var sentence = string.Join(' ', words); // "visit houston and san .... christi"
var cities = sentence.Split("and", StringSplitOptions.None)
.Select(x => x.Split(' ', StringSplitOptions.RemoveEmptyEntries))
.ToArray();
Note that if your input includes spaces in them (like ..., "and", "san antonio", ...) then this may need some adjusting.
For this you can use System.Linq.GroupBy with a little modification to add key as number of "and"s preceding the given word.
Group method:
static string[][] GroupByWord(string[] input, string word)
{
var i = 0;
return input.GroupBy(w =>
{
if (w == word)
{
i++;
return -1;
}
return i;
})
.Where(kv => kv.Key != -1) // remove group with "and" strings
.Select(s => s.ToArray()) // make arrays from groups ["visit", "houston"] for example
.ToArray(); // make arrays of arrays
}
Calling method:
var input = new[] { "visit", "houston", "and", "san", "antonio", "and", "austin", "and", "corpus", "christi" };
var result = GroupByWord(input, "and");
A simpler approach using loops.
IEnumerable<IEnumerable<string>> GetList(IEnumerable<string> source)
{
while(source.Any())
{
var returnValue = source.TakeWhile(x=>!x.Equals("and")).ToList();
yield return returnValue;
source = source.Skip(returnValue.Count()+1);
}
}
You can now do,
var words = new[] { "visit", "houston", "and", "san", "antonio", "and", "austin", "and", "corpus", "christi" };
var result = GetList(words);
Output

LINQ search in list

I have this list
var allPlaces = new[]
{
new { Name = "Red apple", OtherKnownNames = "Green" },
new { Name = "Orange", OtherKnownNames = "" },
new { Name = "Banana", OtherKnownNames = "the" },
}.ToList();
my query is "the apple"
my code does not return me first and third item, query has 2 words separated by a space, I want if any word in query starts with the Name or OtherKnownName should be returned.
var query = "the apple";
var queryParts = query.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var filteredList =
allPlaces
.Where(p =>
p.Name
.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
.Any(pp => queryParts.Any(qp => qp.StartsWith(pp)))
|| p.OtherKnownNames
.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
.Any(pp => queryParts.Any(qp => qp.StartsWith(pp))))
.ToList();
Assuming you want to ignore case, and accept names that match the beginning of query words (based on your example with StartsWith).
Use an extension method to make splitting without empty entries nicer:
public static string[] SplitNoEmpty(this string s, params char[] seps) => s.Split(seps, StringSplitOptions.RemoveEmptyEntries);
You can simply split up the query string and search for matches:
var qwords = query.SplitNoEmpty(' ');
var ans = allPlaces.Where(p => qwords.Any(qw => (p.Name + " " + p.OtherKnownNames).SplitNoEmpty(' ')
.Any(nw => qw.StartsWith(nw, StringComparison.CurrentCultureIgnoreCase))
)
)
.ToList();

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 get the 1st match between 2 string arrays

I have 2 string arrays. The one is the base and the other is changing.
string[] baseArray = { "Gold", "Silver", "Bronze" };
string[] readArray = { "Bronze", "Silver", "Gold" };
// After comparing the readArray over the baseArray the result should be this
//string match = "Gold";
I want to get the 1st in order of the baseArray.
//Example2
string[] readArray = { "Bronze", "Silver" };
//string match should be "Silver"
If you only want one result, using LINQ:
string firstMatch = baseArray.FirstOrDefault(readArray.Contains);
If you only want one result, not using LINQ:
string firstMatch = null;
foreach(string element in baseArray)
{
if (Array.IndexOf(readArray, element) >= 0)
{
firstMatch = element;
break;
}
}
If you want all matching elements, using LINQ:
string[] common = baseArray.Intersect(readArray).ToArray();
If you want all matching elements, not using LINQ:
HasSet<string> common = new HashSet<string>(readArray);
result.Intersect(baseArray);
var match = baseArray.FirstOrDefault(x => readArray.Contains(x));

How to use LINQ select something contain string[]

How to use linq to select something fit the conditions below,
I want select the words JUST contains the string in ArStr[], i.e. a,b,c
In the Wordslist, "aabb" don't contain "c", "aacc" don't contain "b", "aabbccd" contain "d".
So they are not the words I want.
Please help.
Wordslist :
aabb
aacc
aaabbcc
aabbbcc
aabbccd
ArStr[] :
"a"
"b"
"c"
Expected Query:
aaabbcc
aabbbcc
IEnumerable<Word> Query =
from Word in Wordslist
where
Word.Value.Contains(ArStr[0]) // 1
&& Word.Value.Contains(ArStr[1]) // 2
&& Word.Value.Contains(ArStr[2]) // 3
select Word;
You can construct a set of white-list characters and then filter those words that are set-equal with that white-list (ignoring duplicates and order).
var chars = new HashSet<char>(ArStr); // Construct white-list set
var query = from word in wordsList
where chars.SetEquals(word) // Word must be set-equal with white-list
select word;
or
var query = wordsList.Where(chars.SetEquals);
As you've probably noticed, the query you've written does return "aabbccd", because that string contain "a", it contains "b", and it contains "c".
Assuming that ArStr can only contain one-character strings, and you want to return strings that contain only the specified characters, so you should say (adapted from Ani's answer):
var chars = new HashSet<char>(ArStr.Select(s => s[0]));
var query = wordslist.Where(w => chars.SetEquals(w.Value));
However, if the ArStr elements could be more than one character long, the problem needs to be better defined, and the solution will be more complicated.
Use this method to evaluate a word if it passes your condition or not:
bool HasValidCharacters(string word)
{
var allowedCharacters = new List<string> { "a", "b", "c" };
return string.Join("", word.GroupBy(c => c)
.Select(g => g.Key)
.OrderBy(g => g))
.Equals(string.Join("", allowedCharacters.OrderBy(c => c)));
}
Then simply call the method to get the required list:
var words = new List<string> { "aabb", "aacc", "aaabbcc", "aabbbcc", "aabbccd" };
var matchingWords = words.Where(HasValidCharacters);
You could try this:
List<String> words = new List<string> { "aabb", "aacc", "aaabbcc", "aabbbcc", "aabbccd" };
List<string> allowed = new List<string> { "a", "b", "c" };
var lst = words.Where(word => allowed.All(a => word.Contains(a) && !Regex.IsMatch(word, "[^" + string.Join("", allowed) + "]"))).ToList();
Just another way to implement it.
I think you can use String.Trim Method (Char()) on each element , then the empty element is you want .
var arr = new string[] { "aabb", "aacc", "aaabbcc", "aabbbcc", "aabbccd" };
var arStr = new string[] { "a", "b", "c" };
var str = string.Join("", arStr);
var result = from p in arr
let arCharL = arStr.Select(a => Convert.ToChar(a)).ToArray()
let arCharR = p.ToCharArray()
where p.Trim(arCharL).Length == 0 && str.Trim(arCharR).Length == 0
select p;

Categories

Resources