Skip to content

Commit

Permalink
Add Grep exercise
Browse files Browse the repository at this point in the history
  • Loading branch information
tofische committed May 23, 2024
1 parent 3587421 commit 71e9c3b
Show file tree
Hide file tree
Showing 11 changed files with 516 additions and 0 deletions.
11 changes: 11 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,17 @@
"topics": [
"strings"
]
},
{
"slug": "grep",
"name": "Grep",
"uuid": "4592877f-5b1e-4c8f-89aa-9e206de696eb",
"practices": [],
"prerequisites": [],
"difficulty": 5,
"topics": [
"strings"
]
}
],
"foregone": [
Expand Down
4 changes: 4 additions & 0 deletions exercises/practice/grep/.docs/instructions.append.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Hints

To simplify the exercise the flags are already given parsed and input files read,
therefore no IO operations and argument parsing need to be performed.
27 changes: 27 additions & 0 deletions exercises/practice/grep/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Instructions

Search files for lines matching a search string and return all matching lines.

The Unix [`grep`][grep] command searches files for lines that match a regular expression.
Your task is to implement a simplified `grep` command, which supports searching for fixed strings.

The `grep` command takes three arguments:

1. The string to search for.
2. Zero or more flags for customizing the command's behavior.
3. One or more files to search in.

It then reads the contents of the specified files (in the order specified), finds the lines that contain the search string, and finally returns those lines in the order in which they were found.
When searching in multiple files, each matching line is prepended by the file name and a colon (':').

## Flags

The `grep` command supports the following flags:

- `-n` Prepend the line number and a colon (':') to each line in the output, placing the number after the filename (if present).
- `-l` Output only the names of the files that contain at least one matching line.
- `-i` Match using a case-insensitive comparison.
- `-v` Invert the program -- collect all lines that fail to match.
- `-x` Search only for lines where the search string matches the entire line.

[grep]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html
23 changes: 23 additions & 0 deletions exercises/practice/grep/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"authors": [
"tofische"
],
"files": {
"solution": [
"src/Grep.hs",
"package.yaml"
],
"test": [
"test/Tests.hs"
],
"example": [
".meta/examples/success-standard/src/Grep.hs"
],
"invalidator": [
"stack.yaml"
]
},
"blurb": "Search a file for lines matching a regular expression pattern. Return the line number and contents of each matching line.",
"source": "Conversation with Nate Foster.",
"source_url": "https://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: grep

dependencies:
- base

library:
exposed-modules: Grep
source-dirs: src

tests:
test:
main: Tests.hs
source-dirs: test
dependencies:
- grep
- hspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Grep (grep, Flag(..)) where

import Data.Char (toLower)
import Data.List (isInfixOf)

data Flag = N | L | I | V | X deriving (Eq, Ord)

type Flags = [Flag]
type File = (String, [String])
type Files = [File]

grep :: String -> Flags -> Files -> [String]
grep pattern flags files = concatMap grepInFile files
where
flagN = N `elem` flags
flagL = L `elem` flags
flagI = I `elem` flags
flagV = V `elem` flags
flagX = X `elem` flags
pattern' = if flagI then map toLower pattern else pattern
multiple = length files > 1
grepInFile (fileName, content) = if flagL && not (null matchInFile) then [fileName] else matchInFile
where
matchInFile = concatMap grepInLine $ zip content [1..]
grepInLine :: (String, Int) -> [String]
grepInLine (line, lineNum) = [res | if flagV then not match else match]
where
line' = if flagI then map toLower line else line
match = if flagX then pattern' == line' else pattern' `isInfixOf` line'
res =
(if multiple then fileName <> ":" else "") <>
(if flagN then show lineNum <> ":" else "") <>
line
85 changes: 85 additions & 0 deletions exercises/practice/grep/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[9049fdfd-53a7-4480-a390-375203837d09]
description = "Test grepping a single file -> One file, one match, no flags"

[76519cce-98e3-46cd-b287-aac31b1d77d6]
description = "Test grepping a single file -> One file, one match, print line numbers flag"

[af0b6d3c-e0e8-475e-a112-c0fc10a1eb30]
description = "Test grepping a single file -> One file, one match, case-insensitive flag"

[ff7af839-d1b8-4856-a53e-99283579b672]
description = "Test grepping a single file -> One file, one match, print file names flag"

[8625238a-720c-4a16-81f2-924ec8e222cb]
description = "Test grepping a single file -> One file, one match, match entire lines flag"

[2a6266b3-a60f-475c-a5f5-f5008a717d3e]
description = "Test grepping a single file -> One file, one match, multiple flags"

[842222da-32e8-4646-89df-0d38220f77a1]
description = "Test grepping a single file -> One file, several matches, no flags"

[4d84f45f-a1d8-4c2e-a00e-0b292233828c]
description = "Test grepping a single file -> One file, several matches, print line numbers flag"

[0a483b66-315b-45f5-bc85-3ce353a22539]
description = "Test grepping a single file -> One file, several matches, match entire lines flag"

[3d2ca86a-edd7-494c-8938-8eeed1c61cfa]
description = "Test grepping a single file -> One file, several matches, case-insensitive flag"

[1f52001f-f224-4521-9456-11120cad4432]
description = "Test grepping a single file -> One file, several matches, inverted flag"

[7a6ede7f-7dd5-4364-8bf8-0697c53a09fe]
description = "Test grepping a single file -> One file, no matches, various flags"

[3d3dfc23-8f2a-4e34-abd6-7b7d140291dc]
description = "Test grepping a single file -> One file, one match, file flag takes precedence over line flag"

[87b21b24-b788-4d6e-a68b-7afe9ca141fe]
description = "Test grepping a single file -> One file, several matches, inverted and match entire lines flags"

[ba496a23-6149-41c6-a027-28064ed533e5]
description = "Test grepping multiples files at once -> Multiple files, one match, no flags"

[4539bd36-6daa-4bc3-8e45-051f69f5aa95]
description = "Test grepping multiples files at once -> Multiple files, several matches, no flags"

[9fb4cc67-78e2-4761-8e6b-a4b57aba1938]
description = "Test grepping multiples files at once -> Multiple files, several matches, print line numbers flag"

[aeee1ef3-93c7-4cd5-af10-876f8c9ccc73]
description = "Test grepping multiples files at once -> Multiple files, one match, print file names flag"

[d69f3606-7d15-4ddf-89ae-01df198e6b6c]
description = "Test grepping multiples files at once -> Multiple files, several matches, case-insensitive flag"

[82ef739d-6701-4086-b911-007d1a3deb21]
description = "Test grepping multiples files at once -> Multiple files, several matches, inverted flag"

[77b2eb07-2921-4ea0-8971-7636b44f5d29]
description = "Test grepping multiples files at once -> Multiple files, one match, match entire lines flag"

[e53a2842-55bb-4078-9bb5-04ac38929989]
description = "Test grepping multiples files at once -> Multiple files, one match, multiple flags"

[9c4f7f9a-a555-4e32-bb06-4b8f8869b2cb]
description = "Test grepping multiples files at once -> Multiple files, no matches, various flags"

[ba5a540d-bffd-481b-bd0c-d9a30f225e01]
description = "Test grepping multiples files at once -> Multiple files, several matches, file flag takes precedence over line number flag"

[ff406330-2f0b-4b17-9ee4-4b71c31dd6d2]
description = "Test grepping multiples files at once -> Multiple files, several matches, inverted and match entire lines flags"
21 changes: 21 additions & 0 deletions exercises/practice/grep/package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: grep
version: 1.0.0.0

dependencies:
- base

library:
exposed-modules: Grep
source-dirs: src
ghc-options: -Wall
# dependencies:
# - foo # List here the packages you
# - bar # want to use in your solution.

tests:
test:
main: Tests.hs
source-dirs: test
dependencies:
- grep
- hspec
10 changes: 10 additions & 0 deletions exercises/practice/grep/src/Grep.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Grep (grep, Flag(..)) where

data Flag = N | L | I | V | X deriving (Eq, Ord)

type Flags = [Flag]
type File = (String, [String])
type Files = [File]

grep :: String -> Flags -> Files -> [String]
grep pattern flags files = error "You need to implement this function."
1 change: 1 addition & 0 deletions exercises/practice/grep/stack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
resolver: lts-20.18
Loading

0 comments on commit 71e9c3b

Please sign in to comment.