displaying characters and how many times they appear? - c#

I get a string from the user and then put it in a char array. Now I want to display all the characters in the string along with how many times they appear. My code is as follows Please Correct me ?
using System;
class count
{
public void charcount()
{
int i ;
int count = 0;
string s;
Console.WriteLine("Enter the String:");
s = Console.ReadLine();
char[] carr = s.ToCharArray();
for(i = 0; i < carr.Length; i++)
{
for(int j = 1; j < carr.Length; j++)
{
if(carr[j] == carr[i])
{
count++;
}
else
{
return;
}
Console.WriteLine("The Character " + carr[i] + " appears " + count);
}
}
}
static void Main()
{
count obj = new count();
obj.charcount();
}
}

Well, your code will at least have problems due to the fact that you don't build a list of unique characters, you find them in the original string. Any string with characters that appear multiple times will show odd results.
Here's a LINQ expression that calculates the information for you (you can run this in LINQPad to immediately see the results):
void Main()
{
string s = "This is a test, with multiple characters";
var statistics =
from c in s
group c by c into g
select new { g.Key, count = g.Count() };
var mostFrequestFirst =
from entry in statistics
orderby entry.count descending
select entry;
foreach (var entry in mostFrequestFirst)
{
Debug.WriteLine("{0}: {1}", entry.Key, entry.count);
}
}
Output:
: 6 <-- space
t: 5
i: 4
s: 4
h: 3
a: 3
e: 3
l: 2
c: 2
r: 2
T: 1
,: 1
w: 1
m: 1
u: 1
p: 1
If you can't use LINQ, here's an example that doesn't use that:
void Main()
{
string s = "This is a test, with multiple characters";
var occurances = new Dictionary<char, int>();
foreach (char c in s)
{
if (occurances.ContainsKey(c))
occurances[c] = occurances[c] + 1;
else
occurances[c] = 1;
}
foreach (var entry in occurances)
{
Debug.WriteLine("{0}: {1}", entry.Key, entry.Value);
}
}

It looks like you want an outer loop and an inner loop, and for each char in the array, you want to compare to each that follows with int j = 1. In that case, you want int j = i + 1 in the inner loop:
for (int i = 0; i < carr.Length; i++)
{
for (int j = i + 1; j < carr.Length; j++)
{
}
}
But your return statement exits the function right in the middle of things. You need to let the loops complete, so you want to remove that return.
Your Console.WriteLine executes on every iteration of the inner loop, but you really only want it to iterate on the outer loop -- once for each character of the string and not once for every combination of i and j. So you need to push that to the outer loop, outside of the inner loop.
Also, you need to reset the count every time you begin another iteration of the outer loop, because you are counting again, and, you want to start counting at 1 not zero when you find a character because it just appeared once as you first reach it.
And as Lasse points out, you'll get strange output when you hit the same character as you move along the outer loop. An easy way to prevent that is to set the further (rightwards) char[j] to '\0' (null character) on every match, and then in the outer loop, ignore null characters in your counting for example by using continue, effectively culling them as you go along:
for(int i = 0; i < carr.Length; i++)
{
if (carr[i] == '\0')
{
continue; // skip any further chars we've already set to null char
}
int count = 1;
for (int j = i + 1; j < carr.Length; j++)
{
if(carr[j] == carr[i])
{
carr[j] = '\0'; // don't look for this char again later
count++;
}
}
Console.WriteLine("The Character " + carr[i] + " appears " + count);
}
My first thought would be to use a Dictionary as Daniel suggests, like this:
var dict = new Dictionary<char, int>();
string s = "This is a test, with multiple characters";
foreach (var c in s)
{
if (dict.ContainsKey(c))
{
dict[c]++;
}
else
{
dict[c] = 1;
}
}
foreach (var k in dict.Keys)
{
Console.WriteLine("{0}: {1}", k, dict[k]);
}
But I like the elegant LINQ solutions.

If you would have tested your code, you would have realized, that the code is very wrong.
Some of its problems:
You have only one count variable, although you want to count the occurences of all characters in your string
You are comparing the string with itself and return from your method, as soon, as the characters don't match.
The right way would be to use a Dictionary<char, int> to hold the count for each character, something like this:
var counts = new Dictionary<char, int>();
foreach(var c in s)
{
int count;
if(!counts.TryGetValue(c, out count))
{
counts.Add(c, 1);
}
else
{
++counts[c];
}
}
I didn't use LINQ on purpose, because I don't think you would understand that code. That's no offence.

