Created
March 28, 2019 03:01
-
-
Save srfrog/e447819712a9da9bb6c691d766cb0fcc to your computer and use it in GitHub Desktop.
Go: flatten an array of arbitrarily nested arrays of integers into a flat array of integers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The MIT License (MIT) | |
// | |
// Copyright (c) 2019, @srfrog | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// The above copyright notice and this permission notice shall be included in all | |
// copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
// Package main | |
// | |
// This is an example of how to use Go to flatten an arbitrarily nested array of integers into | |
// a flat array of integers. e.g. [[1,2,[3]],4] -> [1,2,3,4] | |
// | |
// Issues: | |
// | |
// - We can't use Go arrays because they can't be nested. | |
// - Also Go does not support nested types per-se. | |
// | |
// Solution: | |
// | |
// Although Go has a static type system, all type representations are described internally by | |
// an interface dynamic type (IDT). The IDT can be assignable to any type including itself. | |
// We can use this property to mimic a nested type using slices. | |
// | |
// In other words, we must abuse the Go type system. | |
package main | |
import ( | |
"reflect" | |
"testing" | |
) | |
// flattenNested receives nested values and flattens them to a one-dimension | |
// int slice. If any value in nested is not an int, this function will return nil. | |
// | |
// The original problem did not specify what to do with invalid values (non-int), so we | |
// take the high road and end quickly. To return a partial slice of int values just | |
// remove the default case in the switch. | |
// | |
// nested is a slice of interface dynamic types. That includes any type representation | |
// but for this exercise we only need to accept two types: int and []interface{} | |
// NOTE: It would be trivial to support other types and/or convert values to int. | |
// | |
// Returns a slice of ints from flatted nested values, or nil. | |
func flattenNested(nested []interface{}) []int { | |
var out []int | |
for _, value := range nested { | |
switch value.(type) { | |
case int: | |
out = append(out, value.(int)) | |
case []interface{}: | |
values := flattenNested(value.([]interface{})) | |
// We must cascade nils otherwise we might end up with partial slice output. | |
// Because appending nil to a slice is a no-op. | |
if values == nil { | |
return nil | |
} | |
out = append(out, values...) | |
default: | |
// Type not accepted, invalidate result. | |
// NOTE: remove this case to return a partial slice output. | |
return nil | |
} | |
} | |
return out | |
} | |
// TestFlattenNested tests flattenNested for various nested inputs and their expected | |
// output. We must use reflect.DeepEqual to compare outputs because slices can't be | |
// compared. | |
func TestFlattenNested(t *testing.T) { | |
// Test cases | |
tests := []struct { | |
in []interface{} // input | |
out []int // expected output | |
}{ | |
{ | |
in: nil, | |
out: nil, | |
}, | |
{ | |
in: []interface{}{}, | |
out: nil, | |
}, | |
{ | |
in: []interface{}{"1", "2", "3"}, | |
out: nil, | |
}, | |
{ | |
in: []interface{}{1, "2", "3"}, | |
out: nil, | |
}, | |
{ | |
in: []interface{}{1, "2", 3}, | |
out: nil, | |
}, | |
{ | |
in: []interface{}{1, 2, 3, 4}, | |
out: []int{1, 2, 3, 4}, | |
}, | |
{ | |
in: []interface{}{1, 2, []interface{}{3, 4}}, | |
out: []int{1, 2, 3, 4}, | |
}, | |
{ | |
in: []interface{}{1, | |
[]interface{}{2, | |
[]interface{}{3, | |
[]interface{}{4}}}}, | |
out: []int{1, 2, 3, 4}, | |
}, | |
{ | |
in: []interface{}{1, 2, | |
[]interface{}{3, 4, | |
[]interface{}{5, 6}, []interface{}{7, 8}}, | |
[]interface{}{9, 10}}, | |
out: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, | |
}, | |
// Edge case: cascade nils | |
{ | |
in: []interface{}{1, []interface{}{}}, | |
out: nil, | |
}, | |
// Edge case: cascade nils | |
{ | |
in: []interface{}{1, []interface{}{[]interface{}{}}}, | |
out: nil, | |
}, | |
// Edge case: cascade nils | |
{ | |
in: []interface{}{1, []interface{}{[]interface{}{"2"}}}, | |
out: nil, | |
}, | |
} | |
for _, tc := range tests { | |
out := flattenNested(tc.in) | |
if !reflect.DeepEqual(out, tc.out) { | |
t.Errorf("Unexpected output: %v != %v", out, tc.out) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment