8

I have to write program that counts how many different letters are in string. For example "abc" will give 3; and "abcabc" will give 3 too, because there are only 3 different letters.

I need to use pascal, but if you can help with code in different languages it would be very nice too.

Here is my code that does not work:

var s:string;
    i,j,x,count:integer;
    c:char;
begin
  clrscr;

  Readln(s);
  c:=s[1];
  x:=1;

  Repeat
  For i:=1 to (length(s)) do
  begin
    If (c=s[i]) then
    begin
      delete(s,i,1);
      writeln(s);
    end;
  end;
  c:=s[1];
  x:=x+1;
  Until length(s)=1;

  Writeln(x);

x is the different letter counter; Maybe my algorythm is very bad.. any ideas? Thank you.

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
va.
  • 848
  • 3
  • 17
  • 39
  • 1
    I added the Delphi tag as well. This way, you will get *a lot* more attention to your question. And, after all, Delphi and Pascal is almost the same thing for simple algorithms like this. – Andreas Rejbrand Mar 05 '11 at 20:27
  • 3
    +1 for posting the code you're trying that didn't work. It looks like you should probably add the homework tag, too. – Ken White Mar 05 '11 at 20:36

9 Answers9

11

You've got answers on how to do it, here's why your way doesn't work.

First of all intuitively you had a good idea: Start with the first char in the string, count it (you forgot to include the counting code), remove all occurrences of the same char in the string. The idea is inefficient, but it would work. You ran into trouble with this bit of code:

For i:=1 to (length(s)) do
begin
  If (c=s[i]) then
  begin
    delete(s,i,1);
  end;
end;

The trouble is, Pascal will take the Length(s) value when it sets up the loop, but your code changes the length of the string by removing chars (using delete(s,i,1)). You'll end up looking at bad memory. The secondary issue is that i is going to advance, it doesn't matter if it matched and removed an char or not. Here's why that's bad.

Index:  12345
String: aabbb

You're going to test for i=1,2,3,4,5, looking for a. When i is 1 you'll find a match, remove the first char, and your string is going to look like this:

Index:  1234
String: abbb

You're now testing with i=2, and it's not a match, because s[2] =b. You just skiped one a, and that given a is going to stay in the array an other round and cause your algorithm to count it twice. The "fixed" algorithm would look like this:

i := 1;
while i <= Length(s) do
  if (c=s[i]) then
    Delete(s,i,1)
  else
    Inc(i);

This is different: In the given example, if I found a match at 1, the cursor doesn't advance, so it sees the second a. Also because I'm using a while loop, not a for loop, I can't get in trouble with possible implementation details of the for loop.

Your algorithm has an other problem. After the loop that removes all occurrences of the first char in string you're preparing the next loop using this code:

c:=s[1];

The trouble is, if you feed this algorithm an string of the form aa (length=2, two identical chars), it's going to enter the loop, delete or occurrences of a (those turning s into an EMPTY string) and then attempt to read the first char of the EMPTY string.

One final word: Your algorithm should handle the empty string on input, returning an count=0. Here's the fixed algorithm:

var s:string;
    i,count:integer;
    c:char;
begin
  Readln(s);
  count:=0;

  while Length(s) > 0 do
  begin
    Inc(Count);
    c := s[1];
    i := 1;
    while i <= Length(s) do
    begin
      If (c=s[i]) then
        delete(s,i,1)
      else
        Inc(i);
    end;
  end;

  Writeln(Count);

  Readln;
end.
Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
4

I am a Delphi expert, so I don't quite know how restrictive plain Pascal is. Nevertheless, this is Delphi:

// Returns the number of *distinct* "ANSI" characters in Str
function NumChrs(const Str: AnsiString): integer;
var
  counts: array[0..255] of boolean;
  i: Integer;
begin
  ZeroMemory(@counts[0], sizeof(boolean) * length(counts));
  for i := 1 to length(Str) do
    counts[ord(Str[i])] := true;
  result := 0;
  for i := 0 to high(counts) do
    if counts[i] then
      inc(result);
end;

The first line can be written

  for i := 0 to high(counts) do
    counts[i] := false;

