02-two-in-a-row.scm 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. (import
  2. (except (rnrs base) let-values map)
  3. (only (guile)
  4. lambda* λ))
  5. ;;; ============
  6. ;;; TWO IN A ROW
  7. ;;; ============
  8. ;; A function shall be defined, which determines, whether there are 2
  9. ;; subsequent elements inside a given list, for which ~eq?~ is ~#t~.
  10. ;; CODE 1:
  11. ;; (define is-first?
  12. ;; (λ (a lat)
  13. ;; (cond
  14. ;; [(null? lat) #f] ; check for empty list
  15. ;; [else (eq? a (car lat))])))
  16. ;; (define two-in-a-row?
  17. ;; (λ (lat)
  18. ;; (cond
  19. ;; [(null? lat) #f] ; check for empty list
  20. ;; [else
  21. ;; (or (is-first? (car lat) (cdr lat))
  22. ;; (two-in-a-row? (cdr lat)))])))
  23. ;; However, this version obscures, that in one case it performs a
  24. ;; check twice. When it checks, whether the list is ~null?~ in
  25. ;; ~is-first?~ and returns ~#f~, ~two-in-a-row?~ will recur and call
  26. ;; itself. Then it will check for a second time, whether the list it
  27. ;; receives is ~null?~.
  28. ;; To avoid this duplicate check, the book suggests to leave the
  29. ;; decision of whether to recur or not to the function, which checks
  30. ;; for an empty list first. That is ~is-first?~.
  31. ;; Let us try to leave that responsibility to ~is-first?~:
  32. ;; CODE 2:
  33. ;; (define is-first?
  34. ;; (λ (a lat)
  35. ;; (cond
  36. ;; [(null? lat) #f]
  37. ;; [else
  38. ;; (or (eq? a (car lat))
  39. ;; ;; Not (cdr lat) here! is-first? is already given the cdr of
  40. ;; ;; the list and the argument ~a~ is the actual car of the
  41. ;; ;; list! The predicate ~is-first?~ does not reduce the
  42. ;; ;; input. That is a responsibility of ~two-in-a-row?~.
  43. ;; (two-in-a-row? lat))])))
  44. ;; (define two-in-a-row?
  45. ;; (λ (lat)
  46. ;; (cond
  47. ;; [(null? lat) #f]
  48. ;; [else
  49. ;; (is-first? (car lat) (cdr lat))])))
  50. ;; Now we have two functions which are mutually recursive.
  51. ;; NOTE: There is also a problem of (two-in-a-row? lat) in ~is-first?~
  52. ;; not being in the tail position and thus growing the stack each
  53. ;; call. (two-in-a-row? lat) is inside the (or ...) expression, which
  54. ;; still needs to be kept on the stack, so that is-first? can return
  55. ;; the result of (or ...).
  56. ;; NOTE: There is still a duplicate check of (null? lat), because when
  57. ;; ~two-in-a-row?~ is called from ~is-first?~, it checks again,
  58. ;; whether the list is empty.
  59. ;; NOTE: is-first? decides, whether to recur or not.
  60. ;; OBSERVATION: When we look at what ~two-in-a-row?~ actually does,
  61. ;; when called from ~is-first?~, we can see, that it does not actually
  62. ;; do much. The ~null?~ check for the empty list will always be ~#f~,
  63. ;; because the same thing was already checked in ~is-first?~.
  64. ;; OBSERVATION: ~lat~ is given to ~two-in-a-row?~. So ~two-in-a-row?~
  65. ;; will always enter the ~else~ branch, when called from ~is-first?~
  66. ;; and the ~null?~ check is only useful, when ~two-in-a-row?~ is
  67. ;; initially called from outside of ~is-first?~.
  68. ;; CONCLUSION: This observation leads to the conclusion, that one
  69. ;; could write a single function, by modifying ~is-first?~ a little
  70. ;; more, so that the call to ~two-in-a-row?~ is no longer needed.
  71. ;; The modified version of ~is-first?~ will be renamed to
  72. ;; ~two-in-a-row?~ to express what it does.
  73. ;; CODE 3:
  74. ;; (define two-in-a-row?
  75. ;; (λ (preceding lat)
  76. ;; (cond
  77. ;; [(null? lat) #f]
  78. ;; [else
  79. ;; (or (eq? preceding (car lat))
  80. ;; (two-in-a-row? (car lat) (cdr lat)))])))
  81. ;; NOTE: There is still a problem here: ~two-in-a-row?~ receives
  82. ;; always 2 arguments. This is inconvenient to work with and
  83. ;; unexpected at the caller side. One can use ~two-in-a-row?-helper~
  84. ;; to avoid this.
  85. ;; NOTE: The problem of non-tail position of the call to
  86. ;; ~(two-in-a-row? (car lat) (cdr lat))~ still persists.
  87. ;; CODE 4:
  88. ;; (define two-in-a-row?-helper
  89. ;; (λ (preceding lat)
  90. ;; (cond
  91. ;; [(null? lat) #f]
  92. ;; [else
  93. ;; (or (eq? preceding (car lat))
  94. ;; (two-in-a-row?-helper (car lat) (cdr lat)))])))
  95. ;; (define two-in-a-row?
  96. ;; (λ (lat)
  97. ;; (cond
  98. ;; [(null? lat) #f]
  99. ;; [else
  100. ;; (two-in-a-row?-helper (car lat) (cdr lat))])))
  101. ;; This clears up the interface for the user.
  102. ;; A tail call optimized version, getting rid of the non-tail position
  103. ;; of the recursive call, could be written as follows:
  104. ;; CODE 5:
  105. (define two-in-a-row?-helper-tail-recursive
  106. (λ (preceding lat)
  107. (cond
  108. [(null? lat) #f]
  109. ;; It is a bit meh, that we cannot simply return the result of the
  110. ;; expression here and need to write #t instead. Perhaps there is a better
  111. ;; way?
  112. [(eq? preceding (car lat)) #t]
  113. [else
  114. ;; Tail-recursive.
  115. (two-in-a-row?-helper-tail-recursive (car lat) (cdr lat))])))
  116. (define two-in-a-row?
  117. (λ (lat)
  118. (cond
  119. [(null? lat) #f]
  120. [else
  121. (two-in-a-row?-helper-tail-recursive (car lat) (cdr lat))])))
  122. ;; USAGE:
  123. (display
  124. (simple-format
  125. #f "(two-in-a-row? '(1 2 2 3 4)) -> ~a\n"
  126. (two-in-a-row? '(1 2 2 3 4))))
  127. (display
  128. (simple-format
  129. #f "(two-in-a-row? ''(1 2 3 4 5)) -> ~a\n"
  130. (two-in-a-row? '(1 2 3 4 5))))