Attempting to exit on key character press with an enter afterwards

Hello everyone,

I am attempting the new course Golang series on Coursera and I am a bit stumped on a problem set that asks to dynamically allocate space to a slice after it reaches its original limit size of 3. Where I am having issues is that if the users decides to press ‘X’ and then enter, it exits the program. Here is my code (note I have not implemented everything yet):

// Write a program which prompts the user to enter integers and stores the integers in a sorted slice.
// The program should be written as a loop. Before entering the loop, the program should create an empty
// integer slice of size (length) 3. During each pass through the loop, the program prompts the user to enter an
// integer to be added to the slice. The program adds the integer to the slice, sorts the slice, and prints
// the contents of the slice in sorted order. The slice must grow in size to accommodate any number of integers
// which the user decides to enter. The program should only quit (exiting the loop) when the user enters the
// character ‘X’ instead of an integer.

package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
)

func main() {
	userSlice := make([]int, 3) //Create a Slice of size 3

	// var intToAdd rune

	i := 0
	for i < len(userSlice) {
		fmt.Println("Welcome to Slices, Please enter a number to add to the slice:")
		// fmt.Scan(&intToAdd)
		inputReader := bufio.NewReader(os.Stdin)
		intToAdd, _, err := inputReader.ReadRune()
		if err != nil {
			log.Fatal(err)
		}
		if intToAdd == 'X' {
			fmt.Println("Thanks for using Slices!")
			break
		}
		userSlice[i] = int(intToAdd)
		fmt.Println("Slice so far:")
		for _, v := range userSlice {

			fmt.Println(v)
		}
		i++
	}

}

I don’t understand how you explain your problem because the program is supposed to exit when the user enters X, then Enter. That part is working.

Concerning the rest, I have these suggestions:

You can call bufio.NewReader() just once, before the for loop. Then keep using inputReader inside the loop. You do not have to create a new Reader every time you call one of its methods.

Since the directions did not require you to support Unicode, using ReadRune() probably was not the best choice. Try another method from the bufio package, such as ReadBytes() or ReadString(). It looks like you were trying to use fmt.Scan and almost got it to work, but you gave it the wrong type argument. Try

var intToAdd int
...
fmt.Scan(&intToAdd)

The way you are doing it now, you are converting a rune into an integer with int(), and getting the decimal value of the internal (binary) representation of a single rune. I think the directions want you to convert a string, like "34" into an int. Use the strconv package for that. https://golang.org/pkg/strconv/ (Just start reading from the top. You will see it.) Or just use fmt.Scan().

To reallocate the slice, all you need to do is use Go’s built-in append() function. Instead of

userSlice[i] = 34

use

userSlice = append(userSlice,34)

append() checks to see if the second argument will fit into userSlice, and if it won’t, it will reallocate the slice for you. You need to set userSlice to append()'s return value because if append() reallocates the slice, it returns a new slice, and you must use that slice from then on.

@jayts, thanks for responding. I originally tried an int, but when I did, I could no longer use ‘X’ and then enter to get the program to exit. I will try your advice and see if it works out. Thanks!

EDIT: I forgot to mention that append seems to dynamically re-allocate extra space in the slice. E.g. let’s say the original size of the slice was 3 and you add to the list by using append, then the list becomes a size of 4. That’s what I noticed using append. Thanks!

Hi

inputReader.ReadRune() will read the next rune and return its character code so if you write A will you get the rune 60. If you want to read numbers as strings and then convert them you could read from stdin with bufio.Scanner

bufio.NewScanner(os.stdin) - creates a new scanner
scanner.Scan() waits until where is more input (user pressed return)
scanner.Text() get the text entered

https://golang.org/pkg/strconv/ has functions to convert strings to numbers
https://golang.org/pkg/sort/ has funtions to sort slices of integers

and finally you can use append to expand a slice

Hope I pointed you in some correct directions and don’t hesitate to come back and ask again :slight_smile:

1 Like

@johandalabacka you did point me in the right direction. Thanks! I will keep trying. Thanks!

1 Like

@FistOfJustice Thanks for the extra explanation. I understand your problem now, but it looks like Johan already gave you a good answer. Let us know if you need more help.

1 Like

@jayts, thanks my friend, I surely will!

About how append() reallocates slices…

Be careful to understand the difference between the length of a slice and it’s capacity. Go has built-in functionslen() and cap() to get these two numbers. The length is how many things you have in the slice, and the capacity is how many spaces the slice has available (that is, how much space was allocated).