if you cannot use the Windows API (or the Delphi FillChar function).

If you wish to have Unicode support (as in Delphi 2009+), you can do

// Returns the number of *distinct* Unicode characters in Str
function NumChrs(const Str: string): integer;
const
  AllocBy = 1024;
var
  FoundCodepoints: array of integer;
  i: Integer;

  procedure Push(Codepoint: integer);
  var
    i: Integer;
  begin
    for i := 0 to result - 1 do
      if FoundCodepoints[i] = Codepoint then
        Exit;
    if length(FoundCodepoints) = result then
      SetLength(FoundCodepoints, length(FoundCodepoints) + AllocBy);
    FoundCodepoints[result] := Codepoint;
    inc(result);
  end;  

begin

  result := 0;
  for i := 1 to length(Str) do
    Push(ord(Str[i]));

end;
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • +1 Wouldn't you be better with `array [AnsiChar] of Boolean`? Then you get get rid of the `ord`. – David Heffernan Mar 05 '11 at 20:29
  • 3
    +1 for `AnsiString`, saying `string` after adding the `Delphi` tag is dangerous. Since you need to loop over the entire array to count the results any way, use an `set of AnsiChar` in place of `array[0..255]`. Less memory, faster code. – Cosmin Prund Mar 05 '11 at 20:33
  • `set of AnsiChar` pretty good idea. Rather annoying to count. Would really love for there to be a built-in that counted set members. – David Heffernan Mar 05 '11 at 20:34
  • This is becoming golfing! @Cosmin: Feel free to write your own version in a new answer! That'll get you some points as well! – Andreas Rejbrand Mar 05 '11 at 20:37
  • @Andreas, writing an other answer to change just 3 lines of code seems silly. Living it as exercise for @user646263 is probably better. – Cosmin Prund Mar 05 '11 at 20:42
  • @Andreas I know this is picky, but those aren't code points. Surrogate pairs are the counter examples. – David Heffernan Mar 05 '11 at 21:09
  • +1 as NumChrs is exactly what I would have written. Definitely the optimal solution. – gabr Mar 06 '11 at 08:39
4

Here's my version. I'm not saying you'll get a great mark in your assignment if you hand this in.

function NumberOfUniqueChars(s: string): Integer;
var
  i, j: Integer;
  c: char;
begin
  for i := 1 to Length(s) do
    for j := i+1 to Length(s) do
      if s[i]<s[j] then
      begin
        c := s[i];
        s[i] := s[j];
        s[j] := c;
      end;
  Result := 0;
  for i := 1 to Length(s) do begin
    if (i=1) or (s[i]<>c) then
      inc(Result);
    c := s[i];
  end;
end;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • +1 for an interesting solution. Sorting first, then iterating and counting... Not sure that's what the teacher is looking for, but it might get points for originality. – Ken White Mar 05 '11 at 21:02
  • +1. This isn't a naïve solution. I'm not sure if there is any gain in speed, though. – Andreas Rejbrand Mar 05 '11 at 21:18
  • +1 I like the idea of sorting, especially useful if the string is Unicode. But I'd use an better sorting algorithm! – Cosmin Prund Mar 05 '11 at 21:20
  • My first (ANSI-only) function, my second (Unicode) function, your function: 28228, 128578, 1967587 (ticks). [These values are based on a naïve comparison, however!] – Andreas Rejbrand Mar 05 '11 at 21:22
  • 2
    @David: Just to add another dimension of originality, you should replace `c := s[i]; s[i] := s[j]; s[j] := c;` with `s[i] := Chr(Ord(s[i]) xor Ord(s[j])); s[j] := Chr(Ord(s[i]) xor Ord(s[j])); s[i] := Chr(Ord(s[i]) xor Ord(s[j]));` – Jørn E. Angeltveit Mar 05 '11 at 22:51
  • @Jørn Beautiful! You know, in all my years, I've never seen that trick before. Clearly I need to get out more! – David Heffernan Mar 05 '11 at 23:23
  • @David :) It's a pity that line breaks aren't supported in comments. – Jørn E. Angeltveit Mar 05 '11 at 23:29
