Check if chars of a string contains in another string with LINQ - c#

I'm making a Scrabble game in the command line with C#. The player must input some words like list below:
Word
Points
some
6
first
8
potsie
8
day
7
could
8
postie
8
from
9
have
10
back
12
this
7
The letters the player got are this:
sopitez
This value is a string. I'll check if the letters contains in the words. For this I've tried this code:
String highst = (from word
in words
where word.Contains(letters)
orderby points descending
select word).First();
But it doesn't work how I'll it. This code wouldn't select any word. I know the reason why because sopitez doesn't contain in any word.
My question now is there a way to check the chars in the string letters contain into the words whitout looping over the chars.
Note: Each letter must be used at most once in the solution.
If I calculate the result it must be potsie or postie. (I must write the logic for that)
P.S.: I'm playing this game: www.codingame.com/ide/puzzle/scrabble

This will not be performant at all but at least it will do the trick. Notice that I've used a dictionary just for the sake of simplicity (also I don't see why you would have repeated words like "potsie", I've never played scrabble). You can as well use a list of Tuples if you follow this code
EDIT: I changed this according to the OP's new comments
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var letters = new HashSet<char>("sopitez");
var wordsMap = new Dictionary<string, int>()
{
{"some", 6}, {"first", 8}, {"potsie", 8}, {"postie", 8}, {"day", 7},
{"could", 8}, {"from", 9}, {"have", 10}, {"back", 12},
{"this", 7}
};
var highest = wordsMap
.Select(kvp => {
var word = kvp.Key;
var points = kvp.Value;
var matchCount = kvp.Key.Sum(c => letters.Contains(c) ? 1 : 0);
return new {
Word = word,
Points = points,
MatchCount = matchCount,
FullMatch = matchCount == word.Length,
EstimatedScore = points * matchCount /(double) word.Length // This can vary... it's just my guess for an "Estiamted score"
};
})
.OrderByDescending(x => x.FullMatch)
.ThenByDescending(x => x.EstimatedScore);
foreach (var anon in highest)
{
Console.WriteLine("{0}", anon);
}
}
}

The problem here is that Contains checks to see if one string contains another; it is not checking to see if it contains all of those characters. You need to replace each string in your dictionary with a HashSet<char> and perform set comparisons like IsSubset or IsSuperset to determine if the letters are matching.
Here is what you're doing:
string a= "Hello";
string b= "elHlo";
bool doesContain = b.Contains(a); //This returns false
Here is what you need to do:
var setA = new HashSet<char>(a);
var setB = new HashSet<char>(b);
bool isSubset = a.IsSubsetOf(b); //This returns true
Update
Actually, this is wrong, because sets remove duplicate elements. But essentially you are misusing Contains. You'll need some more complicated sequence comparison that can allow duplicate letters.
Update2
You need this for word/letters comparison:
//Compares counts of each letter in word and tiles
bool WordCanBeMadeFromLetters(string word, string tileLetters) {
var tileLetterCounts = GetLetterCounts(tileLetters);
var wordLetterCounts = GetLetterCounts(word);
return wordLetterCounts.All(letter =>
tileLetterCounts.ContainsKey(letter.Key)
&& tileLetterCounts[letter.Key] >= letter.Value);
}
//Gets dictionary of letter/# of letter in word
Dictionary<char, int> GetLetterCounts(string word){
return word
.GroupBy(c => c)
.ToDictionary(
grp => grp.Key,
grp => grp.Count());
}
So your original example can look like this:
String highst = (from word
in words
where WordCanBeMadeFromLetters(word, letters)
orderby points descending
select word).First();

Since letters can repeat, I think you need something like this (of course that's not very efficient, but pure LINQ):
var letters = "sopitezwss";
var words = new Dictionary<string, int>() {
{"some", 6}, {"first", 8}, {"potsie", 8}, {"day", 7},
{"could", 8}, {"from", 9}, {"have", 10}, {"back", 12},
{"this", 7}, {"postie", 8}, {"swiss", 15}
};
var highest = (from word
in words
where word.Key.GroupBy(c => c).All(c => letters.Count(l => l == c.Key) >= c.Count())
orderby word.Value descending
select word);

Related

How to sort a list of strings by prefix number then alphabetically

My goal is to sort a List<string> in messy order to an order like this ["1", "1a", "2", "2a", "2b", "a", "b"]
My code is a little long, so I've included it at this link https://dotnetfiddle.net/wZ0dTG.
What I'm trying to do is split the strings using Regex.Split(string, "([0-9]+)")[0] then based on which strings pass int.TryParse, I sort the list numerically or alphabetically.
The regex matches all the integers contained within the string.
Until I apply the regex, it sorts. Although it sorts, it doesn't sort them properly.
When I apply the regex, I get this error:
ArgumentException: Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: 'System.Comparison`1[Ndot_Partnering_Tool.Data.Models.Project
So you have to split your strings into an (optional) numeric part and an (optional) rest. This can be done by a Regex:
var match = Regex.Match(item, #"(?<number>\d+)?(?<rest>.*)$");
The "number" part matches one or more digits, but is optional (question mark), the "rest" part matches the whole rest of the string.
Sorting via Linq:
var input = new List<string>{ "12a", "1", "1a", "2", "2a", "2b", "a", "b", "12a" };
var sorted = input.OrderBy(item =>
{
var match = Regex.Match(item, #"(?<number>\d+)?(?<rest>.*)$");
return Tuple.Create(match.Groups["number"].Success ? int.Parse(match.Groups["number"].Value) : -1, match.Groups["rest"].Value);
}).ToList();
(I deliberately decided to put the items without a leading number before the rest; this was not specified in the question).
Output: a, b, 1, 1a, 2, 2a, 2b, 12a
For this particular task the OrderBy method is perfect for you. I would use that instead of Regex.
OrderBy uses lambda expressions as a key to sort.
Since letters are after numbers in the alphabet this method is using, you can actually just sort by default.
You can do:
List<string> List = new List<string>() {"a", "2b", "1a", "1", "2", "2a", "b", "1b" };
List<string> OrderedList = List.OrderBy(x => x).ToList();
The OrderBy method returns IEnumerable so you have to convert it back to List.
Output:
The original list: a 2b 1a 1 2 2a b 1b
The ordered list: 1 1a 1b 2 2a 2b a b
Two problems:
SplitRegex() failes on argument "a" because it does not match regex (RegEx.Split returns array with one element). You can use this code:
return Regex.Split(x, "([0-9]+)").ElementAtOrDefault(1) ?? string.Empty;
When neither x nor y can be converted to integer, you call CompareString() for x and y, but x and y are not whole strings, they are only numerical parts (and because of that empty). You need to pass list items as is to comparer and extract numbers there:
bool leftcanconvertflag = Int32.TryParse(SplitRegex(x), out leftconvertresult);
bool rightcanconvertflag = Int32.TryParse(SplitRegex(y), out rightconvertresult);
if (leftcanconvertflag && !rightcanconvertflag)
{
compareresult = -1;
}
if (!leftcanconvertflag && rightcanconvertflag)
{
compareresult = 1;
}
if (leftcanconvertflag && rightcanconvertflag)
{
compareresult = leftconvertresult.CompareTo(rightconvertresult);
}
if (!leftcanconvertflag && !rightcanconvertflag)
{
compareresult = CompareString(x, y);
}
and sort list like this:
list.Sort(CompareContractNumbers);

How to replace list of strings in text with items from another list?

Suppose I have a list of strings {"boy", "car", "ball"} and a text "the boy sold his car to buy a ball".
Given another string list {"dog", "bar", "bone"}, my objective is to find all occurrences of the first list inside the text and swap them for the strings of the second list:
BEFORE: the [boy] sold his [car] to buy a [ball]
AFTER: the [dog] sold his [bar] to buy a [bone]
My first thought was to use Regex but I have no idea how to associate a list of strings into a regex and I don't want to write Aho-Corasick.
What is the right way to go for that?
Another example:
Text: aaa bbb abab aabb bbaa ubab
replacing {aa, bb, ab, ub} for {11, 22, 35, &x}
BEFORE: [aa]a [bb]b [ab][ab] [aa][bb] [bb][aa] [ub][ab]
AFTER: [11]a [22]b [35][35] [11][22] [22][11] [&x][35]
If you want to use regex, you may use something like this:
var findList = new List<string>() { "boy", "car", "ball" };
var replaceList = new List<string>() { "dog", "bar", "bone" };
// Create a dictionary from the lists or have a dictionary from the beginning.
var dictKeywords = findList.Select((s, i) => new { s, i })
.ToDictionary(x => x.s, x => replaceList[x.i]);
string input = "the boy sold his car to buy a ball";
// Construct the regex pattern by joining the dictionary keys with an 'OR' operator.
string pattern = string.Join("|", dictKeywords.Keys.Select(s => Regex.Escape(s)));
string output =
Regex.Replace(input, pattern, delegate (Match m)
{
string replacement;
if (dictKeywords.TryGetValue(m.Value, out replacement)) return replacement;
return m.Value;
});
Console.WriteLine(output);
Output:
the dog sold his bar to buy a bone
No need to use Regex, string.Replace would suffice
var input = "the boy sold his car to buy a ball";
var oldvalues = new List<string>() { "boy", "car", "ball" };
var newValues = new List<string>() { "dog", "bar", "bone" };
var output = input;
for (int i = 0; i < oldvalues.Count; i++)
{
output = output.Replace(oldvalues[i], newValues[i]);
}
Console.WriteLine(output);

How to get year in Hong kong language (culture info : zh-hk)

I want to match the string in Hong kong language
I have month and year as below in hongkong language
二零一六年六月份 ===>June 2016
二零一五年六月份 ===>June 2015
I have use culture info (zh-HK) to get month like
But how to get year? Please help
Basically, you need to create a dictionary that uses the Chinese characters as the key and the corresponding numbers as the value:
var dict = new Dictionary<String, String>() {
{"零", "0"},
{"一", "1"},
{"二", "2"},
{"三", "3"},
{"四", "4"},
{"五", "5"},
{"六", "6"},
{"七", "7"},
{"八", "8"},
{"九", "9"},
{"十", "1"} // this is needed for the months to work. If you know Chinese you would know what I mean
};
Then, you split the input string with the separator "年":
string[] twoParts = inputString.Split('年');
You loop through each character of the first part. Using the dictionary you created, you can easily get 2016 from "二零一六".
For the second part, check whether "份" is present at the end. If it is, substring it off. (sometimes months can be written without "份"). After that, do one more substring to get rid of the "月".
Now you use the dictionary above again to turn something like "十二" to "12"
Now you have the year and the month, just create a new instance of DateTime!
Here's the full code:
string inputString = ...;
var dict = new Dictionary<String, String>() {
{"零", "0"},
{"一", "1"},
{"二", "2"},
{"三", "3"},
{"四", "4"},
{"五", "5"},
{"六", "6"},
{"七", "7"},
{"八", "8"},
{"九", "9"},
{"十", "1"} // this is needed for the months to work. If you know Chinese you would know what I mean
};
string[] twoParts = inputString.Split ('年');
StringBuilder yearBuilder = new StringBuilder ();
foreach (var character in twoParts[0]) {
yearBuilder.Append (dict [character.ToString ()]);
}
string month = twoParts [1];
if (month [month.Length - 1] == '份') {
month = month.Substring (0, month.Length - 1);
}
month = month.Substring (0, month.Length - 1);
StringBuilder monthBuilder = new StringBuilder ();
foreach (var character in month) {
monthBuilder.Append (dict [character.ToString ()]);
}
var date = new DateTime (Convert.ToInt32 (yearBuilder.ToString()), Convert.ToInt32 (monthBuilder.ToString()), 1);
Console.WriteLine (date);
EDIT:
I just realized that this doesn't work if the month is October, in which case it will parse to January. To fix this, you need to use a separate dictionary for the months. Since the SE editor doesn't allow me to enter too many Chinese characters, I will try to tell you want to put in this dictionary in the comments.
When you parse the months, please use the new dictionary. So now the month parsing code will look like this:
month = month.Substring (0, month.Length - 1);
string monthNumberString = newDict[month];
No need for the for each loop.

Using group by in C#

Could anyone explain what this sample of code is doing? I can't quite grasp how the words string is being grouped. Is it taking the first letter of each word and grouping them somehow?
// Create a data source.
string[] words = { "apples", "blueberries", "oranges", "bananas", "apricots" };
// Create the query.
var wordGroups1 =
from w in words
group w by w[0] into fruitGroup
where fruitGroup.Count() >= 2
select new { FirstLetter = fruitGroup.Key, Words = fruitGroup.Count() };
The LINQ query groups all the words by their first character. It then removes all groups which contain only one element (=keeps all groups with two or more elements). At the end the groups are filled into new anonymous objects containing the first letter and number of words found starting with that letter.
The LINQ Documentation and samples should get you started reading and writing code like that.
// Create a data source.
string[] words = { "apples", "blueberries", "oranges", "bananas", "apricots" };
// Create the query.
var wordGroups1 =
from w in words //w is every single string in words
group w by w[0] into fruitGroup //group based on first character of w
where fruitGroup.Count() >= 2 //select those groups which have 2 or more members
//having the result so far, it makes what is needed with select
select new { FirstLetter = fruitGroup.Key, Words = fruitGroup.Count() };
Another example. In the array show the frequency of string's length:
var wordGroups1 =
from w in words
group w by w.Length into myGroup
select new { StringLength = myGroup.Key, Freq = myGroup.Count() };
//result: 1 6-length string
// 1 11-length string
// 2 7-length string
// 1 8-length string

Regular expression in C#

i have text something like this.
##MMIVLoader#ProductVer#4.1.2#BCM_7400S_LE#Product#Aug 21 2009#
##MMIVLib#ObjectVer#4.1.2#BCM_7400S_LE#Product#Aug 21 2009#
##HuaweFGDLDrv#ObjectVer#01.00.09#7324#PRODUCT#Aug 20 2009#
##ProtectVer#ObjectVer#127.8.1 #BCM_SDE5.03#PRODUCT#Aug 4 2009 06:56:19#
##KernelSw#ObjectVer#0.0.1#BCM-7454#PRODUCT# Dec 19 2007#
##ReceiverSw#ObjectVer#E.5.6.001#HWBC01ZS#PRODUCT#May 3 2010#
i want the out put in an array like
MMIVLoader 4.1.2
MMIVLib 4.1.2
HuaweFGDLDrv 01.00.09
ProtectVer 127.8.1
KernelSw 0.0.1
ReceiverSw E.5.6.001
Can any one suggest me how to do this in c# using regular expression or is there a any sophisticated way to do this
thanks in advance
This is easy, you can just split by # (removing the empty items) and pull the first and third items.
var list = myString.Split(new String[] {Environment.NewLine},
StringSplitOptions.RemoveEmptyEntries)
.Select(item => item.Split(new char[] {'#'},
StringSplitOptions.RemoveEmptyEntries))
.Where(a => a.Length > 2)
.Select(a => new { Item = a[0], Version = a[2] }).ToArray();
Or simply remove extra stuff from line
Regex.Replace(text, #"^##([^#]+)#[^#]+#([^#]+).*", "$1,$2",RegexOptions.Multiline);
to get
MMIVLoader,4.1.2
MMIVLib,4.1.2
HuaweFGDLDrv,01.00.09
ProtectVer,127.8.1
KernelSw,0.0.1
ReceiverSw,E.5.6.001
And then, just split by comma on each line to get array
If you do want a crazy regex solution, you can use this:
var matches = Regex.Matches(
input,
"##(?<name>.*?)#(Product|Object)Ver#(?<ver>.*?)#",
RegexOptions.IgnoreCase
).Cast<Match>().Select(m => m.Groups);
foreach (var match in matches)
{
Console.WriteLine("{0} {1}", match["name"], match["ver"]);
}
For completeness, here is a LINQ version with query syntax :)
string[] lines = new string[] {
"##MMIVLoader#ProductVer#4.1.2#BCM_7400S_LE#Product#Aug 21 2009#",
"##MMIVLib#ObjectVer#4.1.2#BCM_7400S_LE#Product#Aug 21 2009#",
"##HuaweFGDLDrv#ObjectVer#01.00.09#7324#PRODUCT#Aug 20 2009#",
"##ProtectVer#ObjectVer#127.8.1 #BCM_SDE5.03#PRODUCT#Aug 4 2009 06:56:19#",
"##KernelSw#ObjectVer#0.0.1#BCM-7454#PRODUCT# Dec 19 2007#",
"##ReceiverSw#ObjectVer#E.5.6.001#HWBC01ZS#PRODUCT#May 3 2010#" };
var q = from components in
(from line in lines
select line.Split(new char[] { '#' },
StringSplitOptions.RemoveEmptyEntries))
select new { Name = components[0], Version = components[2] };
foreach (var item in q)
{
Console.WriteLine("Item: Name={0} Version={1}", item.Name, item.Version);
}

Categories

Resources