When append() reallocates, it doesn’t just add one more space and put the next thing into it. It allocates for a greater capacity, so the next time you append something, it won’t have to reallocate again. It will do that if you keep appending things, and each time it runs out of space and needs to reallocate, it will add more extra space than the previous time.

You can modify your program to watch the length and capacity of your slice as you continue to add integers to it. That might be somewhat enlightening. :wink:

1 Like

Thanks @jayts! I am running into some issues with my program. I have modified the code for now to test for the ‘X’ char and then to add the number to the slice if it is not ‘X’. I have not tried append yet to dynamically add more space (kind of like an ArrayList add method in Java). Please have a look at the code and the ouput:

// Write a program which prompts the user to enter integers and stores the integers in a sorted slice.
// The program should be written as a loop. Before entering the loop, the program should create an empty
// integer slice of size (length) 3. During each pass through the loop, the program prompts the user to enter an
// integer to be added to the slice. The program adds the integer to the slice, sorts the slice, and prints
// the contents of the slice in sorted order. The slice must grow in size to accommodate any number of integers
// which the user decides to enter. The program should only quit (exiting the loop) when the user enters the
// character ‘X’ instead of an integer.

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	userSlice := make([]int, 3) //Create a Slice of size 3

	inputReader := bufio.NewReader(os.Stdin)

	for i := 0; i < len(userSlice); i++ {

		fmt.Println("Welcome to Slices, Please enter a number to add to the slice:")

		checkForX, _ := inputReader.ReadByte()

		if checkForX == 'X' {
			fmt.Println("Thanks for using Slices!")
			os.Exit(0)
		} else {

			userSlice[i] = int(checkForX - '0')
			fmt.Println("Slice so far:", userSlice)
		}

	}

} 

Here is the Output, I am not even attempting to add another value and yet it does on its own in the slice. I wonder why:

$ go run slices.go
Welcome to Slices, Please enter a number to add tothe slice:9
Slice so far: [9 0 0]
Welcome to Slices, Please enter a number to add tothe slice:
Slice so far: [9 218 0]
Welcome to Slices, Please enter a number to add tothe slice:X
Thanks for using Slices!

EDIT: Notice how nothing was typed the second time and then the second slot was filled in the slice for some reason. It’s odd

ReadByte reads a byte a value between 0 and 256. Then you write a 9 and presses newline will stdio contain these two bytes 57 (which is ‘9’) and 10 (which is ‘\n’ or newline). The first read will get 57 which you check for X and then subtracts with 48 (which is ‘0’) and then it will read another byte which is 10 and you will check it agains X and then subtract 48. Because bytes maximum value is 255 will 10 - 48 be -38 but it will just become 256 - 38 which is 218.

package main

import (
	"fmt"
)

func main() {
	var b byte = 0
	fmt.Println(b - 38)
}

will print 218

This is a bit hard to explain but I think you should try another approach which is reading string check it for X and else try to convert the string to an integer. Your program can’t handle numbers like 87 which will but the 8 and 7 as separate numbers.

@johandalabacka I completely understand your explanation. I think I will try something else. Thanks!

1 Like

Good luck and keep up the good work :slight_smile:

LOL more like keep up the bad work.

Nah, learning new stuff is hard.

I haven’t used the Scanner type, but Johan might have a good idea by suggesting that. It’s in the bufio package, and you can read about it there.

If you want to use ReadByte() or ReadBytes() it may be more complicated. You will need to read the bytes into a slice, and then later convert that into a string, and then you can convert that to an integer with strconv.Atoi().

Here is some code that will do the operations I described. First, some declarations.

    var intToAdd int
    var byteSlice []byte
    var i int

Now, to read the input,

byteSlice, _ = reader.ReadBytes('\n')

reads bytes from the input into byteSlice, including the newline.

if len(byteSlice) == 2 && byteSlice[0] == 'X' { os.Exit(0) }

exits if the 1st byte is an ‘X’. (To keep the example simple, I’m assuming there was no error and the 2nd byte is a ‘\n’.)

s := string(byteSlice[:len(byteSlice)-1]) 

convert the slice into a string. (Notice that I subtract 1 from the length to ignore the newline at the end, which would confuse strconv.Atoi())

intToAdd, _ = strconv.Atoi(s)

Convert the string into an integer.

if(i < 3) { userSlice[i] = intToAdd; i++ } else { userSlice = append(userSlice,intToAdd) }

