Project 7: Rust Basics

$30.00 $24.00

65 for Problem 1 spread across several functions – 35 for Problem 2 for a small command line program FAQ: TBD (see pinned Piazza posts) Overview This project introduces basic Rust techniques. The first problem builds a number of independent functions which rise moderately in complexity. Early functions deal with basic syntax and iteration through…

Rate this product

You’ll get a: zip file solution

 

Categorys:

Description

Rate this product

65 for Problem 1 spread across several functions

– 35 for Problem 2 for a small command line program

FAQ: TBD (see pinned Piazza posts)

Overview

This project introduces basic Rust techniques.

The first problem builds a number of independent functions which rise

moderately in complexity. Early functions deal with basic syntax and

iteration through Rust’s Slices like type `&[i32]`. Later functions

will require handling of references and dealing with modest ownership

issues that may involve borrowing via the reference creation operator

`&` or cloning data with that method supported.

The second problem builds a small command line program which apes the

behavior of the classic `wc` UNIX utility: it counts the characters,

words, and lines in an input file. This will give a chance to build a

command line program and deal with basic file input in Rust.

Code Pack Files

The following files are distributed in the codepack.

| File | State | Description |

|———————–|———-|——————————————–|

| `project7/` | Provided | Project root |

| `Makefile` | Provided | Supplementary build file, try `make help` |

| `Cargo.toml` | Provided | Options for the project |

| `README.md` | Provided | Project description |

| | | |

| `src/prob1_basics.rs` | EDIT | Problem 1 functions to complete |

| `src/bin/prob2_wc.rs` | EDIT | Problem 2 executable to complete |

| | | |

| `src/bin/cmd_args.rs` | Provided | Sample file demoing command line arguments |

| `src/bin/readfile.rs` | Provided | Sample file demoing file reading |

| | | |

| `tests/test_prob1.rs` | Testing | Unit Tests for Problem 1 |

| | | Try `make test-prob1` |

| `test_prob2_wc.sh` | Testing | Overview testing script for Problem 2 |

| | | Try `make test-prob2` |

| | | |

| `test_prob1.org` | Testing | Point totaling for Problem 1 |

| `test_prob2.org` | Testing | Point totaling for Problem 2 |

| `test_post_filter` | Testing | Used during points tallying |

| `test-data/*` | Testing | Test input files |

| `testy` | Testing | Testing framework for totaling scores |

Cargo Commands

The standard method to build and run tests with Rust’s Cargo tool is

as follows:

“`sh

only build code, show compiler errors

>> cargo build

Compiling project7 v0.1.0 (./330-F2023/projects/p7-rust/p7)

Finished dev [unoptimized + debuginfo] target(s) in 0.79s

build code and tests and then run all tests

>> cargo test

Compiling project7 v0.1.0 (/home/kauffman/Dropbox/teaching/330-F2023/projects/p7-rust/p7)

Running tests/test_prob1.rs (target/debug/deps/test_prob1-7c1ed0b0312b59c1)

running 16 tests

test test_circulant1 … ok

test test_circulant2 … ok

test test_count_words2 … ok

test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

“`

This is standard for development environments but lacks a few useful

features such as point totaling and testing of executable that may be

part of the project.

Building the Project and Running Tests

Since Cargo lacks point totaling and easy testing of executable, this

project layers a `Makefile` on top of it with a few other testing

files. These can be accessed via the standard `make` utility which you

may need to install. A demo:

