Help with question 6-6 from Kochan - Programming in Obj - C book

Discussion in 'Mac Programming' started by theotherbeck, Nov 15, 2008.

  1. theotherbeck macrumors newbie

    Joined:
    Nov 15, 2008
    Location:
    New York, NY
    #1
    Hi all,

    I'm new to programming and going through Stephen Kochan's book, Programming in Objective - C. I have a question about exercise #6 in chapter 6, for anyone who is familiar with the book. Mr. Kochan said that that exercise is hard, and for me it is... I haven't been able to figure it out yet. Below is what I have so far, and what I have prints out the number entered at the terminal in english, like it is supposed to. But it does it in reverse order. It seems to me that there should be able to be some way to initially reverse the entered number, and then run the rest of the program I have, but I can't figure out how. Help? Thanks!


    // Program to print the numbers entered in english

    #import <stdio.h>
    #import <objc/Object.h>

    int main (int argc, char *argv[])
    {
    int number, right_digit;

    printf ("Enter your number.\n");
    scanf ("%i", &number);

    //seems like I should reverse the entered number here, but I can't figure out how.

    do { //gets the last number
    right_digit = number % 10;

    //prints the last number as a word
    if (right_digit == 0) {
    printf("zero");
    }
    else if (right_digit == 1){
    printf("one ");
    }
    else if (right_digit == 2){
    printf("two ");
    }
    else if (right_digit == 3){
    printf("three ");
    }
    else if (right_digit == 4){
    printf("four ");
    }
    else if (right_digit == 5){
    printf("five ");
    }
    else if (right_digit == 6){
    printf("six ");
    }
    else if (right_digit == 7){
    printf("seven ");
    }
    else if (right_digit == 8){
    printf("eight ");
    }
    else if (right_digit == 9){
    printf("nine ");
    }

    // lose last number from number entered
    number /= 10;
    } while (number != 0);




    return 0;
    }
     
  2. Cromulent macrumors 603

    Cromulent

    Joined:
    Oct 2, 2006
    Location:
    The Land of Hope and Glory
    #2
    Helps if you say what the question is so that those without the book can help too :).
     
  3. theotherbeck thread starter macrumors newbie

    Joined:
    Nov 15, 2008
    Location:
    New York, NY
    #3
    I guess it would, huh? :)

    The book says:
    Write a program that takes an integer keyed in from the terminal and extracts and displays each digit of the integer in English. So, if the user types in 932, the program should display the following:
    nine three two
    (remember to display 'zero' if the user types in just a 0.) Note: this exercise is a hard one!

    Thanks!
     
  4. mysterytramp macrumors 65816

    mysterytramp

    Joined:
    Jul 17, 2008
    Location:
    Maryland
    #4
  5. theotherbeck thread starter macrumors newbie

    Joined:
    Nov 15, 2008
    Location:
    New York, NY
    #5
    Great, thanks mt. See my post there... those solutions don't quite seem to solve the problem (unless I am missing something... which is entirely possible :) ) As far as I can tell, all those solutions print the numbers in reverse order... the exercise being to print them in the order they appear.

    Beck
     
  6. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #6
    you have the characters, you don't have to read these into an int. You could easily loop from 0 to strlen(input)-1, and use a switch on input[count], or use input[count] - '0' as an index into an array of char* that has the strings from "zero" to "nine". Alternately, you could use the example from the other thread and have it store the value at each step, prepending each new value until you're done, then print at the end. This should be done with a dynamic string like NSString, though, and you might not be there yet.

    -Lee
     
  7. theotherbeck thread starter macrumors newbie

    Joined:
    Nov 15, 2008
    Location:
    New York, NY
    #7
    Hi Lee,

    Yeah, that NSString prepend thing sounds like what I am looking for, but yes... I'm not there yet. Also, your suggestion of:

    Is also a bit confusing to me... I'm not sure I'm there yet either. I'm guessing strlen(input)-1 has something to do with counting the string length, and then storing those characters in some kind of list, but the book hasn't gone over arrays yet either.

    Both seemingly good solutions, but I am still curious if there is a way to do it with only the things the book has gone over to this point... hmmm.

    Is there something where you can store numbers in a way that 1 & 2 = 12? It seems like if I could do that, then I could use modulus to store the number in reverse and then use the code I wrote...

    Thanks.
     
  8. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #8
    Fair enough, i figured.

    strlen(char *) returns the number of characters in a string up to (but not including) the null terminator. If you are reading things into a char *, you are using arrays already. A string in C is just an array of characters.

    Not having the book, i have no idea what things it has covered to this point. Let's see if we can think of a very simple way to do this.

    I don't understand what you're asking. Before the scanf interprets an integer from the string entered, the input is a series of characters, so if instead of:
    Code:
    scanf("%i",&number);
    you used:
    Code:
    char str[128];
    scanf("%s",&str);
    and the user entered 12, the array looks like:
    str[0] : '1'
    str[1] : '2'
    str[2] : '\0'

    So you then have access to each character/digit in the order they are entered. But... you said no arrays, so...

    A way, without dealing with character arrays, to achieve this would be something like this (I won't write the code, so you can practice):
    determine the maximum number of digits of an integer stored in a 32-bit int. (It can store +/- 2 to the 31st, basically)

    Perform a loop from this number minus one to 0, we'll call the loop control variable x.

    For each iteration of the loop, divide the original value entered by 10^x and store this value in a variable, we'll call it digit_temp (this is not a good name, i'm going blank).

    take digit_temp modulus 10. This will be a number 0 - 10.

    Using a switch, or simply using this value as an index to an array of char *, print the corresponding word.

    Sure. Go forth and code.

    -Lee

    P.S. I missed a step. If the number is less than 10^x, you don't need to print. This will eliminate a bunch of "zero"s printing if you enter a number with less than the maximum digits.
     
  9. mysterytramp macrumors 65816

    mysterytramp

    Joined:
    Jul 17, 2008
    Location:
    Maryland
    #9
    The last several examples all work in forward order. But they might all use arrays. ...

    At first, I thought this was a different tack than what Lee suggested, rereading it, my pseudocode and his might mesh fairly well. I'll try to get back to this this afternoon.

    Set count at the largest power of 10 the user can input ... 9?, 10? (too early, too little coffee, sorry)
    set flag to false // get to this in sec

    Subtract 10^count - user input

    if result is > 0 then
    set digit to input divided by 10^count // digit MUST be an int
    set flag to true
    Use switch to print name of digit
    loop:
    decrement count, and
    subtract digit*10^count from input // this should peel off the leftmost digit

    You need flag because if user inputs "1004" you need to say:

    one zero zero four

    so if flag is true and result < 0, program outputs zero. When flag is false, no output.

    mt
     
  10. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #10
    I wanted to point something out, as it may be a point of confusion if you try to implement what we are suggesting. Mysterytramp and I both suggested using 10 to a particular power using the carrot ^ as shorthand, because that's acceptable in most math contexts. That is not the exponentiation operator in C, and there is no such operator in C. This means you need to:
    Use the pow function
    Use an array of powers of 10 (my preferred method)
    Use a loop to generate a power of 10

    The last seems to be the only one that does not require you to stretch your wings, but it's also my least favorite.

    -Lee
     
  11. mysterytramp macrumors 65816

    mysterytramp

    Joined:
    Jul 17, 2008
    Location:
    Maryland
    #11
    I'm still kind of new to C so I slip into a mishmash of Pascal, Applescript and Hypertalk when I think up code off the top of my head.

    Here's basically what I was suggesting, and I think Lee was suggesting something similar:

    Code:
    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[]) {
    	 
    	int input, count, i = 1, digit = 1;
    	printf("Enter number: ");
    	scanf("%d", &input);
    	
    	if (input > 0) {
    	while (i<=input) {
    		i = i * 10; }
    	
    	// number is less than 10 ^ i power
    	
    	for (count = i; i = i/10 ; count--) {
    		digit = input/i;
    		switch (digit)
    			{
    			case 0 : printf("Zero "); break;
    			case 1 : printf("One "); break;
    			case 2 : printf("Two "); break;
    			case 3 : printf("Three "); break;
    			case 4 : printf("Four "); break;
    			case 5 : printf("Five "); break;
    			case 6 : printf("Six "); break;
    			case 7 : printf("Seven "); break;
    			case 8 : printf("Eight "); break;
    			case 9 : printf("Nine "); break;
    			default : printf("Unknown Integer ");  break;
    			}
    			
    		input = input-(digit*i);
     
    	   }
    	printf("\n");
    	}
    	else {
    		if (input == 0) printf("Zero\n");
    		}	
    	return 0;
    }
    I don't think this uses anything that hasn't been covered up to the exercise. The whole thing is wrapped in if/else because my code as is couldn't anticipate the user entering zero. It seems to do OK with multiples of 10.

    mt
     
  12. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #12
    I wanted the OP to figure it out, but figured I'd post various examples so all of the suggestions I mentioned. Mysterytramp already posted some code, so it shouldn't hurt at this point:

    Using no intermediate int, and uses an array for dealing with the raw characters entered, and using an array for the strings to be printed:
    Code:
    #include <stdio.h>
    #include <string.h>
    
    int main(int argc, char *argv[]) {
      int pos,len;
      char *nums[] = {"zero","one","two","three","four","five","six","seven","eight","nine"};
      char input[8192];
      printf ("Enter your number.\n");
      fgets(input,8192,stdin);
      len=strlen(input);
      for(pos = 0; pos < len; pos++) {
        if(input[pos] > '9' || input[pos] < '0') continue;
        printf("%s ",nums[input[pos] - '0']);
      }
      printf("\n");
    }
    
    Use an intermediate int, still uses arrays for powers of 10 and strings to print:
    Code:
    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
      int number,pos;
      int pows[] = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
      char *nums[] = {"zero","one","two","three","four","five","six","seven","eight","nine"};
      printf ("Enter your number.\n");
      scanf ("%i", &number);
      for(pos = 9; pos >= 0; pos--) {
         if(number < pows[pos] && pos != 0) continue;
         printf("%s ",nums[(number / pows[pos]) % 10]);
      }
      printf("\n");
    }
    
    "Simplified" further to use switch statements (which seem to have been covered?) instead of arrays. The switches are in functions because it looked nicer to me, not sure if functions have been covered yet, though.
    Code:
    #include <stdio.h>
    int get_pow_10(int);
    const char *get_digit_word(int digit);
    int main(int argc, char *argv[]) {
      int number,pos,pow10;
      printf ("Enter your number.\n");
      scanf ("%i", &number);
      for(pos = 9; pos >= 0; pos--) {
         pow10 = get_pow_10(pos);
         if(number < pow10 && pos != 0) continue;
         printf("%s ",get_digit_word((number / pow10) % 10));
      }
      printf("\n");
    }
    
    int get_pow_10(int exp) {
      switch(exp) {
        case(9): return 1000000000;
        case(8): return 100000000;
        case(7): return 10000000;
        case(6): return 1000000;
        case(5): return 100000;
        case(4): return 10000;
        case(3): return 1000;
        case(2): return 100;
        case(1): return 10;
        case(0): return 1;
        default: return -1;
      }
    }
    
    const char *get_digit_word(int digit) {
      switch(digit) {
        case(9): return "nine";
        case(8): return "eight";
        case(7): return "seven";
        case(6): return "six";
        case(5): return "five";
        case(4): return "four";
        case(3): return "three";
        case(2): return "two";
        case(1): return "one";
        case(0): return "zero";
        default: return "non-digit";
      }
    }
    
    If functions haven't been covered yet, this will do the switches in-line:
    Code:
    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
      int number,pos,pow10;
      const char *word;
      printf ("Enter your number.\n");
      scanf ("%i", &number);
      for(pos = 9; pos >= 0; pos--) {
        switch(pos) {
          case(9): pow10 = 1000000000;
            break;
          case(8): pow10 = 100000000;
            break;
          case(7): pow10 = 10000000;
            break;
          case(6): pow10 = 1000000;
            break;
          case(5): pow10 = 100000;
            break;
          case(4): pow10 = 10000;
            break;
          case(3): pow10 = 1000;
            break;
          case(2): pow10 = 100;
            break;
          case(1): pow10 = 10;
            break;
          case(0): pow10 = 1;
            break;
          default: return -1; //can't work with this
            break;
        }
        if(number < pow10 && pos != 0) continue;
        switch((number / pow10) % 10) {
          case(9): word="nine";
            break;
          case(8): word="eight";
            break;
          case(7): word="seven";
            break;
          case(6): word="six";
            break;
          case(5): word="five";
            break;
          case(4): word="four";
            break;
          case(3): word="three";
            break;
          case(2): word="two";
            break;
          case(1): word="one";
            break;
          case(0): word="zero";
            break;
          default: return -1; //This is no good
            break;
        }
        printf("%s ",word);
      }
      printf("\n");
    }
    
    So now you've been shown nearly half a dozen ways to solve the same, fairly simple problem. This goes to show that when it comes to programming there's rarely a definitive way to do anything. Of the examples above, there's a bit of a difference in the runtime, etc. but not human-noticeable. Primarily, I prefer arrays to other means of "generating" fixed values. The compiler will put these static values in an easy-to-find place, and at runtime a very simple calculation is done on the base address of the array and the index. This is much faster than doing a bunch of integer multiplications. Switch is a bit of a tradeoff. No calculations have to be done at runtime, but depending on your compiler, a bunch of if...then..else statements might be generated, and branchy code like this is slower than unconditional execution. Some compilers will turn a switch into a jump-table, which will essentially just be a lookup like an array access, which is better, but I still prefer to just use arrays when possible.

    Otherwise the majority of the time will be spent doing integer division and condition checking for the for loop and if structures. The first example has no division, only subtraction, which is a plus. The modulus operator is, in essence, a division, too.

    Some other things to take into account:
    none of my examples deal with a negative number being entered
    in all but the first example i posted, strange behavior will show up if you enter a value above 2^31 - 1.
    entering non-numeric characters will result in unexpected behavior, in the first example these characters are ignored, but the others depend on the behavior of scanf.

    Hopefully this is helpful now, and going forward as you start picking up some of the other things discussed.

    -Lee
     
  13. theotherbeck thread starter macrumors newbie

    Joined:
    Nov 15, 2008
    Location:
    New York, NY
    #13
    hi Lee,

    Thanks for your response... tried to make some sense of that stuff today... some of it is still over my head... but here is what I came up with. Despite not knowing anything about them, the array option that you mentioned seemed to make the most sense to me so I went with that (see code below). So now I have two questions.

    1) what the 128 in the str[128] what does the 128 mean? it didn't seem to make a difference if I changed it.

    2) as you can see below, I set the variable str_length to put a cap on the loop, but I am wondering how that might be automatically set by reading it from whatever the user enters. i.e. if I enetered 1234, my program would set str_length to 4. It seems like that might have something to do with the char* that you were talking about, but I so far have not been able to get my head around how or where to use char*.

    Thanks again. Here's the most recent code:

    Code:
    // Program to print the numbers entered in english
    
    #import <stdio.h>
    #import <objc/Object.h>
    
    int main (int argc, char *argv[])
    {
    	char	str[128], str_char;
    	int		strNum = 0, str_length;
    	
    	//want to replace this with something that automatically reads the length of the string entered.
    	str_length = 8;
    	
    	
    	printf ("Enter your number.\n");
    	scanf("%s",&str);
    	
    	do {		//gets the numbers from the string entered and stored in str[128], in order, up to the str_length limit set.
    		str_char = str[strNum];
    				
    				//prints the character as a word
    				if (str_char == 0) {
    					printf("zero");
    				}
    				else if (str_char == '1'){
    					printf("one ");
    					}
    				else if (str_char == '2'){
    					printf("two ");
    					}
    				else if (str_char == '3'){
    					printf("three ");
    					}
    				else if (str_char == '4'){
    					printf("four ");
    					}
    				else if (str_char == '5'){
    					printf("five ");
    					}
    				else if (str_char == '6'){
    					printf("six ");
    					}
    				else if (str_char == '7'){
    					printf("seven ");
    					}
    				else if (str_char == '8'){
    					printf("eight ");
    					}
    				else if (str_char == '9'){
    					printf("nine ");
    					}
    				strNum += 1;
    				
    	} while (strNum <= str_length);
    	
    		
    
    	
    	return 0;
    }
    
     
  14. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #14
    This is the length of the array. This means that you have an array of characters that is 128 elements long. This means you can store up to 128 characters in the memory allocated for str. In C, the "standard" way to deal with strings is a list of characters. We won't worry about the * for now, it deals with pointers, and generally people talk about strings in C they refer to them as char *s, because they are just pointers to an array of type char.

    You are using scanf to read a string, which is not really considered "safe". You're str[128] is great until a user enters a string that is 128 characters or more. scanf will gladly continue placing characters in memory well past the end of the space set aside for str. What this means is that you're now placing characters the user has entered in space that is not intended to for that purpose. It may be memory that "belongs" to you, like the bytes set aside for str_length, or it may be memory that doesn't belong to you, resulting in a crash. The likely error you'd get in this case would be a "segmentation fault", meaning you've read or written in a memory segment that does not belong to you.

    In my example i used another function to read from the keyboard/console/stdin called fgets. It accepts a parameter stating the maximum length to read, so you can be sure you don't overflow the buffer you pass in. In my example I used 8192. This is probably overkill, but i wanted to pick some arbitrarily large size and that's what i settled on. If a user tried to enter a string 8192 characters or longer, only the first 8191 plus the null terminator would get copied into the memory set aside for my variable input, and the rest would be left in stdin, waiting to be read. This makes sure that I don't use memory that doesn't belong to me, so I don't get erratic behavior or a crash if a user gets overzealous/falls asleep on the keyboard.

    In my first example I used the function you are looking for, strlen, to get this value based on the user's input. This function will count the number of characters in an area of memory until it reaches a null terminator (a byte with the value of 0). This can be tricky, because the valid indexes into a character array 'str' are from 0 to strlen(str)-1. The "- 1" is easy to miss, and can lead to "off-by-one" errors, and those are no good.

    Don't worry about char * yet, I'm sure the book will get there.

    So once you put a strlen call in there, another things that looks a bit off is that you never print a newline (the sequence \n will put a newline into your output) at the end of your output. This isn't a huge deal, but things will look nicer if you have it in there.
    Another small thing is that all of your outputs have spaces after them except for zero.

    Otherwise, while it's certainly correct, i prefer to keep braces on the same line as control structures, instead of having a closing brace, then on a new line the else or else if clause.

    Also, right now there's an off-by-one situation in the work with your while predicate. right now it's running from 0 to 8, but be sure to consider what it should run to once you get str_length from the strlen function. On a related note, is there a reason for the do..while instead of a for? Pretty much every loop control structure is equivalent, you just have to think about the correct predicate based on the one you've chosen. Normally when you are dealing with a list of things, a for "makes sense" for a fixed list of things you want to go through. A while/do-while is generally more sensible when you're not sure when something is going to end, i.e. if you're doing I/O in the loop and don't know when the user enters a certain string.

    -Lee

    P.S. Noticed another error. For your check if str_char is '0', you instead compare it to 0, so this will only print if you are evaluating the null terminator. Stick it in single quotes and you should be fine.
     
  15. theotherbeck thread starter macrumors newbie

    Joined:
    Nov 15, 2008
    Location:
    New York, NY
    #15
    Hey guys,

    Thanks for all the info... for some reason when I posted my last response, the last post I had seen was Lee's post #8... there was a lot said between then and my post #13, which I can't for the life of me figure out how I missed... (maybe didn't refresh the page when I came back to it and didn't get emails for some reason?) In any event, there is a bunch of great information between #8 and #13 from both of you... thanks! I will spend some time working through it, doing my best to learn it all. It all looks very, very helpful so far though. Mt's example seems like the way the book would want you to do it, using only things up to that point... but seeing it done in all those other ways will really help me learn how to use some new things that I haven't seen yet. Again, thank you very much for all your input!! It's very helpful.

    Beck
     

Share This Page