I didn’t use append() for the first three because append() adds to the end of an existing slice. When you used make() to create the slice, the 3 elements were initialized to zero.

I checked to make sure all that worked by writing a program that solves the problem you were given. So if you want to try this method, it should work as I described. Or at least you can look it over and maybe learn from it.

2 Likes

@jayts, thank you! I will give that a try and report back here. Cheers!

I want to thank everyone thus far for the assistance. Based on the suggestions from @jayts and @johandalabacka, I have come a long way. Thanks!

I am having issues with the sort.Ints() function though as it semi-sorts but sometimes removes an integer from the slice. It just seems a bit odd. Index 0 seems to always be 0 in the sorted slice here is what the output looks like:

Assessment1 $ go run slices.goWelcome to Slices, Please enter a number to add tothe slice:1Slice so far: [0 0 1]
Welcome to Slices, Please enter a number to add tothe slice:9
Slice so far: [0 1 9]
Welcome to Slices, Please enter a number to add tothe slice:10
Slice so far: [0 1 10]
Welcome to Slices, Please enter a number to add tothe slice:12
Slice so far: [0 1 10 12]
Welcome to Slices, Please enter a number to add tothe slice:15
Slice so far: [0 1 10 12 15]
Welcome to Slices, Please enter a number to add tothe slice:X 

As one can see, 9 was omitted from the slice. Here is the code:

    // Write a program which prompts the user to enter integers and stores the integers in a sorted slice.
    // The program should be written as a loop. Before entering the loop, the program should create an empty
    // integer slice of size (length) 3. During each pass through the loop, the program prompts the user to enter an
    // integer to be added to the slice. The program adds the integer to the slice, sorts the slice, and prints
    // the contents of the slice in sorted order. The slice must grow in size to accommodate any number of integers
    // which the user decides to enter. The program should only quit (exiting the loop) when the user enters the
    // character ‘X’ instead of an integer.

package main

import (
	"bufio"
	"fmt"
	"os"
	"sort"
	"strconv"
)

func main() {
	userSlice := make([]int, 3) //Create a Slice of size 3

	inputReader := bufio.NewReader(os.Stdin)
	var intToAdd int
	var byteSlice []byte

	i := 0
	for i < 100 {

		fmt.Println("Welcome to Slices, Please enter a number to add to the slice:")

		byteSlice, _ = inputReader.ReadBytes('\n')

		if len(byteSlice) == 2 && byteSlice[0] == 'X' {
			os.Exit(0)
		} else {
			s := string(byteSlice[:len(byteSlice)-1])
			intToAdd, _ = strconv.Atoi(s)
			if i < 3 {
				userSlice[i] = intToAdd

			} else {
				userSlice = append(userSlice, intToAdd)
			}
			sort.Ints(userSlice)
			fmt.Println("Slice so far:", userSlice)
		}
		i++
	}

}

You’re making great progress. The reason why a number disappers is because then the slice is sorted changes the order but you still inserts values at a specifik index

[0, 0, 0] you enter 9 at index 0 [9, 0, 0] and slice is sorted [0, 0, 9]
[0, 0, 9] you enter 6 at index 1 [0, 6, 9] and slice is sorted [0, 6, 9]
[0, 6, 9] you enter 4 at index 2 [0, 6, 4] and slice is sorted [0, 4, 6] and here does the 9 disappear

You cant sort the whole array until it is completly filled. then i < 3 must you sort a slice of userslice like sort.Ints(userslice[x:y]) there both or one of x and y can be specified

1 Like

You are really close to solving it! As Johan said, the problem is that after you sort the slice and i is still less than 3, you may overwrite an element of the slice the next time you put another integer into it.

Here is one thing you can try: Don’t ever sort userSlice. When you want to print it in sorted order, use make() to make a new slice with the same number of elements, then use Go’s built-in copy() function to make the new slice into a copy of userSlice. Then sort and print the new slice.

(If you want to be efficient, you can use this method when i < 3, and use the way you are doing it now when i >= 3.)

I got that to work, but I won’t show you the code because I don’t want to spoil the exercise for you. If you haven’t learned about copy() yet, you can read about Go’s built-in functions here:

https://golang.org/src/builtin/builtin.go

Look for func copy(dst, src []Type) int in that page, and read the comment just above it that explains how copy() works. This might be a good time to look at information about the other built-in functions, too!

1 Like

@jayts and @johandalabacka thanks once again for the tremendous help. I am a bit dissapointed in myself for asking for help. Thanks!