“`sh

>> make help

Typical usage is:

> make build all programs

> make test run all tests

> make test-prob1 run test for problem 1

> make test-prob2 run test for problem 2

> make clean-tests remove test-results/ directory

> make clean remove build files

LINUX ONLY (makes use of testy script which fails on MacOS

> make ltest run all tests and show scores, linux only

> make ltest-prob1 run problem 1 tests and show score, linux only

> make ltest-prob2 run problem 2 tests and show score, linux only

> make ltest-prob2 testnum=5 run problem 2 test 5 only, linux only

>> make test-prob1

cargo build

Finished dev [unoptimized + debuginfo] target(s) in 0.02s

cargo test –test test_prob1

Finished test [unoptimized + debuginfo] target(s) in 0.02s

Running tests/test_prob1.rs (target/debug/deps/test_prob1-8c4fed3e3a54c66a)

running 16 tests

test test_circulant1 … ok

test test_circulant2 … ok

test test_circulant3 … ok

test test_count_words1 … ok

test test_count_words2 … ok

test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

>> make test-prob2

./test_prob2_wc.sh

=============== PROBLEM 2 TESTS ===============

BUILDING PROGRAMS

Finished dev [unoptimized + debuginfo] target(s) in 0.02s

EXPECTED AND ACTUAL OUTPUT DIFFER, TEST FAILURES LIKELY

Examine the above side-by-side diff, look for | < > symbols

in the middle which indicate differnces between the Expected

and Actual output

“`

The command `make test-prob1` will build and run tests for problem 1

and report the point totals for it. Likewise `make test-prob2` will

test problem 2 and `make test` will run all tests.

On Linux systems, you may also run `make ltest` to run all tests just

as they would on Gradescope which will show your overall scores for

Problems 1 and 2 along with additional information on test

failures. Unfortunately MacOS and Windows cmd.exe are incompatible

with the scripts used for this.

Ground Rules

As before, this is an individual assignment. You only receive help on

the project from staff members associated with the course.

Generally you may use functionality in Rust’s standard library and the

external Regex crate which will be downloaded as part of the first

project build. ~~However, use of an existing function or package that

would complete a required function or program in with no effort from

the student is prohibited under penalty of loss of all credit on the

project. That is: do the work.~~ (We haven’t found any library functions that would be prohibited – don’t stress it).

Guidance is given on techniques to use in each of the problems so keep

a copy of the README close.

Problem 1: Basic Functions in `src/prob1_basics.rs`

`gauss`

