From 3733333ba14f47d823c91e3a7ee57f6b6790c545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BE=D1=84=D1=8C=D1=8F?= <[sonya_pr@bk.ru]> Date: Wed, 2 Oct 2024 13:16:26 +0300 Subject: [PATCH] part1 --- .gitignore | 1 + part1/go.mod | 11 +++ part1/go.sum | 10 +++ part1/input.txt | 7 ++ part1/main.go | 48 ++++++++++ part1/output.txt | 4 + part1/reader/reader.go | 21 +++++ part1/reader/reader_test.go | 14 +++ part1/settings/settings.go | 154 ++++++++++++++++++++++++++++++++ part1/settings/settings_test.go | 27 ++++++ part1/uniq/uniq.go | 59 ++++++++++++ part1/uniq/uniq_test.go | 26 ++++++ part1/writer/writer.go | 17 ++++ 13 files changed, 399 insertions(+) create mode 100644 .gitignore create mode 100644 part1/go.mod create mode 100644 part1/go.sum create mode 100644 part1/input.txt create mode 100644 part1/main.go create mode 100644 part1/output.txt create mode 100644 part1/reader/reader.go create mode 100644 part1/reader/reader_test.go create mode 100644 part1/settings/settings.go create mode 100644 part1/settings/settings_test.go create mode 100644 part1/uniq/uniq.go create mode 100644 part1/uniq/uniq_test.go create mode 100644 part1/writer/writer.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/part1/go.mod b/part1/go.mod new file mode 100644 index 0000000..4d65564 --- /dev/null +++ b/part1/go.mod @@ -0,0 +1,11 @@ +module uniq + +go 1.22.0 + +require github.com/stretchr/testify v1.9.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/part1/go.sum b/part1/go.sum new file mode 100644 index 0000000..60ce688 --- /dev/null +++ b/part1/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/part1/input.txt b/part1/input.txt new file mode 100644 index 0000000..5d0a928 --- /dev/null +++ b/part1/input.txt @@ -0,0 +1,7 @@ +We love music. +I love music. +They love music. + +I love music of Kartik. +We love music of Kartik. +Thanks. \ No newline at end of file diff --git a/part1/main.go b/part1/main.go new file mode 100644 index 0000000..2e2518b --- /dev/null +++ b/part1/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "os" + "uniq/reader" + "uniq/settings" + "uniq/uniq" + "uniq/writer" +) + +func main() { + + var mas []string + var err error + + options, err := settings.GetOptions() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + in, err := settings.SetInput() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + out, err := settings.SetOutput() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + mas, err = reader.Reader(in) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + res := uniq.Uniq(mas, options) + + err = writer.Writer(res, out) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/part1/output.txt b/part1/output.txt new file mode 100644 index 0000000..a5fc042 --- /dev/null +++ b/part1/output.txt @@ -0,0 +1,4 @@ +We love music. + +I love music of Kartik. +Thanks. diff --git a/part1/reader/reader.go b/part1/reader/reader.go new file mode 100644 index 0000000..13f2adc --- /dev/null +++ b/part1/reader/reader.go @@ -0,0 +1,21 @@ +package reader + +import ( + "bufio" + "fmt" + "io" +) + +func Reader(input io.Reader) ([]string, error) { + in := bufio.NewScanner(input) + var masOfLines []string = make([]string, 0) + for in.Scan() { + txt := in.Text() + masOfLines = append(masOfLines, txt) + } + if len(masOfLines) == 0 { + return nil, fmt.Errorf("Empty stream") + } else { + return masOfLines, nil + } +} diff --git a/part1/reader/reader_test.go b/part1/reader/reader_test.go new file mode 100644 index 0000000..f784857 --- /dev/null +++ b/part1/reader/reader_test.go @@ -0,0 +1,14 @@ +package reader + +import ( + "bytes" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestFail(t *testing.T) { + var input = `` + in := bytes.NewBufferString(input) + _, err := Reader(in) + assert.NotNil(t, err) +} diff --git a/part1/settings/settings.go b/part1/settings/settings.go new file mode 100644 index 0000000..1314b32 --- /dev/null +++ b/part1/settings/settings.go @@ -0,0 +1,154 @@ +package settings + +import ( + "flag" + "fmt" + "io" + "os" + "strconv" + "strings" +) + +type Options struct { + WriteRepeatedLines bool + CountNumOfRepeats bool + OnlyUnicLines bool + Compare func(string, string, int, int) bool + IgnoreNumOfChars int + IgnoreNumOfFields int + IgnoreCase bool +} + +func SetInput() (io.Reader, error) { + if flag.Arg(0) != "" { + file, err := os.Open(flag.Arg(0)) + if err != nil { + return nil, err + } + return file, nil + } else { + return os.Stdin, nil + } +} +func SetOutput() (io.Writer, error) { + if flag.Arg(1) != "" { + file, err := os.OpenFile(flag.Arg(1), os.O_WRONLY|os.O_CREATE, 0777) + if err != nil { + return nil, err + } + return file, nil + } else { + return os.Stdout, nil + } +} +func checkRightUsage(options Options) error { + var booleans []bool = []bool{options.WriteRepeatedLines, options.CountNumOfRepeats, options.OnlyUnicLines} + count := 0 + for _, value := range booleans { + if value { + count++ + } + } + if count > 1 { + usagesMas := rightUsage(options, booleans) + var usages string + for i := range usagesMas { + usages += "\n" + usagesMas[i] + } + return fmt.Errorf("use only one option: -d or -c or -u \ntry one of this usages:" + usages) + } + return nil +} +func GetOptions() (Options, error) { + var flagD bool + var flagC bool + var flagU bool + var flagI bool + var flagF int + var flagS int + flag.BoolVar(&flagD, "d", false, "only repeat") + flag.BoolVar(&flagC, "c", false, "count") + flag.BoolVar(&flagU, "u", false, "not repeat") + flag.BoolVar(&flagI, "i", false, "ignore letter case") + flag.IntVar(&flagF, "f", 0, "ignore first n fields") + flag.IntVar(&flagS, "s", 0, "ignore first n chars") + flag.Parse() + options := Options{ + WriteRepeatedLines: flagD, + CountNumOfRepeats: flagC, + OnlyUnicLines: flagU, + IgnoreNumOfChars: flagS, + IgnoreNumOfFields: flagF, + IgnoreCase: flagI, + } + err := checkRightUsage(options) + if err != nil { + return Options{}, err + } + if flagI { + options.Compare = EqualWithoutCase + } else { + options.Compare = EqualWithCase + } + return options, nil +} +func rightUsage(options Options, booleans []bool) []string { + str := "go run main.go " + var flags []string = []string{"-d", "-c", "-u"} + var strings []string = make([]string, 0) + for i, value := range booleans { + if value { + strings = append(strings, str) + strings[i] += flags[i] + } + } + if options.IgnoreNumOfChars > 0 { + for i := range strings { + strings[i] = strings[i] + " -s " + strconv.Itoa(options.IgnoreNumOfChars) + } + } + if options.IgnoreNumOfFields > 0 { + for i := range strings { + strings[i] = strings[i] + " -f " + strconv.Itoa(options.IgnoreNumOfFields) + } + } + if options.IgnoreCase { + for i := range strings { + strings[i] = strings[i] + " -i " + } + } + if flag.Arg(0) != "" { + for i := range strings { + strings[i] = strings[i] + " " + flag.Arg(0) + } + } + if flag.Arg(1) != "" { + for i := range strings { + strings[i] = strings[i] + " " + flag.Arg(1) + } + } + return strings +} +func cutFieldsAndChars(str string, ignoredFields int, ignoredChars int) string { + words := strings.Fields(str) + if len(words) < ignoredFields { + return "" + } + newStr := strings.Join(words[ignoredFields:], " ") + if len(newStr) < ignoredChars { + return "" + } + return strings.Join(words[ignoredFields:], " ")[ignoredChars:] +} + +func EqualWithoutCase(str1 string, str2 string, ignoredFields int, ignoredChars int) bool { + newStr1 := cutFieldsAndChars(str1, ignoredFields, ignoredChars) + newStr2 := cutFieldsAndChars(str2, ignoredFields, ignoredChars) + return strings.ToLower(newStr1) == strings.ToLower(newStr2) +} + +func EqualWithCase(str1 string, str2 string, ignoredFields int, ignoredChars int) bool { + newStr1 := cutFieldsAndChars(str1, ignoredFields, ignoredChars) + newStr2 := cutFieldsAndChars(str2, ignoredFields, ignoredChars) + return newStr1 == newStr2 +} diff --git a/part1/settings/settings_test.go b/part1/settings/settings_test.go new file mode 100644 index 0000000..27ecca0 --- /dev/null +++ b/part1/settings/settings_test.go @@ -0,0 +1,27 @@ +package settings + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestCheckRightUsage(t *testing.T) { + assert.NotNil(t, checkRightUsage(Options{WriteRepeatedLines: true, CountNumOfRepeats: true, OnlyUnicLines: true, IgnoreNumOfChars: 3})) +} +func TestRightUsage(t *testing.T) { + assert.Equal(t, rightUsage(Options{WriteRepeatedLines: true, CountNumOfRepeats: true, OnlyUnicLines: true, IgnoreNumOfChars: 3}, []bool{true, true, true}), []string{"go run main.go -d -s 3", "go run main.go -c -s 3", "go run main.go -u -s 3"}, "must be equal") + assert.Equal(t, rightUsage(Options{WriteRepeatedLines: true, CountNumOfRepeats: true, IgnoreNumOfFields: 6}, []bool{true, true, false}), []string{"go run main.go -d -f 6", "go run main.go -c -f 6"}, "must be equal") +} +func TestCutFieldsAndChars(t *testing.T) { + assert.Equal(t, cutFieldsAndChars("", 1, 1), "", "must be equal") + assert.Equal(t, cutFieldsAndChars("a", 3, 0), "", "must be equal") + assert.Equal(t, cutFieldsAndChars("aaa", 0, 100), "", "must be equal") +} +func TestEqualWithoutCase(t *testing.T) { + assert.True(t, EqualWithoutCase("hello", "HelLo", 0, 0)) + assert.False(t, EqualWithoutCase("helo", "HelLo", 0, 0)) +} +func TestEqualWithCase(t *testing.T) { + assert.True(t, EqualWithCase("hello", "hello", 1, 0)) + assert.False(t, EqualWithCase("hello", "HelLo", 0, 0)) +} diff --git a/part1/uniq/uniq.go b/part1/uniq/uniq.go new file mode 100644 index 0000000..1c40396 --- /dev/null +++ b/part1/uniq/uniq.go @@ -0,0 +1,59 @@ +package uniq + +import ( + "strconv" + "uniq/settings" +) + +type lineInfo struct { + str string + count int +} + +func Uniq(masOfLines []string, options settings.Options) []string { + var lineInfoMas []lineInfo = make([]lineInfo, 0) + var ignoredChars int = options.IgnoreNumOfChars + var ignoredFields int = options.IgnoreNumOfFields + + for i := 0; i < len(masOfLines); i++ { + str := masOfLines[i] + if i == 0 { + lineInfoMas = append(lineInfoMas, lineInfo{str, 1}) + continue + } + prev := masOfLines[i-1] + if options.Compare(str, prev, ignoredFields, ignoredChars) { + lineInfoMas[len(lineInfoMas)-1].count += 1 + } else { + lineInfoMas = append(lineInfoMas, lineInfo{str, 1}) + } + } + + var resultMas []string = make([]string, 0) + + if options.WriteRepeatedLines { + for i := 0; i < len(lineInfoMas); i++ { + num := lineInfoMas[i].count + if num > 1 { + resultMas = append(resultMas, lineInfoMas[i].str) + } + } + } else if options.OnlyUnicLines { + for i := 0; i < len(lineInfoMas); i++ { + num := lineInfoMas[i].count + if num == 1 { + resultMas = append(resultMas, lineInfoMas[i].str) + } + } + } else if options.CountNumOfRepeats { + for i := 0; i < len(lineInfoMas); i++ { + num := lineInfoMas[i].count + resultMas = append(resultMas, strconv.Itoa(num)+" "+lineInfoMas[i].str) + } + } else { + for i := 0; i < len(lineInfoMas); i++ { + resultMas = append(resultMas, lineInfoMas[i].str) + } + } + return resultMas +} diff --git a/part1/uniq/uniq_test.go b/part1/uniq/uniq_test.go new file mode 100644 index 0000000..7135ae0 --- /dev/null +++ b/part1/uniq/uniq_test.go @@ -0,0 +1,26 @@ +package uniq + +import ( + "github.com/stretchr/testify/assert" + "testing" + "uniq/settings" +) + +func TestOK(t *testing.T) { + tests := []struct { + str []string + option settings.Options + expected []string // ожидаемый результат + }{ + {[]string{"I love music.", "I love music.", "I love music.", "", "I love music of Kartik.", "I love music of Kartik.", "Thanks.", "I love music of Kartik.", "I love music of Kartik."}, settings.Options{Compare: settings.EqualWithCase}, []string{"I love music.", "", "I love music of Kartik.", "Thanks.", "I love music of Kartik."}}, + {[]string{"I love music.", "I love music.", "I love music.", "", "I love music of Kartik.", "I love music of Kartik.", "Thanks.", "I love music of Kartik.", "I love music of Kartik."}, settings.Options{CountNumOfRepeats: true, Compare: settings.EqualWithCase}, []string{"3 I love music.", "1 ", "2 I love music of Kartik.", "1 Thanks.", "2 I love music of Kartik."}}, + {[]string{"I love music.", "I love music.", "I love music.", "", "I love music of Kartik.", "I love music of Kartik.", "Thanks.", "I love music of Kartik.", "I love music of Kartik."}, settings.Options{WriteRepeatedLines: true, Compare: settings.EqualWithCase}, []string{"I love music.", "I love music of Kartik.", "I love music of Kartik."}}, + {[]string{"I love music.", "I love music.", "I love music.", "", "I love music of Kartik.", "I love music of Kartik.", "Thanks.", "I love music of Kartik.", "I love music of Kartik."}, settings.Options{OnlyUnicLines: true, Compare: settings.EqualWithCase}, []string{"", "Thanks."}}, + {[]string{"I LOVE MUSIC.", "I love music.", "I LoVe MuSiC.", "", "I love MuSIC of Kartik.", "I love music of kartik.", "Thanks.", "I love music of kartik.", "I love MuSIC of Kartik."}, settings.Options{Compare: settings.EqualWithoutCase}, []string{"I LOVE MUSIC.", "", "I love MuSIC of Kartik.", "Thanks.", "I love music of kartik."}}, + {[]string{"We love music.", "I love music.", "They love music.", "", "I love music of Kartik.", "We love music of Kartik.", "Thanks."}, settings.Options{Compare: settings.EqualWithCase, IgnoreNumOfFields: 1}, []string{"We love music.", "", "I love music of Kartik.", "Thanks."}}, + {[]string{"I love music.", "A love music.", "C love music.", "", "I love music of Kartik.", "We love music of Kartik.", "Thanks."}, settings.Options{Compare: settings.EqualWithCase, IgnoreNumOfChars: 1}, []string{"I love music.", "", "I love music of Kartik.", "We love music of Kartik.", "Thanks."}}, + } + for i := range tests { + assert.Equal(t, Uniq(tests[i].str, tests[i].option), tests[i].expected, "must be equal") + } +} diff --git a/part1/writer/writer.go b/part1/writer/writer.go new file mode 100644 index 0000000..9e7bfa8 --- /dev/null +++ b/part1/writer/writer.go @@ -0,0 +1,17 @@ +package writer + +import ( + "bufio" + "io" +) + +func Writer(masOfLines []string, output io.Writer) error { + out := bufio.NewWriter(output) + for i := 0; i < len(masOfLines); i++ { + if _, err := out.WriteString(masOfLines[i] + "\n"); err != nil { + return err + } + } + err := out.Flush() + return err +}