Counting duplicate chars in a C# string - c#

I'm new to C# and trying to work out how to count the number of duplicates in a string. Example input and output would be:
"indivisibility" -> 1 # 'i' occurs six times
"Indivisibilities" -> 2 # 'i' occurs seven times and 's' occurs twice
"aA11" -> 2 # 'a' and '1'
"ABBA" -> 2 # 'A' and 'B' each occur twice
My code so far is as follows:
using System;
using System.Collections;
using System.Linq;
public class Kata
{
public static int DuplicateCount(string str)
{
Stack checkedChars = new Stack();
Stack dupChars = new Stack();
str = str.ToLower();
for (int i=1; i < str.Length; i++) {
var alreadyCounted = checkedChars.Contains(str[i]) && dupChars.Contains(str[i]);
if (!checkedChars.Contains(str[i])) {
checkedChars.Push(str[i]);
} else if (checkedChars.Contains(str[i])) {
dupChars.Push(str[i]);
} else if (alreadyCounted) {
break;
}
}
return dupChars.Count;
}
}
My approach is to loop through each character in the string. If it hasn't been seen before, to add it to a 'checkedChars' Stack (to keep track of it). If it's already been counted, add it to a 'dupChars' Stack. However, this is failing the tests. E.g:
aabbcde is the string, and the test fails with: Expected: 2 But Was: 1
Also when I console out errors, it appears that the checkedChars Stack is empty.
Can anyone spot where I have gone wrong please?

I'd suggest you use LINQ instead. It's a more suitable tool for the problem, and it results in much cleaner code:
class Program
{
static void Main(string[] args)
{
var word = "indivisibility";
Console.WriteLine($"{word} has {CountDuplicates(word)} duplicates.");
word = "Indivisibilities";
Console.WriteLine($"{word} has {CountDuplicates(word)} duplicates.");
word = "aA11";
Console.WriteLine($"{word} has {CountDuplicates(word)} duplicates.");
word = "ABBA";
Console.WriteLine($"{word} has {CountDuplicates(word)} duplicates.");
Console.ReadLine();
}
public static int CountDuplicates(string str) =>
(from c in str.ToLower()
group c by c
into grp
where grp.Count() > 1
select grp.Key).Count();
}
}
Here's the output:
indivisibility has 1 duplicates.
Indivisibilities has 2 duplicates.
aA11 has 2 duplicates.
ABBA has 2 duplicates.
Hope this helps.

You need to start the loop at int i = 0, because indexing start at 0 and not 1. So to get the first character you'll need to call str[0].
You can also remove the break as your code will never hit it, since the first 2 conditions are exactly the opposite of each other. Instead check first if alreadyCounted is true and use continue (not break as it will exit the loop entirely!) to skip to the next iteration, to avoid counting the same characters more than once.

you can use LINQ for this -
var str = "aabbcde";
var count = str.ToLower().GroupBy(x => x).Select(y => y).Where(z=>z.Count()>1).Count();

You can also use MoreLinq.CountBy:
using System;
using System.Linq;
using MoreLinq;
namespace ConsoleApp1
{
internal class Program
{
private static int CountDuplicateCharacters(string s)
{
return s?.CountBy(c => c).Where(kvp => kvp.Value > 1).Count() ?? 0;
}
private static void Main(string[] args)
{
foreach (var s in new string[] { "indivisibility", "Indivisibilities", "aA11", "ABBA" })
{
Console.WriteLine(s + ": " + CountDuplicateCharacters(s));
}
}
}
}
In case you do not want to differentiate between lower and upper case you need to supply an EqualityComparer as a second argument to CountBy.

Related