If you want to go the LINQ way, this is a fairly brief way to do it (which I realize is pretty much the same as Lasse V. Karlsen's answer, only using different syntax):
var s = Console.ReadLine();
foreach (var group in s.GroupBy(c => c).OrderByDescending(g => g.Count()))
{
Console.WriteLine(" {0}: {1}", group.Key, group.Count());
}
The logic is the same whatever approach you use:
Identify each unique character
Count how many times each character occurs in the text
Output the result
In my code sample, s.GroupBy(c => c) takes care of the first two steps. The call OrderByDescending(g => g.Count()) will just sort the result so that more frequent characters come first. Each element in the result has a Key property (the character) and (amongst others) a Count() method that will return the number of occurrences for that character.

Related

Find How many times an Anagram is contained in a String - asp.net c#

I need to find how many times the Anagrams are contained in a String like in this example:(the anagrams and the string itself)
Input 1(String) = thegodsanddogsweredogged
Input 2(String) = dog
Output(int) = 3
the output will be 3 because of these - (thegodsanddogsweredogged)
So far i managed to check how many times the word "dog" is contained in the string:
public ActionResult FindAnagram (string word1, string word2)
{
int ?count = Regex.Matches(word1, word2).Count;
return View(count);
}
This works to check how many times the word is contained but i still get an error: Cannot convert null to 'int' because it is a non-nulable value type.
So i need to check for how many times input 2 and the anagrams of input 2(dog,god,dgo,ogd etc) are contained in input 1?(int this case its 3 times-thegodsanddogsweredogged)
Thank you
I wanted to post a variation which is more readable at the cost of some runtime performance. Anton's answer is probably more performant, but IMHO less readable than it could be.
The nice thing about anagrams is that you know their exact length, and you can figure out all possible anagram locations quite easily. For a 3 letter anagram in a 100 letter haystack, you know that there are 98 possible locations:
0..2
1..3
2..4
...
96..98
97..99
These indexes can be generated quite easily:
var amountOfPossibleAnagramLocations = haystack.Length - needle.Length + 1;
var substringIndexes = Enumerable.Range(0, amountOfPossibleAnagramLocations);
At this point, you simply take every listed substring and test if it's an anagram.
var anagramLength = needle.Length;
int count = 0;
foreach(var index in substringIndexes)
{
var substring = haystack.Substring(index, anagramLength);
if(substring.IsAnagramOf(needle))
count++;
}
Note that a lot of this can be condensed into a single LINQ chain:
var amountOfPossibleAnagramLocations = haystack.Length - needle.Length + 1;
var anagramLength = needle.Length;
var anagramCount = Enumerable
.Range(0, amountOfPossibleAnagramLocations)
.Select(x => haystack.Substring(x, anagramLength))
.Count(substring => substring.IsAnagramOf(needle));
Whether it's more readable or not depends on how comfortable you are with LINQ. I personally prefer it (up to a reasonable size, of course).
To check for an anagram, simply sort the characters and check for equality. I used an extension method for the readability bonus:
public static bool IsAnagramOf(this string word1, string word2)
{
var word1Sorted = String.Concat(word1.OrderBy(c => c));
var word2Sorted = String.Concat(word2.OrderBy(c => c));
return word1Sorted == word2Sorted;
}
I've omitted things like case insensitivity or ignoring whitespace for the sake of brevity.
It would be better not to try to use Regex but write your own logic.
You can use a dictionary with key char - a letter of the word and value int - number of letter occurrences. And build such a dictionary for the word.
Anagrams will have similar dictionaries, so you can build a temp dictionary for each temp string built using the Windowing method over your str and compare it with the dictionary built for your word.
Here is my code:
using System;
using System.Collections.Generic;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var str = "thegodsanddogsweredogged";
var word = "dog";
Console.WriteLine("Word: " + word);
Console.WriteLine("Str: " + str);
Console.WriteLine();
var count = CountAnagrams(str, word);
Console.WriteLine("Count: " + count);
Console.ReadKey();
}
private static int CountAnagrams(string str, string word) {
var charDict = BuildCharDict(word);
int count = 0;
for (int i = 0; i < str.Length - word.Length + 1; i++) {
string tmp = "";
for (int j = i; j < str.Length; j++) {
tmp += str[j];
if (tmp.Length == word.Length)
break;
}
var tmpCharDict = BuildCharDict(tmp);
if (CharDictsEqual(charDict, tmpCharDict)) {
count++;
Console.WriteLine("Anagram: " + tmp);
Console.WriteLine("Index: " + i);
Console.WriteLine();
}
}
return count;
}
private static Dictionary<char, int> BuildCharDict(string word) {
var charDict = new Dictionary<char, int>();
foreach (var ch in word)
{
if (charDict.ContainsKey(ch))
{
charDict[ch] += 1;
}
else
{
charDict[ch] = 1;
}
}
return charDict;
}
private static bool CharDictsEqual(Dictionary<char, int> dict1, Dictionary<char, int> dict2)
{
if (dict1.Count != dict2.Count)
return false;
foreach (var kv in dict1) {
if (!dict2.TryGetValue(kv.Key, out var val) || val != kv.Value)
{
return false;
}
}
return true;
}
}
}
Possibly there is a better solution, but mine works.
P.S. About your error. You should change int? to int, because your View might expect non-nullable int type

