Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(reverse-string): add reverse-string practice exercise #21

Merged
merged 1 commit into from
May 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@
"practices": [],
"prerequisites": [],
"difficulty": 2
},
{
"slug": "reverse-string",
"name": "Reverse String",
"uuid": "5e273619-6a13-443f-8734-8ff1e9963f2e",
"practices": [],
"prerequisites": [],
"difficulty": 2
}
]
},
Expand Down
7 changes: 7 additions & 0 deletions exercises/practice/reverse-string/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Instructions

Reverse a string

For example:
input: "cool"
output: "looc"
11 changes: 11 additions & 0 deletions exercises/practice/reverse-string/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"authors": ["ajborla"],
"files": {
"solution": ["reverse_string.prg"],
"test": ["reverse_string_test.prg"],
"example": [".meta/example.prg"]
},
"blurb": "Reverse a string.",
"source": "Introductory challenge to reverse an input string",
"source_url": "https://medium.freecodecamp.org/how-to-reverse-a-string-in-javascript-in-3-different-ways-75e4763c68cb"
}
17 changes: 17 additions & 0 deletions exercises/practice/reverse-string/.meta/example.prg
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
* ----------------------------------------------------------------------------
* exercism.org
* Harbour Track Exercise: reverse-string
* Contributed: Anthony J. Borla ([email protected])
* ----------------------------------------------------------------------------

function ReverseString(string)
local i, rstring := ""
* Reject missing, or wrong number of arguments
if PCOUNT() <> 1 ; return NIL ; endif

* Build return string by traversing input string backwards (right-to-left)
for i := LEN(string) to 1 step -1
rstring := rstring + SUBSTR(string, i, 1)
next
return rstring

28 changes: 28 additions & 0 deletions exercises/practice/reverse-string/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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.

[c3b7d806-dced-49ee-8543-933fd1719b1c]
description = "an empty string"

[01ebf55b-bebb-414e-9dec-06f7bb0bee3c]
description = "a word"

[0f7c07e4-efd1-4aaa-a07a-90b49ce0b746]
description = "a capitalized word"

[71854b9c-f200-4469-9f5c-1e8e5eff5614]
description = "a sentence with punctuation"

[1f8ed2f3-56f3-459b-8f3e-6d8d654a1f6c]
description = "a palindrome"

[b9e7dec1-c6df-40bd-9fa3-cd7ded010c4c]
description = "an even-sized word"
153 changes: 153 additions & 0 deletions exercises/practice/reverse-string/PRGUNIT.prg
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
* ----------------------------------------------------------------------------
* Harbour Unit Test Worker
* Anthony J. Borla ([email protected])
* ----------------------------------------------------------------------------

#ifndef UTILS_PRG
#include "utils.prg"
#endif

procedure MakeTestDatabaseStructure(dbfName)
* Creation overwites any existing database
create &dbfName

* Each record describes the structure of a FIELD
append blank
replace Field_name with "NAME", Field_type with "C",;
Field_Len with 80, Field_dec with 0
append blank
replace Field_name with "CMPOP", Field_type with "C",;
Field_Len with 2, Field_dec with 0
append blank
replace Field_name with "EXPVALUE", Field_type with "C",;
Field_Len with 80, Field_dec with 0
append blank
replace Field_name with "CMDSTR", Field_type with "C",;
Field_Len with 80, Field_dec with 0

* Ensure data written to disk
close &dbfName
return

procedure MakeTestDatabase(dbfName)
local dbfStructure := dbfName + "_STRUCTURE"

* Build test database from database structure file
do MakeTestDatabaseStructure with dbfStructure
create &dbfName from &dbfStructure

* Ensure database structure file is removed
dbfStructure := dbfStructure + ".dbf"
erase &dbfStructure
return

procedure AddTestDatabase(dbfName, testName, cmpOp, expValue, cmdStr)
* Load a test data record into tests database (note use of 'Wrap' to
* preserve spaces in expected value string)
use &dbfName
append blank
replace &dbfName->NAME with testName
replace &dbfName->CMPOP with cmpOp
replace &dbfName->EXPVALUE with Wrap(expValue)
replace &dbfName->CMDSTR with cmdStr
close &dbfName
return

