#lang racket

(provide (all-defined-out)) ;; so we can put tests in a second file

;; Your solutions should be correct, in good style, and use only features we have used
;; in class. Do not use mutation except in problems 10 and 13. (You also need mutation
;; to test problem 11.)

;; Write a function sequence that takes 3 arguments spacing, low, and high,
;; all assumed to be numbers. Further assume spacing is positive. sequence
;; produces a list of numbers from low to high (including low and possibly
;; high) separated by spacing and in sorted order. Sample solution: 4 lines.
;; eg. (sequence2311) -> '(357911)
(define (sequence spacing low high)
  (if (> low high) '() ;; if low is more than high, return an empty list
      ;; else, cons low with the result of calling sequence with spacing, low + spacing, and high
      (cons low (sequence spacing (+ low spacing) high) )
  )
)

;; Write a function string-append-map that takes a list of strings xs and a
;; string suffix and returns a list of strings. Each element of the output
;; should be the corresponding element of the input appended with suffix
;; (with no extra space between the element and suffix). You must use
;; Racket-library functions map and string-append. Sample solution: 2 lines.
(define (string-append-map xs suffix)
  (map (lambda (arg) ;; declare anonymous function fn to string-append suffix to the argument
          (string-append arg suffix)) xs)) ;; map fn to xs


;; Write a function list-nth-mod that takes a list xs and a number n. If the
;; number is negative, terminate the computation with (error "list-nth-mod:
;; negative number"). Else if the list is empty, terminate the computation
;; with (error "list-nth-mod: empty list"). Else return the ith element of
;; the list where we count from zero and i is the remainder produced when
;; dividing n by the list’s length. Library functions length, remainder, car,
;; and list-tail are all useful – see the Racket documentation.
;; Sample solution is 6 lines.

(define (list-nth-mod xs n)
  (cond ;; define conditional block; 
    [(< n 0) (error "list-nth-mod: negative number")] ;; if n is negative, throw error "list-nth-mod: negative number"
    [(= xs '()) (error "list-nth-mod: empty list")] ;; else if xs is empty, throw error "list-nth-mod: empty list"
    [else
      (let loop ([xs xs] [n n] [len (length xs)])
        (cond
          [(zero? n) (car xs)]
          [else (loop (cdr xs) (sub1 n) len)]
        )
      )
    ]
  )
)

;; Write a function stream-for-k-steps that takes a stream s and a number
;; k. It returns a list holding the first k values produced by s in order.
;; Assume k is non-negative. Sample solution: 5 lines. Note: You can test
;; your streams with this function instead of the graphics code.
;; Problem 4

(define (stream-for-k-steps s k)
  (cond [(< 0 k) null]
        [(= 0 k) null]
        [#t (cons (car s) (stream-for-k-steps (cdr s) (- k 1)))]))

;; Write a stream funny-number-stream that is like the stream of natural numbers
;; (i.e., 1, 2, 3, ...) except numbers divisble by 6 are negated (i.e., 1, 2, 3,
;; 4, 5, -6, 7, 8, 9, 10, 11, -12, 13, ...). Remember a stream is a thunk that
;; when called produces a pair. Here the car of the pair will be a number and the
;; cdr will be another stream.

(define (funny-number-stream n)
  (stream-cons
   (if (zero? (modulo n 6)) (- n) n)
   (funny-number-stream (+ n 1))))

;; Write a stream zoran-then-bentley, where the elements of the stream alternate
;; between the strings "zoran.jpg" and "bentley.jpg" (starting with "zoran.jpg").
;; More specifically, zoran-then-bentley should be a thunk that when called
;; produces a pair of "zoran.jpg" and a thunk that when called produces a pair of
;; "bentley.jpg" and a thunk that when called... etc. Sample solution: 4 lines.

(define zoran-then-bentley
  (letrec
    ([fn (lambda (s)
      (cons s (lambda () (fn (if (equal? s "zoran.jpg") "bentley.jpg" "zoran.jpg")))))
    ])
    (fn "zoran.jpg")
  )
)

;; Write a function stream-add-one that takes a stream s and returns another stream.
;; If s would produce v for its ith element, then (stream-add-one s) would produce the
;; pair (1 . v) for its ith element. Sample solution: 4 lines. Hint: Use a thunk that
;; when called uses s and recursion. Note: One of the provided tests uses (stream-add-one
;; zoran-then-bentley) with place-repeatedly.

(define (stream-add-one s) ;; used letrec to define a recursive function that returns a pair of 1 and the next number
  (letrec
    ([fn (lambda (s)
      (cons 1 (lambda () (fn (stream-rest s)))))
    ])
    (fn s)
  )
)

;; Write a function cycle-lists that takes two lists xs and ys and returns a stream. The
;; lists may or may not be the same length, but assume they are both non-empty. The elements
;; produced by the stream are pairs where the first part is from xs and the second part is
;; from ys. The stream cycles forever through the lists. For example, if xs is ’(1 2 3) and
;; ys is ’("a" "b"), then the stream would produce, (1 . "a"), (2 . "b"), (3 . "a"), (1 . "b"),
;; (2 . "a"), (3 . "b"), (1 . "a"), (2 . "b"), etc.

;; Sample solution is 6 lines and is more complicated than the previous stream problems. Hints:
;; Use one of the functions you wrote earlier. Use a recursive helper function that takes a
;; number n and calls itself with (+ n 1) inside a thunk.

(define (cycle-lists xs ys) ;; used letrec to define a recursive function that returns a pair of the first element of xs and the first element of ys
  (letrec
    ([fn (lambda (xs ys)
          (cons (cons (car xs) (car ys)) (lambda () (fn (cdr xs) (cdr ys))))
    )]
    )
    (fn xs ys)
  )
)

;; Write a function vector-assoc that takes a value v and a vector vec. It should behave like Racket’s
;; assoc library function except (1) it processes a vector (Racket’s name for an array) instead of a list,
;; (2) it allows vector elements not to be pairs in which case it skips them, and (3) it always takes
;; exactly two arguments. Process the vector elements in order starting from 0. You must use library
;; functions vector-length, vector-ref, and equal?. Return #f if no vector element is a pair with a car
;; field equal to v, else return the first pair with an equal car field. Sample solution is 9 lines,
;; using one local recursive helper function.

;; assoc: Locates the first element of lst whose car is equal to v according to is-equal?. If such an
;; element exists, the pair (i.e., an element of lst) is returned. Otherwise, the result is #f.
;; https://docs.racket-lang.org/reference/pairs.html

;; eg (vector-assoc 10 #(1 2 3 4 5 6 7 8 9 10)) -> #f
(define (vector-assoc v vec)
  (letrec
    ([fn (lambda (v vec i)
      (if (= i (vector-length vec)) #f
          (let ([elem (vector-ref vec i)])
            (if (and (pair? elem) (equal? v (car elem))) elem
                (fn v vec (+ i 1))
            )
          )
      )
    )]
    )
    (fn v vec 0)
  )
)

Please check the correctness of these function implementations, given their instructions.