Find the first distinct character in an array of characters and its index

Given an array of chars, I am looking for the best way to find the first distinct char and its index in the array. This code seems to do the job, but I am wondering if there is a better way to do it without so many loops. Thanks for your input!
static string firstDistinctChar(char[] myChars)
{
string result = "No Distinct Chars found!";
Dictionary<char, int> charDict = new Dictionary<char, int>();
for (int i = 0; i < myChars.Length; i++)
{//create dictionary of char and counts of char in array
if (charDict.TryGetValue(myChars[i], out int count))
{
charDict[myChars[i]] = count + 1;
}
else
{
charDict.Add(myChars[i], 1);
}
}
foreach (var item in charDict)
{
//remove all non distinct chars from dictionary
if (item.Value > 1) { charDict.Remove(item.Key); }
}
for (int i = 0; i < myChars.Length; i++)
{
//loop thru each char in array and return first matching char and index
if (charDict.TryGetValue(myChars[i], out _))
{
result = string.Format("The char: {0} is the first distinct char in the array with an index of : {1}", myChars[i], i);
return result;
}
}
return result;
}
Well it could be shorted a lot using linq.
void Main()
{
var z = firstDistinctChar("AABBCCddefg".ToCharArray());
Console.WriteLine(z);
}
static string firstDistinctChar(char[] myChars)
{
// Group chars in the array then take groups with count = 1 and extract only the first group
var group = myChars.GroupBy(c => c).Where(c => c.Count() == 1).FirstOrDefault();
if (group != null)
{
// From the first group extract the first (and only) value
char u = group.First();
// Now find the position of the distinct char
// creating a new array until we reach the distinct character
int i = myChars.TakeWhile(c => c != u).Count();
return string.Format("The char: {0} is the first distinct char in the array at index {1}", u,i);
}
else
return "No Distinct Chars found!";
}
This works because according to this answer
GroupBy - The IGrouping objects are yielded in an order based on the
order of the elements in source that produced the first key of each
IGrouping. Elements in a grouping are yielded in the order they appear
in source.
Is this better than your current solution? I am sure that is shorter but in terms of perfomance it should be tested with your actual data and without the proper comments is a little obscure
I am wondering if there is a better way to do it without so many loops.
The biggest changes I would make are
Remove the 2nd loop.
Use the last loop to check for a char count of 1, which is what allows the removal of the 2nd loop.
The other comments are just my 2 cents.
static string FirstDistinctChar(char[] myChars)
{
// You'll want to decide what to do if your parameter is null.
Dictionary<char, int> charDict = new Dictionary<char, int>();
// You're iterating thru every element, you don't need the index value,
// and you're not changing the collection, so use foreach instead of for.
// foreach is preferred for readability and maintainability.
foreach (char c in myChars)
{
if (charDict.TryGetValue(c, out int count))
{
charDict[c] = count + 1;
}
else
{
charDict.Add(c, 1);
}
}
// It's not necessary to remove non-distinct chars from charDict;
// just check for a value of 1. This allows the removal of a loop from your code. :-)
for (int i = 0; i < myChars.Length; ++i)
{
if (charDict[myChars[i]] == 1)
{
// Placeholders are difficult to read. I use string interpolation (c# 6 and up).
return $"The char {myChars[i]} is the first distinct char in the array with an index of {i}";
}
}
return "No distinct chars found.";
}

How to run-length encode 'EEDDDNE' to '2E3DNE'?