Why is this c# Palindrome Program not working?

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace Palindrome
{
class Program
{
static void Main(string[] args)
{
string filePath = #"C:\Users\Me\Desktop\Palindromes\palindromes.txt";
//This gets the file we need
var meStack = new Stack<string>();
//this creates the stack
foreach (var item in File.ReadLines(filePath))
{
meStack.Push(item.ToUpper());
}
//for every item in the file, push onto the stack and make it upper case
while (meStack.TryPop(out string Line))
{
reverseMe(Line);
}
//While every line in the stack is popped out, every line goes to the fucntion reverseMe
static bool reverseMe(string Line)
{
return
Line == Line.Reverse();
}
//return true if line is the same as the line backwards or false if its not.
}
}
}
How do I get output?
I have written comments to try and understand... but I am not getting a console output. I want the code to take in the file, put all the strings into a stack, and send every line in that stack to the reverseMe() function, which is a bool. The bool will see if the string is the same forward as it is backwards and if so it will return true or false. Basically my console is empty when I try to run this code.. What do I do?
There is a problem in the method reverseMe, The function Reverse gives you collection of char if applied on string, then you need to convert IEnumerable<char> to string by new string() or string.Concat(), like the following code:
static bool reverseMe(string Line)
{
//deleting whitespaces, tabs
Line = Regex.Replace(Line, #"\s+", "");
return Line == new string(Line.Reverse().ToArray());
//or
//return Line == string.Concat(Line.Reverse());
//or like Dmitry comment
//return Line.SequenceEqual(Line.Reverse());
}
Calling reverseMe, and output result like : word is not palindrome
while (meStack.TryPop(out string Line))
{
string isOrNotPalindrome = reverseMe(Line) ? string.Empty : "not";
Console.WriteLine($"{Line} is {isOrNotPalindrome} palindrome");
}
Demo
bool isPalindrome1 = reverseMe("madam");
bool isPalindrome2 = reverseMe("nurses run");
bool isPalindrome3 = reverseMe("AaBbbBaAp");
Result
true
true
false
I hope this will help you fix the issue
Let's start from the problem; I assume that you want to scan all file's lines and print out if the line is a palindrom.
First, we need to implement IsPalindrom method:
private static bool IsPalindrom(string value) {
if (null == value)
return false; // or true, ot throw ArgumentNullException
// We have to prepare the string: when testing for palindrom
// 1. Let's ignore white spaces (' ', '\r', '\t' etc.)
// 2. Let's ignore punctuation (':', ',' etc.)
// 3. Let's ignore cases (i.e. 'M' == 'm')
// So "Madam, I'm Adam" will be a proper palindrom
value = string.Concat(value
.Where(c => !char.IsWhiteSpace(c))
.Where(c => !char.IsPunctuation(c))
.Select(c => char.ToUpperInvariant(c)));
// Instead of Reversing we can just compare:
// [0] and [Length - 1] then [1] and [Length - 2] etc.
for (int i = 0; i < value.Length / 2; ++i)
if (value[i] != value[value.Length - 1 - i])
return false; // we have a counter example: value is NOT a palidrom
// Value has been scanned, no counter examples are found
return true;
}
Time to write Main method:
static void Main(string[] args) {
string filePath = #"C:\Users\Me\Desktop\Palindromes\palindromes.txt";
var result = File
.ReadLines(filePath)
.Where(line => !string.IsNullOrWhiteSpace(line)) // let's skip empty lines
.Select(line => $"{(IsPalindrom(line) ? "Palindrom" : "Not a palindrom")}: \"{line}\"");
// Do not forget to print result on the Console:
foreach (var record in result)
Console.WriteLine(record);
// Pause to have a look at the outcome (wait for a key to be pressed)
Console.ReadKey();
}
Uhm there isn't a Console.WriteLine() to do any actual output after getting results from the reverseMe() function.

Using two parameters (string, int) to define a max number of specific characters in string output

I am not very experienced with C# and have got a task to develop a simple program that can take two parameters; one string and one integer. The string is to be returned, and the integer is supposed to define the max number of each specific character that is returned in the string, i.e.:
input: "aaabbbbc", "2" output: aabbc
input: "acc", "1" output: ac
I've tried looking at different collections like IEnumerator to help make the code easier to write, but as I'm not very experienced I can't sort out how to utilize them.
This is the code that I've written so far:
public static string TwoParameters()
{
Console.Write("Write some characters (i.e. 'cccaabbb'): ");
string myString = Console.ReadLine();
return myString;
Console.Write("Write a number - ");
int max = Convert.ToInt32(Console.Read());
}
public static void Counter(string myString, int max)
{
string myString = TwoParameters(myString);
foreach (char a in myString)
{
var occurences = myString.Count(x => x == 'a');
if (occurences > max)
max = occurences;
}
}
Errors I get when running:
CS0136: Local or parameter 'myString' cannot be declared in scope because of enclosing local scope.
CS1501: No overload for method 'TwoParameters' takes 1 arg.
CS1061: 'string' does not contain a definition for count.
CS0165: Use of unassigned local variable 'myString'.
CS7036: There is no argument given that corresponds to the required formal parameter 'myString' of 'Program.Counter(string, int)'
Any kind of pointers to what I'm doing wrong, suggestions to how I can improve my code and/or finish it up for the program to make the output will be hugely appreciated.
A string can be treated as an IEnumerable<char>. You can use LINQ to first group the characters then take only 2 from each group, eg :
var input="aaabbbbc";
var max=2;
var chars=input.GroupBy(c=>c)
.SelectMany(g=>g.Take(2))
.ToArray();
var result=new String(chars);
This produces
aabbc
This query groups the characters together with GroupBy and then takes only max from each group with Take. SelectMany flattens all the IEnumerable<char> returned from Take into a single IEnumerable<char> that can be used to create a string
This function would also respect the order within the string, so aabcabc, 2 would result into aabcbc:
static string ReturnMaxOccurences(string source, int count)
{
return source.Aggregate(new Accumulator(), (acc, c) =>
{
acc.Histogram.TryGetValue(c, out int charCount);
if (charCount < count)
acc.Result.Append(c);
acc.Histogram[c] = ++charCount;
return acc;
}, acc => acc.Result.ToString());
}
But you need also this little helper class:
public class Accumulator
{
public Dictionary<char, int> Histogram { get; } = new Dictionary<char, int>();
public StringBuilder Result { get; } = new StringBuilder();
}
This method iterates the whole string and save within a histogram the occurences of each character. If the value is lower than the desired max value it will be added to the result string, otherwise it simply steps over the character and continues with the next.
Pointers to what you are doing wrong:
you reset your given max to the counts in your string
you only handle "a"
your TwoParameters function has unreachable code
you try to declare a variable name again already providing it to the function as parameter
you do not build a string to output
Using Linq is probably somewhat overkill for your level of knowledge. This is a simpler Version of Oliver's answer - respecting order of letters as well:
public static void Main()
{
var input = "abbaaabcbcccd";
var max = 2;
// stores count of characters that we added already
var occurences = new Dictionary<char,int>();
var sb = new StringBuilder();
foreach (var c in input)
{
// add with a count of 0 if not yet encountered
if (!occurences.ContainsKey(c))
{
// if you want only "consecutive" letters not repeated over max:
// uncomment the following line that resets your dict - you might
// want to use a single integer and character instead in that case
// occurences.Clear(); // better use a single int-counter instead
occurences[c] = 0;
}
// add character if less then max occurences
if (occurences[c] < max)
{
sb.Append(c);
occurences[c]+=1;
}
}
Console.WriteLine(sb.ToString());
}
Output:
abbaccd

C# Local variable won't mutate in if statement

I am quite new the C# and I have googled the answer. The closest answer I have found was this one. But it doesn't help me.
I am trying to write a function that finds the biggest number in a string using loops and splicing only. For some reason, when the condition is met, the local variable big won't mutate in the if statements. I have tried to debug it by setting big = 34 when I hit a space, but even then it won't mutate the local variable.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace parser
{
class Sub_parser
{
// function to find the greatest number in a string
public int Greatest(string uinput)
{
int len = uinput.Length;
string con1 = "";
int big = 0;
int m = 0;
// looping through the string
for (int i = 0; i < len; i++)
{
// find all the numbers
if (char.IsDigit(uinput[i]))
{
con1 = con1 + uinput[i];
}
// if we hit a space then we check the number value
else if (uinput[i].Equals(" "))
{
if (con1 != "")
{
m = int.Parse(con1);
Console.WriteLine(m);
if (m > big)
{
big = m;
}
}
con1 = "";
}
}
return big;
}
public static void Main(string[] args)
{
while (true)
{
string u_input = Console.ReadLine();
Sub_parser sp = new Sub_parser();
Console.WriteLine(sp.Greatest(u_input));
}
}
}
}
The problem comes from your check in this statement :
else if (uinput[i].Equals(" "))
uinput[i] is a char, while " " is a string : see this example
if you replace the double quotes by single quotes, it works fine...
else if (uinput[i].Equals(' '))
And, as stated by the comments, the last number will never be checked, unless your input string ends by a space. This leaves you with two options :
recheck again the value of con1 after the loop (which is not very good-looking)
Rewrite your method because you're a bit overdoing things, don't reinvent the wheel. You can do something like (using System.Linq):
public int BiggestNumberInString(string input)
{
return input.Split(null).Max(x => int.Parse(x));
}
only if you are sure of your input
When you give a number and a space in the keyboard you only read the number, no space.
So you have uinput="34".
Inside the loop, you check if the m > big only if uinput[i].Equals(" "). Which is never.
In general if you read a line, with numbers followed by space, it would ignore the last number.
One solution would be to append a " " into uinput, but i recommend splicing.
string[] numbers = uinput.Split(null);
Then iterate over the array.
Also, as said in another answer compare uinput[i].Equals(' ') because " "represents a string, and you were comparing a char with a string.
As Martin Verjans mentioned, in order to make your code work you have to edit it like in his example.
Although there is still a Problem if you input a single number. The output would then be 0.
I would go for this Method:
public static int Greatest(string uinput)
{
List<int> numbers = new List<int>();
foreach(string str in uinput.Split(' '))
{
numbers.Add(int.Parse(str));
}
return numbers.Max();
}

C# - Dealing with contradictions in string.replace

I'm getting started with C# and programming in general and I've been playing with the "if" statements, arrays and generally getting to grips with things. However, one thing that has stumped me is how you would go about performing an replace operation which is inherently contradictory.
IE: I have string "AAABBB" but I want to search through my text and replace all "A"s with "B"s and vice-versa. So my intended output would be "BBBAAA".
I'm currently trying to use string.replace and if statements but it's not working (it follows the order of the statements, so in the above examples I'd get all "A" or all "B".
Code examples:
if (string.Contains("a"));
{
string = string.Replace("a", "b");
}
if (string.Contains("b"));
{
string = string.Replace("b", "a");
}
Any help would be super welcome!
If you're always replacing one character with another, it's probably simplest to convert it to a char[] and go through it one character at a time, fixing each one appropriately - rather than doing "all the As" and then "all the Bs".
public static string PerformReplacements(string text)
{
char[] chars = text.ToCharArray();
for (int i = 0; i < chars.Length; i++)
{
switch (chars[i])
{
case 'A':
chars[i] = 'B';
break;
case 'B':
chars[i] = 'A';
break;
}
}
return new string(chars);
}
Consider using Linq:
s = new string(s.Select(x => x == 'A' ? 'B' : x == 'B' ? 'A' : x).ToArray());
The reason why this fails is because all A's are first replaced by B's but then back to A's.
A generic way to solve this is the following:
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
public class Foo {
public static string ParallelReplace (string text, char[] fromc, char[] toc) {
Contract.Requires(text != null);
Contract.Requires(fromc != null);
Contract.Requires(toc != null)
Contract.Requires(fromc.Length == toc.Length);
Contract.Ensures(Contract.Result<string>().Length == text.Length);
Array.Sort(fromc,toc);
StringBuilder sb = new StringBuilder();
foreach(char c in text) {
int i = Array.BinarySearch(fromc,c);
if(i >= 0) {
sb.Append(toc[i]);
} else {
sb.Append(c);
}
}
return sb.ToString();
}
}
Demo with csharp interactive shell:
csharp> Foo.ParallelReplace("ABasdsadsadaABABB",new char[] {'b','a','s'},new char[] {'f','s','a'});
"ABsadasdasdsABABB"
This represents a mapping {b->f,a->s,s->a}. The method works in O(s*log(n)+n*log(n)), with s the length of the string and n the number of rules.
The Contract's are not necessary, but can help if one uses a static code analysis tool to prevent making errors.

Array searching in C# throwing error

I'm playing around with C# trying to get the basics down pat, but I'm stumped on this odd error. I'm trying to search through an array of names, and then return the location of any matches. At the moment I'm just printing the name, but I'm able to get the location.
For some reason though, when I type a name I want to search for and hit enter, the program crashes saying "ArraySearch.exe has stopped working". Any idea whats wrong??
Pardon the nooby questions, I'm still new to this language/paradigm :p
Thanks! :)
using System;
public class Test
{
public static void Main()
{
string[] stringArray = {"Nathan", "Bob", "Tom"};
bool [] matches = new bool[stringArray.Length];
string searchTerm = Console.ReadLine();
for(int i = 1;i<=stringArray.Length;i++){
if (searchTerm == stringArray[i - 1]){
matches[i] = true;
}
}
for(int i = 1;i<=stringArray.Length;i++){
if (matches[i]){
Console.WriteLine("We found another " + stringArray[i - 1]);
}
}
}
}
i will reach 3, which will give Out of range exception here:
matches[i] = true;
This would be easy to spot using debugging. (Try to learn using it. Very useful)
matches has 3 elements so the index ranges from 0 to 2.
using System;
public class Test
{
public static void Main()
{
string[] stringArray = { "Nathan", "Bob", "Tom" };
bool[] matches = new bool[stringArray.Length];
string searchTerm = "Bob";
for (int i = 1; i <= stringArray.Length; i++)
{
if (searchTerm == stringArray[i - 1])
{
matches[i - 1] = true;
}
}
for (int i = 1; i <= stringArray.Length; i++)
{
if (matches[i - 1])
{
Console.WriteLine("We found another " + stringArray[i - 1]);
}
}
}
}
See the fix above. It was failing because the bool array does not have an item at index 3. 3 is 1 index more than its last index, i.e. 2.
Its indexes are 0,1,2. which in turn store 3 elements just as u require.
When looping through arrays it is best to get used to using the actual indexes.
So i would suggest looping from 0 until array.length - 1. That way u have a direct reference to every element in the array.
I would strongly suggest using lambda's for this kind of processing.
Much fewer lines and simple to understand.
For example:
using System;
using System.Linq;
namespace ConsoleApplication5
{
public class Test
{
public static void Main()
{
string[] stringArray = { "Nathan", "Bob", "Tom" };
bool exists = stringArray.Any(x => x.Equals("Nathan"));
Console.WriteLine(exists);
}
}
}
The above method call, Any(), does the search for you. There are many options to explore. You can replace Any() with First(), Last() etc. If i were you I would definitely explore the System.Linq library for these kind of operations on collections.
(Remember that an array is also a collection)

Categories

Resources