function RunTests(dbfName, keepTestDBF, outputJSON)
local testName, cmpOp, expValue, cmdStr, retValue, testExpr
local success := .T.

use &dbfName

* Determine, and print, number of tests (required for TAP)
if outputJSON == NIL .OR. !outputJSON
? "1.." + LTRIM(STR(LASTREC()))
endif

* Execute unit tests
do while !EOF()
* Extract test data (note use of 'Unwrap' to extract space-preserved
* expected value string)
testName := ALLTRIM(&dbfName->NAME)
cmpOp := &dbfName->CMPOP
expValue := Unwrap(ALLTRIM(&dbfName->EXPVALUE))
cmdStr := ALLTRIM(&dbfName->CMDSTR)

* Execute test, and build test expression
retValue := TypeToS(&cmdStr)
testExpr := '"' + retValue + '" ' + cmpOp + ' "' + expValue + '"'

* If the parameter flag, outputJSON, is omitted, or set to .F., then
* emit test report in TAP format
if outputJSON == NIL .OR. !outputJSON
* Report test outcome - TAP
if &testExpr
? "OK " + LTRIM(STR(RECNO())) + " - " + testName
else
* Single test failure signals failure of whole suite
success := .F.
? "FAIL " + LTRIM(STR(RECNO())) + " - " + testName
endif
else
* Report test outcome - JSON
? "JSON"
endif

* ... next test
skip
enddo

close &dbfName

* If the parameter flag, keepTestDBF, is omitted, or set to .F., then
* remove the tests database
if keepTestDBF == NIL .OR. !keepTestDBF
dbfName := dbfName + ".dbf"
erase &dbfName
endif

return success

function TypeToS(value)
* Use VALTYPE() instead of TYPE() to check type
local typeValue := VALTYPE(value)

switch typeValue
* Array type (assume 1D array of non-aggregate elements),
* returns the concatenation of elements as a string
case "A" ; return ArrToS(value)

* Character type returned untouched
case "C" ; return value

* Date as "yyyymmdd"
case "D" ; return DTOS(value)

* Logical as literal string representation of self
case "L" ; return IIF(value, ".T.", ".F.")

* String-converted numerics are right-justified, so ensure are
* returned trimmed
case "N" ; return ALLTRIM(STR(value))

* Support use of NIL return type (usually to indicate error)
case "U" ; return "NIL"
endswitch

* Ignore the remaining types, just return NIL (likely runtime error)
return NIL

* Utilities to preserve leading and trailing spaces in strings as they
* are stored into, and extracted from, database fields
function Wrap(string) ; return WrapString(string, .F., "[", "]")
function Unwrap(string) ; return WrapString(string, .T.)

function WrapString(string, doUnwrap, wrapStart, wrapEnd)
local uws
if doUnwrap
uws := SUBSTR(SUBSTR(string, 2), 1, LEN(string) - 2)
else
uws := wrapStart + string + wrapEnd
endif
return uws

8 changes: 8 additions & 0 deletions exercises/practice/reverse-string/reverse_string.prg
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
* ----------------------------------------------------------------------------
* exercism.org
* Harbour Track Exercise: reverse-string
* ----------------------------------------------------------------------------

function ReverseString(string)
return ""

48 changes: 48 additions & 0 deletions exercises/practice/reverse-string/reverse_string_test.prg
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
* ----------------------------------------------------------------------------
* Harbour Unit Test Runner [reverse_string.prg]
* ----------------------------------------------------------------------------

* Variable declarations
memvar TESTS, SUCCESS

* Test database name
TESTS := IIF(PCOUNT() > 0, hb_PValue(1), "TESTS")

* Create tests database
do MakeTestDatabase with TESTS

* Add test data into tests database. Each test case stub should be altered
* to follow this format:
*
* do AddTestDatabase with TESTS, "say Hi!", "==", "Hello, World!", "HelloWorld()"
* do AddTestDatabase with TESTS, "add 5 and 3", "==", "8", "Add_5_And_3(5, 3)"
*
* Note:
* 1st field is the test description (already supplied)
* 2nd field is comparator operator, usually "=="
* 3rd field is the function return result, always written as a string
* 4th field is the function (optionally with arguments), always written as a string
*