Explanation: The task itself is that we have 13 strings (stored in the sor[] array) like the one in the title or 'EEENKDDDDKKKNNKDK'
and we have to shorten it in a way that if there's two or more of the same letter next to eachother then we have to write it in the form of 'NumberoflettersLetter'
So by this rule, 'EEENKDDDDKKKNNKDK' would become '3ENK4D3K2NKDK'
using System;
public class Program
{
public static void Main(string[] args)
{
string[] sor = new string[] { "EEENKDDDDKKKNNKDK", "'EEDDDNE'" };
char holder;
int counter = 0;
string temporary;
int indexholder;
for (int i = 0; i < sor.Length; i++)
{
for (int q = 0; q < sor[i].Length; q++)
{
holder = sor[i][q];
indexholder = q;
counter = 0;
while (sor[i][q] == holder)
{
q++;
counter++;
}
if (counter > 1)
{
temporary = Convert.ToString(counter) + holder;
sor[i].Replace(sor[i].Substring(indexholder, q), temporary); // EX here
}
}
}
Console.ReadLine();
}
}
Sorry I didn't make the error clear, it says that :
"The value of index and length has to represent a place inside the string (System.ArgumentOutOfRangeException) - name of parameter: length"
...but I have no clue what's wrong with it, maybe it's a tiny little mistake, maybe the whole thing is messed up, so this is why I'd like someone to help me with this D:
(Ps 'indexholder' is there because i need it for another exercise)
EDIT:
'sor' is the string array that holds these strings (there are 13 of them) like the one mentioned in the title or in the example
You can use regex for this:
Regex.Replace("EEENKDDDDKKKNNKDK", #"(.)\1+", m => $"{m.Length}{m.Groups[1].Value}")
Explanation:
(.) matches any character and puts it in group #1
\1+ matches group #1 as many times can it can
Shortening the same string inplace is more difficult then construction a new one while iterating the old one char by char. If you plan to iteratively add to a string it is better to use the StringBuilder - class instead of adding directly to a string (performance reasons).
You can streamline your approach by using IEnumerable.Aggregate function wich does the iteration on one string for you automatically:
using System;
using System.Linq;
using System.Text;
public class Program
{
public static string RunLengthEncode(string s)
{
if (string.IsNullOrEmpty(s)) // avoid null ref ex and do simple case
return "";
// we need a "state" between the differenc chars of s that we store here:
char curr_c = s[0]; // our current char, we start with the 1st one
int count = 0; // our char counter, we start with 0 as it will be
// incremented as soon as it is processed by Aggregate
// ( and then incremented to 1)
var agg = s.Aggregate(new StringBuilder(), (acc, c) => // StringBuilder
// performs better for multiple string-"additions" then string itself
{
if (c == curr_c)
count++; // same char, increment
else
{
// other char
if (count > 1) // store count if > 1
acc.AppendFormat("{0}", count);
acc.Append(curr_c); // store char
curr_c = c; // set current char to new one
count = 1; // startcount now is 1
}
return acc;
});
// add last things
if (count > 1) // store count if > 1
agg.AppendFormat("{0}", count);
agg.Append(curr_c); // store char
return agg.ToString(); // return the "simple" string
}
Test with
public static void Main(string[] args)
{
Console.WriteLine(RunLengthEncode("'EEENKDDDDKKKNNKDK' "));
Console.ReadLine();
}
}
Output for "'EEENKDDDDKKKNNKDK' ":
'3ENK4D3K2NKDK'
Your approach without using the same string is more like this:
var data = "'EEENKDDDDKKKNNKDK' ";
char curr_c = '\x0'; // avoid unasssinged warning
int count = 0; // counter for the curr_c occurences in row
string result = string.Empty; // resulting string
foreach (var c in data) // process every character of data in order
{
if (c != curr_c) // new character found
{
if (count > 1) // more then 1, add count as string and the char
result += Convert.ToString(count) + curr_c;
else if (count > 0) // avoid initial `\x0` being put into string
result += curr_c;
curr_c = c; // remember new character
count = 1; // so far we found this one
}
else
count++; // not new, increment counter
}
// add the last counted char as well
if (count > 1)
result += Convert.ToString(count) + curr_c;
else
result += curr_c;
// output
Console.WriteLine(data + " ==> " + result);
Output:
'EEENKDDDDKKKNNKDK' ==> '3ENK4D3K2NKDK'
Instead of using the indexing operator [] on your string and have to struggle with indexes all over I use foreach c in "sometext" ... which will proceed char-wise through the string - much less hassle.
If you need to run-length encode an array/list (your sor) of strings, simply apply the code to each one (preferably by using foreach s in yourStringList ....

C# iterate a continuously growing multi dimensional array

Imagine I wanted to iterate from A to Z. We would use either Foreach or For loop. After attaining Z I would then like to iterate from AA to ZZ, so it starts at AA, then goes to AB, AC...AZ, BA, BC..BZ..ZA,ZB, ZZ. At which point we would move to three chars, then 4 etc up to an undefined point.
Because we don't have a defined length for the array we cannot use nested for loops... so
Question: How can this be done?
Note, No code has been given because we all know how to foreach over an array and nest foreach loops.
Here's some code that will do what you want. Full explanation follows but in summary it takes advantage of the fact that once you have done all the letters of a given length you do A followed by that entire sequence again then B followed by the entire sequence again, etc.
private IEnumerable<string> EnumerateLetters()
{
int count = 1;
while (true)
{
foreach(var letters in EnumerateLetters(count))
{
yield return letters;
}
count++;
}
}
private IEnumerable<string> EnumerateLetters(int count)
{
if (count==0)
{
yield return String.Empty;
}
else
{
char letter = 'A';
while(letter<='Z')
{
foreach(var letters in EnumerateLetters(count-1))
{
yield return letter+letters;
}
letter++;
}
}
}
There are two methods. The first is the one that you call and will generate an infinite sequence of letters. The second does the recursion magic.
The first is pretty simple. it has a count of how many letters we are on, calls the second method with that count and then enumerates through them returning them. Once it has done all for one size it increases the count and loops.
The second method is the one that does the magic. It takes in a count for the number of letters in the generated string. If the count is zero it returns an empty string and breaks.
If the count is more than one it will loop through the letters A to Z and for each letter it will append the sequence that it one shorter than it to the A. Then for the B and so on.
This will then keep going indefinitely.
The sequence will keep generating indefinitely. Because it uses recursion it would be theoretically possible to start stack overflowing if your letter string becomes too long but at one level of recursion per letter in the string you will need to be getting up to very long strings before you need to worry about that (and I suspect if you've gone that far in a loop that you'll run into other problems first).
The other key point (if you are not aware) is that yield return uses deferred execution so it will generate each new element in the sequence as it is needed so it will only generate as many items as you ask for. If you iterate through five times it will only generate A-E and won't have wasted any time thinking about what comes next.
Yet another generator (adding 1 to a number with radix == 26: A stands for 0, B for 1, ... Z for 25):
// please, notice, that Generator() can potentially spawn ifinitely many items
private static IEnumerable<String> Generator() {
char[] data = new char[] { 'A' }; // number to start with - "A"
while (true) {
yield return new string(data);
// trying to add one
for (int i = data.Length - 1; i >= 0; --i)
if (data[i] == 'Z')
data[i] = 'A';
else {
data[i] = (char) (data[i] + 1);
break;
}
// have we exhausted N-length numbers?
if (data.All(item => item == 'A'))
data = Enumerable
.Repeat('A', data.Length + 1) // ... continue with N + 1-length numbers
.ToArray();
}
}
Test
// take first 1000 items:
foreach (var item in Generator().Take(1000))
Console.WriteLine(item);
Outcome
A
B
C
..
X
Y
Z
AA
AB
..
AZ
BA
BB
BC
..
ZY
ZZ
AAA
AAB
AAC
..
ALK
ALL
You could do something like this, it gives me unending output of your pattern (sorry, not exact your pattern, but you understand how to do it)
public static IEnumerable<string> Produce()
{
string seed = "A";
int i = 0;
while (true)
{
yield return String.Join("", Enumerable.Repeat(seed, i));
if (seed == "Z")
{
seed = "A";
i++;
}
else
{
seed = ((char)(seed[0]+1)).ToString();
}
}
}
And than :
foreach (var s in Produce())
{
//Do something
}
EDIT I have desired output with this method :
public static IEnumerable<string> Produce()
{
int i = 1;
while (true)
{
foreach(var c in produceAmount(i))
{
yield return c;
}
i++;
}
}
private static IEnumerable<string> produceAmount(int i)
{
var firstRow = Enumerable.Range('A', 'Z' - 'A'+1).Select(x => ((char)x).ToString());
if (i >= 1)
{
var second = produceAmount(i - 1);
foreach (var c in firstRow)
{
foreach (var s in second)
{
yield return c + s;
}
}
}
else
{
yield return "";
}
}
The way to go is to use simple recursive approach. C# is a good language to present an idea with the use of generators:
private static IEnumerable<string> EnumerateLetters(int length) {
for (int i = 1; i <= length; i++) {
foreach (var letters in EnumerateLettersExact(i)) {
yield return letters;
}
}
}
private static IEnumerable<string> EnumerateLettersExact(int length) {
if (length == 0) {
yield return "";
}
else {
for (char c = 'A'; c <= 'Z'; ++c) {
foreach (var letters in EnumerateLettersExact(length - 1)) {
yield return c + letters;
}
}
}
}
private static void Main(string[] args) {
foreach (var letters in EnumerateLetters(2)) {
Console.Write($"{letters} ");
}
}
EnumerateLetters generates successive sequences of letters. The parameter decides up to which length would you like to request sequences.
EnumerateLettersExact takes care of generating sequences recursively. It can either be empty or is a concatenation of some letter with all sequences of shorter length.
Your're about to have an array from A to Z [A,...,Z].
Then your going to make multiple for loops
for example:
PSEUDOCODE
foreach(in array){
first = declare first variable (array)
foreach(in array{
second =declare 2nd variable (array)
return first + second
}
}
Try the following. This is a method to generate the appropriate string for a given number. You can write a for loop for however many number of iterations you want.
string SingleEntry(int number)
{
char[] array = " ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToArray();
Stack<string> entry = new Stack<string>();
List<string> list = new List<string>();
int bas = 26;
int remainder = number, index = 0;
do
{
if ((remainder % bas) == 0)
{
index = bas;
remainder--;
}
else
index = remainder % bas;
entry.Push(array[index].ToString());
remainder = remainder / bas;
}
while (remainder != 0);
string s = "";
while (entry.Count > 0)
{
s += entry.Pop();
}
return s;
}

How to get all permutations of groups in a string?

This is not homework, although it may seem like it. I've been browsing through the UK Computing Olympiad's website and found this problem (Question 1): here. I was baffled by it, and I'd want to see what you guys thought of how to do it. I can't think of any neat ways to get everything into groups (checking whether it's a palindrome after that is simple enough, i.e. originalString == new String(groupedString.Reverse.SelectMany(c => c).ToArray), assuming it is a char array).
Any ideas? Thanks!
Text for those at work:
A palindrome is a word that shows the same sequence of letters when
reversed. If a word can have its letters grouped together in two or
more blocks (each containing one or more adjacent letters) then it is
a block palindrome if reversing the order of those blocks results in
the same sequence of blocks.
For example, using brackets to indicate blocks, the following are
block palindromes:
• BONBON can be grouped together as (BON)(BON);
• ONION can be grouped together as (ON)(I)(ON);
• BBACBB can be grouped together as (B)(BACB)(B) or (BB)(AC)(BB) or
(B)(B)(AC)(B)(B)
Note that (BB)(AC)(B)(B) is not valid as the reverse (B)(B)(AC)(BB)
shows the blocks in a different order.
And the question is essentially how to generate all of those groups, to then check whether they are palindromes!
And the question is essentially how to generate all of those groups, to then check whether they are palindromes!
I note that this is not necessarily the best strategy. Generating all the groups first and then checking to see if they are palidromes is considerably more inefficient than generating only those groups which are palindromes.
But in the spirit of answering the question asked, let's solve the problem recursively. I will just generate all the groups; checking whether a set of groups is a palindrome is left as an exercise. I am also going to ignore the requirement that a set of groups contains at least two elements; that is easily checked.
The way to solve this problem elegantly is to reason recursively. As with all recursive solutions, we begin with a trivial base case:
How many groupings are there of the empty string? There is only the empty grouping; that is, the grouping with no elements in it.
Now we assume that we have a solution to a smaller problem, and ask "if we had a solution to a smaller problem, how could we use that solution to solve a larger problem?"
OK, suppose we have a larger problem. We have a string with 6 characters in it and we wish to produce all the groupings. Moreover, the groupings are symmetrical; the first group is the same size as the last group. By assumption we know how to solve the problem for any smaller string.
We solve the problem as follows. Suppose the string is ABCDEF. We peel off A and F from both ends, we solve the problem for BCDE, which remember we know how to do by assumption, and now we prepend A and append F to each of those solutions.
The solutions for BCDE are (B)(C)(D)(E), (B)(CD)(E), (BC)(DE), (BCDE). Again, we assume as our inductive hypothesis that we have the solution to the smaller problem. We then combine those with A and F to produce the solutions for ABCDEF: (A)(B)(C)(D)(E)(F), (A)(B)(CD)(E)(F), (A)(BC)(DE)(F) and (A)(BCDE)(F).
We've made good progress. Are we done? No. Next we peel off AB and EF, and recursively solve the problem for CD. I won't labour how that is done. Are we done? No. We peel off ABC and DEF and recursively solve the problem for the empty string in the middle. Are we done? No. (ABCDEF) is also a solution. Now we're done.
I hope that sketch motivates the solution, which is now straightforward. We begin with a helper function:
public static IEnumerable<T> AffixSequence<T>(T first, IEnumerable<T> body, T last)
{
yield return first;
foreach (T item in body)
yield return item;
yield return last;
}
That should be easy to understand. Now we do the real work:
public static IEnumerable<IEnumerable<string>> GenerateBlocks(string s)
{
// The base case is trivial: the blocks of the empty string
// is the empty set of blocks.
if (s.Length == 0)
{
yield return new string[0];
yield break;
}
// Generate all the sequences for the middle;
// combine them with all possible prefixes and suffixes.
for (int i = 1; s.Length >= 2 * i; ++i)
{
string prefix = s.Substring(0, i);
string suffix = s.Substring(s.Length - i, i);
string middle = s.Substring(i, s.Length - 2 * i);
foreach (var body in GenerateBlocks(middle))
yield return AffixSequence(prefix, body, suffix);
}
// Finally, the set of blocks that contains only this string
// is a solution.
yield return new[] { s };
}
Let's test it.
foreach (var blocks in GenerateBlocks("ABCDEF"))
Console.WriteLine($"({string.Join(")(", blocks)})");
The output is
(A)(B)(C)(D)(E)(F)
(A)(B)(CD)(E)(F)
(A)(BC)(DE)(F)
(A)(BCDE)(F)
(AB)(C)(D)(EF)
(AB)(CD)(EF)
(ABC)(DEF)
(ABCDEF)
So there you go.
You could now check to see whether each grouping is a palindrome, but why? The algorithm presented above can be easily modified to eliminate all non-palindromes by simply not recursing if the prefix and suffix are unequal:
if (prefix != suffix) continue;
The algorithm now enumerates only block palindromes. Let's test it:
foreach (var blocks in GenerateBlocks("BBACBB"))
Console.WriteLine($"({string.Join(")(", blocks)})");
The output is below; again, note that I am not filtering out the "entire string" block but doing so is straightforward.
(B)(B)(AC)(B)(B)
(B)(BACB)(B)
(BB)(AC)(BB)
(BBACBB)
If this subject interests you, consider reading my series of articles on using this same technique to generate every possible tree topology and every possible string in a language. It starts here:
http://blogs.msdn.com/b/ericlippert/archive/2010/04/19/every-binary-tree-there-is.aspx
This should work:
public List<string> BlockPalin(string s) {
var list = new List<string>();
for (int i = 1; i <= s.Length / 2; i++) {
int backInx = s.Length - i;
if (s.Substring(0, i) == s.Substring(backInx, i)) {
var result = string.Format("({0})", s.Substring(0, i));
result += "|" + result;
var rest = s.Substring(i, backInx - i);
if (rest == string.Empty) {
list.Add(result.Replace("|", rest));
return list;
}
else if (rest.Length == 1) {
list.Add(result.Replace("|", string.Format("({0})", rest)));
return list;
}
else {
list.Add(result.Replace("|", string.Format("({0})", rest)));
var recursiveList = BlockPalin(rest);
if (recursiveList.Count > 0) {
foreach (var recursiveResult in recursiveList) {
list.Add(result.Replace("|", recursiveResult));
}
}
else {
//EDIT: Thx to #juharr this list.Add is not needed...
// list.Add(result.Replace("|",string.Format("({0})",rest)));
return list;
}
}
}
}
return list;
}
And call it like this (EDIT: Again thx to #juharr, the distinct is not needed):
var x = BlockPalin("BONBON");//.Distinct().ToList();
var y = BlockPalin("ONION");//.Distinct().ToList();
var z = BlockPalin("BBACBB");//.Distinct().ToList();
The result:
x contains 1 element: (BON)(BON)
y contains 1 element: (ON)(I)(ON)
z contains 3 elements: (B)(BACB)(B),(B)(B)(AC)(B)(B) and (BB)(AC)(BB)
Although not so elegant as the one provided by #Eric Lippert, one might find interesting the following iterative string allocation free solution:
struct Range
{
public int Start, End;
public int Length { get { return End - Start; } }
public Range(int start, int length) { Start = start; End = start + length; }
}
static IEnumerable<Range[]> GetPalindromeBlocks(string input)
{
int maxLength = input.Length / 2;
var ranges = new Range[maxLength];
int count = 0;
for (var range = new Range(0, 1); ; range.End++)
{
if (range.End <= maxLength)
{
if (!IsPalindromeBlock(input, range)) continue;
ranges[count++] = range;
range.Start = range.End;
}
else
{
if (count == 0) break;
yield return GenerateResult(input, ranges, count);
range = ranges[--count];
}
}
}
static bool IsPalindromeBlock(string input, Range range)
{
return string.Compare(input, range.Start, input, input.Length - range.End, range.Length) == 0;
}
static Range[] GenerateResult(string input, Range[] ranges, int count)
{
var last = ranges[count - 1];
int midLength = input.Length - 2 * last.End;
var result = new Range[2 * count + (midLength > 0 ? 1 : 0)];
for (int i = 0; i < count; i++)
{
var range = result[i] = ranges[i];
result[result.Length - 1 - i] = new Range(input.Length - range.End, range.Length);
}
if (midLength > 0)
result[count] = new Range(last.End, midLength);
return result;
}
Test:
foreach (var input in new [] { "BONBON", "ONION", "BBACBB" })
{
Console.WriteLine(input);
var blocks = GetPalindromeBlocks(input);
foreach (var blockList in blocks)
Console.WriteLine(string.Concat(blockList.Select(range => "(" + input.Substring(range.Start, range.Length) + ")")));
}
Removing the line if (!IsPalindromeBlock(input, range)) continue; will produce the answer to the OP question.
It's not clear if you want all possible groupings, or just a possible grouping. This is one way, off the top-of-my-head, that you might get a grouping:
public static IEnumerable<string> GetBlocks(string testString)
{
if (testString.Length == 0)
{
yield break;
}
int mid = testString.Length / 2;
int i = 0;
while (i < mid)
{
if (testString.Take(i + 1).SequenceEqual(testString.Skip(testString.Length - (i + 1))))
{
yield return new String(testString.Take(i+1).ToArray());
break;
}
i++;
}
if (i == mid)
{
yield return testString;
}
else
{
foreach (var block in GetBlocks(new String(testString.Skip(i + 1).Take(testString.Length - (i + 1) * 2).ToArray())))
{
yield return block;
}
}
}
If you give it bonbon, it'll return bon. If you give it onion it'll give you back on, i. If you give it bbacbb, it'll give you b,b,ac.
Here's my solution (didn't have VS so I did it using java):
int matches = 0;
public void findMatch(String pal) {
String st1 = "", st2 = "";
int l = pal.length() - 1;
for (int i = 0; i < (pal.length())/2 ; i ++ ) {
st1 = st1 + pal.charAt(i);
st2 = pal.charAt(l) + st2;
if (st1.equals(st2)) {
matches++;
// DO THE SAME THING FOR THE MATCH
findMatch(st1);
}
l--;
}
}
The logic is pretty simple. I made two array of characters and compare them to find a match in each step. The key is you need to check the same thing for each match too.
findMatch("bonbon"); // 1
findMatch("bbacbb"); // 3
What about something like this for BONBON...
string bonBon = "BONBON";
First check character count for even or odd.
bool isEven = bonBon.Length % 2 == 0;
Now, if it is even, split the string in half.
if (isEven)
{
int halfInd = bonBon.Length / 2;
string firstHalf = bonBon.Substring(0, halfInd );
string secondHalf = bonBon.Substring(halfInd);
}
Now, if it is odd, split the string into 3 string.
else
{
int halfInd = (bonBon.Length - 1) / 2;
string firstHalf = bonBon.Substring(0, halfInd);
string middle = bonBon.Substring(halfInd, bonBon.Length - halfInd);
string secondHalf = bonBon.Substring(firstHalf.Length + middle.length);
}
May not be exactly correct, but it's a start....
Still have to add checking if it is actually a palindrome...
Good luck!!

Categories