3

And using a Delphi construct (not efficient, but clean)

function returncount(basestring: String): Integer;
var charstrings: TStringList;
    I:Integer;
begin
Result := 0;
charstrings := TStringlist.create;
try    
  charstrings.CaseSensitive := False;
  charstrings.Duplicates := DupIgnore;
  for I := 1 to length(basestring) do 
    charstrings.Add(basestring[i]);
  Result := charstrings.Count;
finally
  charstrings.free;
end;
end;
DamienG
  • 6,575
  • 27
  • 43
2

Different languages are ok?

RUBY:

s = "abcabc"
 => "abcabc"
m = s.split(//)
 => ["a", "b", "c", "a", "b", "c"]
p = m & m
 => ["a", "b", "c"]
p.count
 => 3
Kyle Macey
  • 8,074
  • 2
  • 38
  • 78
  • 1
    -1 This has no similarity (or even the possibility to easily translate) to Pascal. It's not helpful at all for this question. – Ken White Mar 05 '11 at 20:35
  • Took back the downvote, as I missed the OP saying different languages were OK. Still don't think it's helpful because it's not translatable to Pascal, though. – Ken White Mar 05 '11 at 20:38
  • I suppose you could write a TStringElements class that splits and then sort those elements, and then count them. It's not directly translateable, but the concepts of a dynamic language (ruby or python) can be expressed (at a cost of a lot more lines of code) in a static typed language. – Warren P Mar 06 '11 at 15:35
2

A Delphi version. Same idea as @The Communist Duck Python version.

function GetNumChars(Str: string): Integer;
var
    s: string;
    c: Char;
begin
    s := '';
    for c in Str do
    begin
        if Pos(c, s) = 0 then
        begin
            s := s + c;
        end;
    end;
    Result := Length(s);
end;
Mikael Eriksson
  • 136,425
  • 22
  • 210
  • 281
  • 1) `Str` parameter should be `const`. 2) The `begin` and `end` aren't necessary. 3) Builting up a string character-by-character is rather inefficient. 4) Are you allowed to use `Pos` if this is an exercise in writing an algorithm? Nevertheless, +1. – Andreas Rejbrand Mar 05 '11 at 21:29
  • @Andreas 1) Agree. 2) I like them :). 3) If you scan a book `s` will probably end up in about 50 to 60 characters. Not a big problem here but still it is a good point. 4) Hmmm. I don't know. Maybe he should write his own version of `Pos`. – Mikael Eriksson Mar 05 '11 at 21:45
2

Just tossing in a set-alternative...

program CountUniqueChars;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  InputStr: String;
  CountedChr: Set of Char;
  TotalCount: Integer;
  I: Integer;
begin
  Write('Text: ');
  ReadLn(InputStr);

  CountedChr := [];
  TotalCount := 0;

  for I := 1 to Length(InputStr) do
  begin
    Write('Checking: ' + InputStr[i]);
    if (InputStr[i] in CountedChr)
    then WriteLn('  --')
    else begin
      Include(CountedChr, InputStr[i]);
      Inc(TotalCount);
      WriteLn('  +1')
    end;
  end;


  WriteLn('Unique chars: ' + IntToStr(TotalCount));
  ReadLn;
end.
Jørn E. Angeltveit
  • 3,029
  • 3
  • 22
  • 53
1

In Python, with explanation if you want it for any other language: (Since you wanted different languages)

s = 'aahdhdfrhr' #s is the string
l = [] #l is an empty list of some kind.
for i in s: #Iterate through the string
    if i not in l: #If the list does not contain the character
        l.append(i) #Add the character to the list
print len(l) #Print the number of characters in the list
PrettyPrincessKitty FS
  • 6,117
  • 5
  • 36
  • 51
0
function CountChars(const S:AnsiString):Integer;
var C:AnsiChar; CS:Set of AnsiChar;
begin
  Result := 0;
  CS := [];
  for C in S do
    if not (C in CS) then
    begin
      CS :=  CS + [C];
      Inc(Result);
    end;
end;
Wouter van Nifterick
  • 23,603
  • 7
  • 78
  • 122