* Add test data into tests database
do AddTestDatabase with TESTS, "Empty string", "==", "", "ReverseString('')"
do AddTestDatabase with TESTS, "A word", "==", "tobor", "ReverseString('robot')"
do AddTestDatabase with TESTS, "A capitalised word", "==", "nemaR", "ReverseString('Ramen')"
do AddTestDatabase with TESTS, "A sentence with punctuation", "==", "!yrgnuh m'I", "ReverseString('I'+CHR(39)+'m hungry!')"
do AddTestDatabase with TESTS, "A palindrome", "==", "racecar", "ReverseString('racecar')"
do AddTestDatabase with TESTS, "An even-sized word", "==", "reward", "ReverseString('drawer')"

* Execute unit tests. Arguments:
* - Tests database name
* - Database retention flag (.T. to not delete test database on test end)
* - JSON output flag (.T. to emit test results in JSON format [default is TAP])
SUCCESS := RunTests(TESTS, SToBool(hb_PValue(2)), SToBool(hb_PValue(3)))

* Return success status to OS
ERRORLEVEL(IIF(SUCCESS, 0, 1))

* Code under test (CUT)
#include "reverse_string.prg"

* Unit Test Framework
#include "PRGUNIT.prg"
86 changes: 86 additions & 0 deletions exercises/practice/reverse-string/utils.prg
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
* ----------------------------------------------------------------------------
* Harbour Miscellaneous Utilities
* Anthony J. Borla ([email protected])
* ----------------------------------------------------------------------------

#define UTILS_PRG

*
* Given a string, charSet, interpreted as a set of individual characters,
* and a string, string, returns a copy of string with all occurrences of
* the characters in charSet, removed.
*
function RemoveCharSet(charSet, string)
local i, clen := LEN(charSet)
for i := 1 to clen
string := STRTRAN(string, SUBSTR(charSet, i, 1))
next
return string

*
* Given a string, and a separator string (usually a single character)
* returns an array of separator-split tokens, or the original string
* if separation not possible.
*
function SToArr(string, separator)
local array := {}, i, element

* Return untouched string if no separator, or it is not in string
if PCOUNT() < 2 .OR. separator == NIL ; return string ; endif
i := AT(separator, string) ; if i == 0 ; return string ; endif

* Parse the string, extracting each element, and adding to array
do while i <> 0
element := LEFT(string, i - 1)
if !EMPTY(element) ; AADD(array, element) ; endif
string := SUBSTR(string, i + 1)
i := AT(separator, string)
enddo

* Handle last element, and return array
if !EMPTY(string) ; AADD(array, string) ; endif
return array

*
* Given an array whose elements are non-aggregate types, returns a
* string of those elements separated by separator. If a string
* cannot be built, an empty string is returned.
*
function ArrToS(array, separator)
local i, element, string := "", arrlen := LEN(array)
if PCOUNT() < 2 .OR. separator == NIL ; separator := "" ; endif
if arrlen < 1 ; return "" ; endif
for i := 1 to arrlen
element := IIF(VALTYPE(array[i]) <> "C", ALLTRIM(STR(array[i])), array[i])
string += element + separator
next
return ;
IIF(EMPTY(separator), string, SUBSTR(string, 1, RAT(separator, string) - 1))

*
* Given a string, returns the Boolean status indicating whether
* it is convertible to an integer. Non-numeric and floating
* point values will both return .F.
*
function IsINTString(s)
local slen, i
if PCOUNT() <> 1 .OR. VALTYPE(s) <> "C" ; return .F. ; endif
slen := LEN(s)
if AT(".", s) <> 0 .OR. slen < 1 ; return .F. ; endif
if VAL(s) <> 0 ; return .T. ; endif
for i := 1 to slen
if !(SUBSTR(s, i, 1) $ "0123456789") ; return .F. ; endif
next
return .T.

*
* Given a string, returns the Boolean value represented.
*
function SToBool(s)
return ;
IIF(VALTYPE(s) <> "C", NIL, ;
IIF(UPPER(s) == ".T.", .T., ;
IIF(UPPER(s) == ".F.", .F., ;
IIF(SUBSTR(s, 1, 1) $ 'Tt', .T., ;
IIF(SUBSTR(s, 1, 1) $ 'Ff', .F., NIL)))))