As the story goes, [young Friedrich Gauss was tasked to add up numbers

1 to

100](https://www.americanscientist.org/article/gausss-day-of-reckoning)

and had it done in a snap. Write a function to do the same for an

arbitrary stopping integer. The input type for the function is an

`i32`, a 32-bit integer referred to almost everywhere else as an

`int`.

“`rust

pub fn gauss(n: i32) -> i32;

/// Returns the sum 1 + 2 + … + n

///

/// If n is less than 0, return -1

///

/// May use either a fixed equation to calculate the answer or an

/// iterative approach (though Gauss himself would likely prefer the

/// former…)

///

/// EXAMPLES:

/// gauss(5) -> 15

/// gauss(10) -> 55

/// gauss(-2) -> -1

“`

`in_range`

Count elements in a range.

“`rust

pub fn in_range(slice: &[i32], lo: i32, hi: i32) -> i32

/// Returns a count of elements in `slice` that satisfy:

/// lo <= x <= hi

/// Uses an iterator over the slice to ensure all elements are visited.

///

/// EXAMPLES:

/// in_range([5,2,1,3,9], 2, 5) -> 3

/// in_range([5,2,1,3,9], 3, 4) -> 1

/// in_range([5,2,1,3,9], 2, 10) -> 4

/// in_range([], 2, 10) -> 0

“`

The data type in this case is Rusts [*Slice

type*](https://doc.rust-lang.org/book/ch04-03-slices.html other-slices)

notated here as `&[i32]` for

– `[..]` to indicate that it is a slice

– `i32` to indicate the elements of the slice are `i32` integers

– `&` to indicate this is a reference to a slice (almost all slices

are references)

Slices serve a similar place to arrays in other languages. They are of

fixed length and one can access their length via `slice.len()`. They

also have a wide [variety of

methods](https://doc.rust-lang.org/std/primitive.slice.html)

associated with them. One aspect that they have in common with

containers in Python and Java is their “iterability” as in:

“`rust

fn printall(slice: &[i32]){

for &x in slice {

println!(“x: {}”,x);

}

}

“`

Several other Rust data types such as Vectors have this property.

Keep in mind as well that rust defaults all `let` bound variables to

immutable by default so you’ll likely need to set up mutability.

`mean`

“`rust

pub fn mean(slice: &[f64]) -> Option<f64>

/// Calculates and returns the mean of elements in `slice` of floating

/// point values. To handle empty slices, this function returns an

/// Option: None for empty slices, and Some(mean) for filled slices.

///

/// EXAMPLES:

/// mean([]) -> None

/// mean([10.0, 5.0, 7.0, 20.0]) -> Some(10.5)

/// mean([-10.0, 3.0, 2.0]) -> Some(-1.6666)

“`

This function will have nearly identical structure to the previous one

for its iteration pattern. The catches are to add just a little

checking for 0-length slices and to get used to the Option type which

is identical in form and use to OCaml’s option.

`subset`

“`rust

pub fn subset<T>(slicea: &[T], sliceb: &[T]) -> bool

where T: PartialEq<T> // allows comparisons via ==

/// Returns true if `slicea` is a subset of `sliceb` and false

/// otherwise. The function is generic so works with slices of any

/// type. The slices are not ordered so must be searched sequentially.

///

/// EXAMPLES:

/// subset([1,3,2], [1,2,3,4,5]) -> true

/// subset([1,3,2], [1,3,4,5]) -> false

/// subset([“a”,”c”,”d”,”c”], [“d”,”c”,”a”]) -> true

/// subset([“a”,”c”,”d”,”c”], [“d”,”c”,”a”,”r”]) -> true

/// subset([“a”,”q”,”d”], [“d”,”c”,”a”,”r”]) -> false

///

/// NOTE: Utilizing certain methods of slices may lead to shorter code

/// by letting methods detect whether elements are contained or not.

“`

Examining the prototype for `subset()` carefully you’ll see that it

has a type parameter `<T>`. This makes the function generic: it can

work on any type of slice as shown in the examples. There is a

constraint on `T` however: it must implement certain methods which are

described in the `PartialEq` trait (traits are collections of methods

similar to Java’s interfaces). The essence of `PartialEq` is that it

allows two data to compared for equality via `a == b`. Types like

integers and strings have this property so will work with `subset()`

To complete `subset()` you’ll need to check whether all elements of

the left `slicea` exist in the right `sliceb`. There is no order to

either set so a linear search is in order. You may do this manually or

explore methods of slices that may lower the number of lines of code

you need to write.

`to_binstring`

“`rust

pub fn to_binstring(num: u32) -> String

/// Return a string showing the binary digits of the given unsigned

/// (positive) integer. Calculates the binary digits through repeated

/// division by 2 where the remainders become the digits in the binary

/// number. A survey of this method is here

/// https://www.cuemath.com/numbers/decimal-to-binary/

///

/// While converting, pushes digits of “0” or “1” into a container

/// type, vector being a good choice. These digits need to be

/// reversed at the end of the computation (last digit found is most

/// significant, leftmost bit) so visits the digits in reverse order

/// in the container appending them to a String which is ultimately

/// returned.

///

/// No leading zeros are provided so the left-most character is always

/// a 1 EXCEPT in the special case of decimal value 0 which should

/// return the string “0”.

///

/// EXAMPLES:

/// to_binstring( 0) -> “0”

/// to_binstring( 2) -> “10”

/// to_binstring( 9) -> “1001”

/// to_binstring( 32) -> “100000”

/// to_binstring(510) -> “111111110”

“`

The function above is meant to convert a number to a string

representing its binary equivalent. Before engaging with the code,

it’s a good idea to work a few examples by hand to make sure you have

a solid algorithm to use. The easiest one to implement (and therefore

the recommended one) is to repeatedly divide by 2 and track remainders

which are then reversed. Here is a brief example:

*Convert 124 to base 2*:

“`text

124 / 2 = 62 rem 0

62 / 2 = 31 rem 0

31 / 2 = 15 rem 1

15 / 2 = 7 rem 1

7 / 2 = 3 rem 1

3 / 2 = 1 rem 1

1 / 2 = 0 rem 1

Remainders in revers order:

1111100 which is 124

“`

Take note of the termination criteria (quotient is 0) and that the

remainders are the digits of the binary number.

Also take note that after the digits are accumulated, you’ll need to

reverse them. This is usually done by pushing to the end of a vector

or string then visiting the elements in reverse order. Doing so yields

a Linear time algorithm.

Or you can implement a quadratic algorithm that inserts at the

beginning of your data structures… *like a chump.*

You’ll want to acquaint yourself with the `String` and `Vector` types

that Rust provides which have methods like `push_str(x) / push(x)` to

take on to their ends and are iterable.

`circulant`

“`rust

pub fn circulant<T>(r0_slice: &[T]) -> Vec<Vec<T>>

where T:Clone // elements have .clone() for deep copying

/// Construct a circulant matrix (2D vector) from the given

/// slice. Briefly a circulant matrix has row i rotated left by i

/// elements with row 0 being identical to the parameter r0_slice. A

/// vector or rows is allocated and each row is constructd as a

/// rotation left by i indices from the 0th row. The function is

/// generic and accepts any type that can be Cloned elements from

/// r0_slice must have clone() invoked on them to get a memory

/// distinct copy of them.

///

/// EXAMPLES:

///

/// circulant(&[1,2,3]) ->

/// [[1,2,3],

/// [2,3,1],

/// [3,1,2]]

///

/// circulant(&[“a”,”b”,”c”,”d”]) ->

/// [[“a”,”b”,”c”,”d”]

/// [“b”,”c”,”d”,”a”]

/// [“c”,”d”,”a”,”b”]

/// [“d”,”a”,”b”,”c”]]

“`

Note again that `ciculant()` is generic and will work on any type so

long as the elements can be `.clone()`’d as dictated by the `Clone`

trait. The typical algorithm to complete this is to allocate a Vector

of rows, then build each row of the matrix by cloning elements from

the input slice into a row vector and finally adding the completed row

to vector of all rows. There is a bit of trickiness with getting the

iteration pattern right to shift elements in each row but with a

little finagling this one will not be too bad.

`count_words`

“`rust

pub fn count_words(text: &String) -> i32

/// Returns a count of the number of “words” in `text`. The notion of

/// a word is a series of non-whitespace characters, (whitespace is

/// space, tab, newline). Regular expressions may be used for counting

/// words or character-by-character iteration to count whitespace

/// transitions. In either case, iteration through the string will be

/// required, either by regex match by direct iteration on the

/// characters.

///

/// NOTE: The Regular expression “crate” is marked as a dependency for

/// this project so will be downloaded HOWEVER you will need to add an

/// appropriate “use” statement to access its functionality here.

///

/// EXAMPLES:

/// count_words( &String::from(“hello world”)) -> 2

/// count_words( &String::from(” “)) -> 0

/// count_words( &String::from(“ALL … NON – whitespace !! “)) -> 6

/// count_words( &String::from(“tabs\tor spaces\tor\ttabs\tor spaces”)) -> 7

“`

A classic use of regular expressions is to iterate across some notion

of words. An easy definition of a “word” is a contiguous sequence of

non-whitespace characters. As the doc comment indicates, you may

attempt the problem by either

– Using the Regular expression crate that Rust provides which will

require searching out its API and adapting what you’ve learned about

regexs to this situation

– Iterating over the characters and doing some manual checking. This

will be tedious but since the difference is merely whitespace vs

not, it is feasible to hand code this. Just expect to make some

mistakes in your first attempts.

Note: the input type is `&String`, a string reference. This function

would probably be better specified as a string slice as in `&str` as

this would make it more flexible but this was only realized after all

the test cases were written. So it goes…

Problem 2: Word Count on Files in `src/bin/prob2_wc.rs`

Expanding on the last function in the basics which counted words in a

string, this problem will have you implement a small command line

program that approximates the functionality of the classic `wc` UNIX

utility that prints the number of characters, words, and lines in a

file. For those unacquainted, `wc` works as follows:

“`

>> wc test-data/bruce.txt

2 17 91 test-data/bruce.txt

2 lines

17 words

91 characters

>> wc test-data/howl.txt

145 2909 17521 test-data/howl.txt

145 lines

2909 words

17521 characters

“`

Create and Run a Main Function

In the file `src/bin/prob2_wc.rs` create a `main ()` function. This

should allow you to use the following cargo commands to experiment

with it.

“`sh

with command line arg of ‘test-data/bruce.txt’

>> cargo run –bin prob2_wc — test-data/bruce.txt

no command line arg to test functionalty when it is missing

>> cargo run –bin prob2_wc —

with –quiet don’t print cargo build messages

>> cargo run –quiet –bin prob2_wc — test-data/bruce.txt

“`

While more cumbersome that just running an `a.out`, it is the

prescribed method to stay compatible with cargo which buries built

executables several layers deep in the project directory structure.

Interactive experimentation will be helpful as you develop your

program.

Command Line Handling

Examine the provided `src/bin/cmd_args.rs` which demonstrates how to

access commandline arguments in Rust. This is necessary in `prob2_wc`

as the file to counted comes from the command line. Experimenting with

this program is a good idea and can be done using a similar cargo

command as above:

“`sh

>> cargo run –quiet –bin cmd_args — abc 123 you and me

arg[0]: target/debug/cmd_args

arg[1]: abc

arg[2]: 123

arg[3]: you

arg[4]: and

arg[5]: me

6 total args

“`

Opening and Reading Files Line-by-Line

A common tactic for handling input files is to read them one line at a

time. This is often done in Rust via the `BufReader` type and is

demonstrated in the provided `src/bin/readfile.rs` program. It demos

several ways as well that files are opened and checked for errors as

well as how to iterate through lines of a file printing each line in

turn. Again, experimenting with this program will allow you to adapt

its techniques to solve problem 2:

“`sh

>> cargo run –quiet –bin readfile —

readlines1():

1: Four score and seven years ago our fathers brought forth on this continent, a

2: new nation, conceived in Liberty, and dedicated to the proposition that all men

3: are created equal.

4:

5: Now we are engaged in a great civil war, testing whether that nation, or any

“`

Pay close attention to several aspects of handling files demonstrated

in `readfile.rs`

– Opening files returns Rusts `Result` type, with variants of `Ok` and

`Err` indicating success and failure. These must be handled in some

way. Several options including `match` statements are demonstrated

in `readfile.rs`.

– Unlike in `readfile.rs`, there are some specific non-`panic!`

expectations for `prob2_wc` when a file can’t be opened which are

described in the next subsection.

– Reading lines may also fail yielding `Ok/Err` results. Handling for

this is also demonstrated and should be mimicked.

**NOTE**: when reading line-by-line with `BufReader`, newline

characters are always **chopped**. This means that if you are trying

to count characters (and you are) you’ll need to add one for each line

to account for the omitted `\n`. At least, this is the case when

handling UNIX files which are the provided test files. Windows users

that open up those text files may inadvertently convert them to DOS

format which uses 2 characters per line break and is likely to break

the tests. Be forewarned on all accounts: add 1 char per line and

avoid converting files to DOS text format.

Graceful Exiting

In the event that no command line is provided OR that a file cannot be

opened, `prob2_wc` should exit gracefully using the `exit(num)`

function from the standard rust libraries. This function has already

been `used`ed at the top of the `prob2_wc.rs` file so can be used without

qualifying its crate location.

The required messages for failure are as follows:

“`sh

>> cargo run –quiet –bin prob2_wc —

usage: target/debug/prob2_wc <filename>

error when unable to open a file

>> cargo run –quiet –bin prob2_wc — no-such-file.txt

Couldn’t open file no-such-file.txt: No such file or directory (os error 2)

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ||||||||||||||||||||||||||||||||||||||

Required messsage Reason provied by Err(reason)

“`

The second message has two parts: the left part is from `prob_wc`

while the right part is part of the `Err()` variant that is returned

when a file fails to open. Print them together when this occurs.

Counting Words

It is slightly trickier to count words than lines and

characters. However, you *may* have completed a function that does

just that recently and it *may* be a good idea to make use of it here.

Accessing a function in `prob2_wc.rs` from another part of the project

can be done with a `use` statement. Experiment a little using the

following basic pattern:

“`rust

use project_name::file_name:function_name;

“`

Technically speaking these are crate and module names, not project and

function, but the similarities are enough.

Once this you have access to your function from problem 1, use it to

count words on each line that is read.

Counting Characters

**Added Mon Nov 20 05:39:37 PM EST 2023**

Traditionally character counts corresponded directly to byte

counts. In this interpretation a multi-byte unicode character would

count as multiple characters. Rust’s `string.len()` function returns

the total bytes in the string.

Multi-byte characters may instead be counted as a single character

which is the behavior of Rust’s `string.chars().count()` function.

Implementations of `prob2_wc` **should favor `string.len()`** to count

the total bytes (characters) of the string. This is the “right”

behavior based on mimicking the original `wc` utility’s behavior which

counts the total bytes.

Details of character vs byte counts for multi-byte characters are

described in Rust’s documentation of the `String` data type:

https://doc.rust-lang.org/std/string/struct.String.html utf-8

A number of students encountered this issue and reported the

difference in behavior in [Piazza Post

1929](https://piazza.com/class/lkimk0rc39wfi/post/1929).

Output Formatting

After counting lines, words, and characters. Print a message at the

end of the program indicating the counts and the filename like the

following examples:

“`

>> cargo run –quiet –bin prob2_wc — test-data/dijkstra.txt

40 271 1633 test-data/dijkstra.txt

| | | |

line word char filename

“`

Each of line/word/char are 4 wide minimum with right alignment and

whitespace padded on the left. If this sounds intimidating, you’ll

find Rust’s format strings are useful: try using something like

“`rust

println!(“{:4}”,anumber);

“`

and adapt as needed. While some of the examples have more than 4

digits in their counts, we’re not aiming for total beauty, just

awareness that format strings are useful.

Submitting

First, make sure all your changes are pushed to Github using the `git

add`, `git commit`, and `git push` commands.

Next, to submit your project, you can run `submit` from your project

directory.

The `submit` command will pull your code from GitHub, not your local

files. If you do not push your changes to GitHub, they will not be

uploaded to gradescope.

NOTE: All submissions are subject to manual review. If it is

determined that code passed tests cases due to their hard-coding

behavior to match test case expectations, credit will be deducted with

additional penalties. As ever: Do the work.

Academic Integrity

Please **carefully read** the academic honesty section of the course

syllabus. Academic dishonesty includes posting this project and its

solution online like a public github repo. **Any evidence** of

impermissible cooperation on projects, use of disallowed materials or

resources, or unauthorized use of computer accounts, **will be**

submitted to the Student Honor Council, which could result in an XF

for the course, or suspension or expulsion from the University. Be

sure you understand what you are and what you are not permitted to do

in regards to academic integrity when it comes to project

assignments. These policies apply to all students, and the Student

Honor Council does not consider lack of knowledge of the policies to

be a defense for violating them. Full information is found in the

course syllabus, which you should review before starting.

Project 7: Rust Basics
$30.00 $24.00