/* * Note: This program is written to demonstrate the ideas behind channels and synronization. * It isn't written to be efficient code in term of memory use (I use copy by value, not by reference) * or in terms of runtime (I use a waitgroup where reading from channels could provides synronization instead). * * Using slice.Sort in mysort() followed by a 4 way merge in main() is particularly ugly. * * Main() is split into 6 main steps: * Step 1: Get the list of ints from the user * Step 2: Split the list into four partitions of approximately equal size * Step 3: Pass each quater to a goroutine to sort * Step 4: Fetch results from channels * Step 5: Merge the results. N.B. I'm taking "merge the 4 sorted subarrays into one large sorted array." literally here. * Step 6: Print the output * * Some shuffled sequences to test this on: * 2 5 3 6 1 7 10 4 8 9 * 3 13 1 11 19 17 16 7 10 14 9 4 6 8 5 2 12 15 18 20 * 24 18 5 15 4 12 28 30 3 27 29 14 20 6 23 21 10 8 19 9 17 16 7 25 11 2 22 1 26 13 * */ package main import ( "fmt" "os" "bufio" "strings" "strconv" "sync" "slices" ) func mysort(wg *sync.WaitGroup, c chan []int, goroutineNum int, numbers []int) { defer wg.Done() fmt.Printf("mysort%d got: %v\n", goroutineNum, numbers) slices.Sort(numbers) // Using the sort in slices as this isn't an exercise in writing a sort() c <- numbers fmt.Printf("mysort%d returned: %v\n", goroutineNum, numbers) } func main() { numbers := make([]int, 0) // Step 1: Get the list of ints from the user scanner := bufio.NewScanner(os.Stdin) fmt.Printf("\nPlease enter a list of integers separated by spaces ( e.g. \"1 3 5 2\" ):\n") scanner.Scan() line := scanner.Text() fields := strings.Fields(line) for _, v := range(fields) { n, err := strconv.Atoi(v) if err != nil { fmt.Println("Ignoring string: " + v) continue } numbers = append(numbers, n) } // Step 2: Split the list into four partitions of approximately equal size quarter1 := numbers[:len(numbers)/4] quarter2 := numbers[len(numbers)/4 : len(numbers)/2] quarter3 := numbers[len(numbers)/2 : len(numbers)/4+len(numbers)/2] quarter4 := numbers[len(numbers)/4+len(numbers)/2:] // Step 3: Pass each quater to a goroutine to sort // N.B. the waitgroups are not needed for syncronization here as reading from unbuffered channels in Step 4 // would perform the same function. I'm using buffered channels just so the waitgroup is needed. // This is to demonstrate the idea of waitgroups. c1 := make(chan []int, 1) c2 := make(chan []int, 1) c3 := make(chan []int, 1) c4 := make(chan []int, 1) var wg sync.WaitGroup wg.Add(4) go mysort(&wg, c1, 1, quarter1) go mysort(&wg, c2, 2, quarter2) go mysort(&wg, c3, 3, quarter3) go mysort(&wg, c4, 4, quarter4) wg.Wait() // Step 4: Fetch results from channels nums1 := <- c1 nums2 := <- c2 nums3 := <- c3 nums4 := <- c4 // Step 5: Merge the results. N.B. I'm taking "merge the 4 sorted subarrays into one large sorted array." literally here. // It would be far easier, and more readable, to merge nums1 and nums2 into nums5, merge nums3 and nums4 into nums6, // and then merge nums5 and nums6 into the final output. sorted := []int{} lowest_list := 0 lowest_number := 0 for len(sorted) < len(numbers) { // Step 5a: Find which of nums1 to nums4 starts with the lowest number if len(nums1) > 0 { lowest_list = 1; lowest_number = nums1[0]; } if len(nums2) > 0 { if (len(nums1) == 0) || nums2[0] < lowest_number { lowest_list = 2 lowest_number = nums2[0] } } if len(nums3) > 0 { if (len(nums1) == 0 && len(nums2) == 0) || nums3[0] < lowest_number { lowest_list = 3 lowest_number = nums3[0] } } if len(nums4) > 0 { if (len(nums1) == 0 && len(nums2) == 0 && len(nums3) == 0) || nums4[0] < lowest_number { lowest_list = 4 lowest_number = nums4[0] } } // Step 5b: Update sorted with the lowest found and offset the []int it was found in sorted = append(sorted, lowest_number) switch lowest_list { case 1: nums1 = nums1[1:] case 2: nums2 = nums2[1:] case 3: nums3 = nums3[1:] case 4: nums4 = nums4[1:] } } // Step 6: Print the output fmt.Println("Final merged output:\n") fmt.Println(sorted) }