We have a list containing names of countries. We need to find names of countries from list b/w two letters. Like names of all countries with name starting b/w A-G and so on. We create following linq query but its ugly.
var countryAG = from elements in countryList
where elements.StartsWith("A") ||
elements.StartsWith("B") ||
elements.StartsWith("C") ||
elements.StartsWith("D") ||
elements.StartsWith("E") ||
elements.StartsWith("F") ||
elements.StartsWith("G") ||
elements.StartsWith("H")
select elements;
where countryList is created in C#
List< string> countryList = new List< string>();
Any help or any other efficient way to accomplish above task?
var countryAG = from elements in countryList
where elements[0] >= 'A' && elements[0] <= 'H'
select elements;
Chars are just numbers really, thus you can compare them as such
I can't test it right now, but I would try
countryList.Where((s) => s[0] <= 'A' && s[0] >= 'G');
You could use a prefix list and then use the prefix list for comparison - this way you can easily use different prefix lists based on what range you are interested in:
List<string> prefixList = new List<string>() { "A", "B", "C", "D", "E", "F", "G" };
var countryAG = countryList.Where( x=> prefixList.Any( p => x.StartsWith(p)));
Try
char[] startingLetters = new char[] {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'};
var countryAG =
from elements in countryList
where elements.IndexOfAny(startingLetters, 0, 1) == 0
select elements;
See here for information on IndexOfAny.
Try use this code:
var start = "a";
var end = "g";
var regex = new Regex(string.Format("^[{0}-{1}]", start, end));
var result = list.Where(x => regex.Match(x.ToLowerInvariant()).Success);
'start' and 'end' are static as an example.
I have two extension functions:
public static IEnumerable<char> Range(char start, char end)
{
return Enumerable.Range((int)start, (int)end - (int)start + 1).Select(i => (char)i);
}
which creates a range of characters, and
public static bool In(this string source, IEnumerable<string> collection)
{
return collection.Contains(source);
}
which is just the inverse of Contains, mostly for readability.
Together I can do:
where elements[0].In(Range('a', 'f')))
List<string> mainList = new List<string>()
{
"A","B","DD","EE","F","G","EE","CC","DD","Q","R","CC"
};
List<string> searchList = new List<string>() { "DD", "EE", "CC" };
var finalList = mainList.Where(x => searchList.Any(p => p == x)).ToList();
Related
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
I have a collection of objects that bind to a System.Web.UI.WebControls.ListControl:
foreach (var answer in SomeCollection)
{
System.Web.UI.WebControls.ListItem listItem = new System.Web.UI.WebControls.ListItem();
listItem.Value = answer.ID.ToString();
listItem.Text = answer.AnswerText;
listControl.Items.Add(listItem);
}
I now want to add a prefix to each answer of "A", "B", "C", "D", etc. So the output would look like:
A. Answer 1
B. Answer 2
C. Answer 3
The most answers we have is 10 so there is no need to worry about running out of letters. What is the right way to do this?
I have tried the following and it works, however I feel there should be a better way:
char[] alphabet = new char[]{ 'A', 'B', 'C', 'D', 'E', 'F', 'G',..., 'Y', 'Z' };
for (int i = 0; i < SomeCollection.Count; i++)
{
var answer = SomeCollection[i]
System.Web.UI.WebControls.ListItem listItem = new System.Web.UI.WebControls.ListItem();
listItem.Value = answer.ID.ToString();
listItem.Text = alphabet[i] + "." + answer.AnswerText;
listControl.Items.Add(listItem);
}
I'm assuming the main concern is that big character array. The following should also work...
char letter = 'A';
for (int i = 0; i < SomeCollection.Count; i++)
{
var answer = SomeCollection[i]
System.Web.UI.WebControls.ListItem listItem = new System.Web.UI.WebControls.ListItem();
listItem.Value = answer.ID.ToString();
listItem.Text = letter + "." + answer.AnswerText;
listControl.Items.Add(listItem);
letter++;
}
Off the top of my head, it sounds like you could zip a generated Enum.Range of characters with your collection... something like this, perhaps:
var someCollection = new List<string> {"Item1", "Item2", "Item3"};
var prefixes = Enumerable.Range('a', 'z' - 'a' + 1).Select(x => (char)x);
...
var items = prefixes.Zip(someCollection, (a,b) => a + " " + b);
Doing this, you can just assign the items collection directly to your listControl.
I don't actually have a compiler at hand, but how about something along the lines of this?
var charcode = (int)'A';
var items = SomeCollection.Select((answer, index) => new ListItem{
Value = answer.ID,
Text = String.Format("{0}.{1}", (char)(charcode+index), answer.AnswerText)
});
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;
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();
Can anyone help me with a nice LINQ expression for transforming a list of strings in another list containing only the shortest distinct common prefixes for the strings? The delimiter for prefixes is ..
Example: ["A", "A.B.D", "A", "A.B","E","F.E", "F","B.C"]
Goes to: ["A", "E", "F", "B.C"]
Removed:
"A.B.D" and "A.B" because the prefix "A" is already in the list
"A" because is duplicate
"F.E" because "F" already in list
Thanks!
Here you go:
from set in
(from item in list select item.Split('.')).GroupBy(x => x[0])
select
set.First()
.TakeWhile((part, index) => set.All(x => x.Length > index && x[index].Equals(part)))
.Aggregate((x, y) => String.Format("{0}.{1}", x, y));
By way of explanation:
First, we split all the strings by '.' and group by their first token.
Then, we look at the first element of each grouping, and we take parts from it while every element of that group continues to match (TakeWhile).
Then, we take all those parts and recompose them with the Aggregate(String.Format).
var items = new[] { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var result = items
.OrderBy(s => s.Length)
.Distinct()
.ToLookup(s => s.Substring(0, 1))
.Select(g => g.First());
Order the items by their length, call distinct to remove duplicates, convert to groupings based on the first character, and select the first item in each group.
Yields:
"A", "E", "F", "B.C"
Edit: You probably don't even need Distinct as your selecting the first item in each group anyway, so it's really redundant.
EDIT: thanks to the comments for pointing out a bug in my earlier approach.
To get around that shortcoming this query should work:
var list = new List<string> { "A.B.D", "A", "A.B","E","F.E", "F","B.C", "B.C.D" };
var result = list.OrderBy(s => s)
.GroupBy(s => s[0])
.Select(g => g.First());
foreach (var s in result)
{
Console.WriteLine(s);
}
Incorrect approach:
The following query will group each string by the first character. Next, if the group count has more than one item the key is selected, otherwise the single item is selected.
var list = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var result = list.GroupBy(s => s[0])
.Select(g => g.Count() > 1 ? g.Key.ToString() : g.Single());
foreach (var s in result)
{
Console.WriteLine(s);
}
Nailed it - assuming that if the source list contains "Q.X" & "Q.Y" then the result should contain "Q".
var source = new []
{
"A", "A.B.D", "A",
"A.B", "E", "F.E",
"F", "B.C",
"Q.X", "Q.Y",
"D.A.A", "D.A.B",
};
Func<string, int> startsWithCount =
s => source.Where(x => x.StartsWith(s)).Count();
var results =
(from x in source.Distinct()
let xx = x.Split('.')
let splits = Enumerable
.Range(1, xx.Length)
.Select(n => String.Join(".", xx.Take(n)))
let first = startsWithCount(splits.First())
select splits
.Where(s => startsWithCount(s) == first)
.Last()
).Distinct();
// results == ["A", "E", "F", "B.C", "Q", "D.A"]
string[] source = {"A", "A.B", "A.B.D", "B.C", "B.C.D", "B.D", "E", "F", "F.E"};
var result =
source.Distinct()
.Select(str => str.Split('.'))
.GroupBy(arr => arr[0])
.Select(g =>
{
return string.Join(".",
g.Aggregate((arr1, arr2) =>
{
return arr1.TakeWhile((str, index) => index < arr2.Length
&& str.Equals(arr2[index]))
.ToArray();
}));
});
Steps:
(1) Remove duplicated elements by Distinct()
(2) Split each element to an array, also get ready to be grouped
(3) Group those arrays by the first string in the array
(4) For each group, create one common prefix by aggregating all arrays in the group. The logic for aggregating is that for two arrays arr1 and arr2, take the elements in arr1 until (1)out of bounds (2) corresponding element in arr2 is different
Note: I add two return statements in the code, to make it look cleaner. It can be shorter if remove return and its {} brackets.
How about:
var possible = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var shortest = possible.Distinct().Where(x => possible.Distinct().Where(y => !y.Equals(x) && x.StartsWith(y)).Count() == 0).ToList();
It checks the list against itself excluding items that are equal and any items that starts with any of the other items. I'm not sure about the effeciency though :)
I think it might be hard to solve with one single nice looking linq expression so I wrote a recursive function using linq that solves the problem:
class Program
{
static void Main(string[] args)
{
var input = new string[] { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C", "B.C.D", "B.E" };
var output = FilterFunc(input);
foreach (var str in output)
Console.WriteLine(str);
Console.ReadLine();
}
static string[] FilterFunc(string[] input)
{
if (input.Length <= 1)
return input;
else
{
var firstElem = input[0];
var indexNr = firstElem.Length;
var maxFilteredElems = 0;
for (int i = firstElem.Length; i > 0; i--)
{
var numberOfFilteredElems = input.Where(x => x.StartsWith(firstElem.Substring(0, i))).Count();
if (numberOfFilteredElems > maxFilteredElems)
{
maxFilteredElems = numberOfFilteredElems;
indexNr = i;
}
}
var prefix = firstElem.Substring(0, indexNr);
var recursiveResult = FilterFunc(input.Where(x => !x.StartsWith(prefix)).ToArray());
var result = recursiveResult.ToList();
prefix = prefix.EndsWith(".") ? prefix.Substring(0, prefix.Length - 1) : prefix;
result.Insert(0, prefix);
return result.ToArray();
}
}
}
The code could probably be more effective and more organized but don't have time for that now. I think the other solutions are wrong so far, so that's why you get my longer one. I think you need to solve it recursively to be sure to get the shortest list.
My attempt, loop through items removing anything prefixed with another item.
static void Run()
{
var list = new string[] {"A", "A.B.D", "A",
"A.B", "E", "F.E",
"F", "B.C",
"Q.X", "Q.Y",
"D.A.A", "D.A.B"
};
int size = 0;
var prefixList = new string[list.Length];
Array.Copy(list, prefixList, list.Length);
for (int i = 0; i < list.Length; i++)
prefixList
= prefixList
.Where(c => !c.StartsWith(list[i]) || c == list[i])
.Distinct()
.ToArray();
foreach (string s in prefixList)
Console.WriteLine(s);
Console.ReadLine();
}
var list = new[] { "A.B.D", "A", "E", "A.B", "F", "F.E", "B.C.D", "B.C" };
var result = from s in list
group s by s.Split('.').First() into g
select LongestCommonPrefix(g);
foreach (var s in result)
{
Console.WriteLine(s);
}
Output:
A
E
F
B.C
Method to find longest common prefix from here (replace / with .).
My understanding of the question says a list containing both "B.C" and "B.E" but no "B" would get both "B.C" and "B.E".
string[] items = { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
char delimiter = '.';
var result = (from item in items.Distinct()
where !items.Any(other => item.StartsWith(other + delimiter))
select item).ToArray();
foreach (var item in result)
{
Console.WriteLine(item);
}
output
A
E
F
B.C
also works with multi-character prefixes
string[] items =
{
"Alpha",
"Alpha.Beta.Delta",
"Alpha",
"Alpha.Beta",
"Echo",
"Foxtrot.Echo",
"Foxtrot",
"Baker.Charlie"
};
gets
Alpha
Echo
Foxtrot
Baker.Charlie
If I strictly stick to the definition that dave provided, the answer is easier than it seems:
remove duplicates => distinct
remove any item that starts with any other item in the list
so we get:
from item in items.Distinct()
where !items.Any(other => other != item && item.StartsWith(other + '.'))
select item;
For the B.C and B.D question, this works as specified: Neither one includes the other, so none of the removing conditions mentioned by dave is triggered.
I admit that there might be more exciting anwers, but I'm afraid that's just not in the question ;)
Update: added delimiter to where clause in order to account for multi-char words. thanks svick!
var list = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var result = (list.Select(a => a.Split('.').First())).Distinct();