<commit_before>(ns luhnybin.core
  (:use [clojure.java.io :only [make-reader]]
        [clojure.string  :only [join]])
  (:gen-class))

(set! *warn-on-reflection* true)

;; First define a few small helpers.

(def ^:private digits #{ \0 \1 \2 \3 \4 \5 \6 \7 \8 \9 })
(def ^:private extras #{ \- \space })
(def ^:private ccchars (apply conj digits extras))

(def ^:private c->i
  "Map of characters to their single digit integer values."
  { \0 0, \1 1, \2 2, \3 3, \4 4, \5 5, \6 6, \7 7, \8 8, \9 9 })
(def ^:private c->i*
  "Map of characters to their doubled and digit-summed integer values."
  { \0 0, \1 2, \2 4, \3 6, \4 8, \5 1, \6 3, \7 5, \8 7, \9 9, })

;; 1. ignoring non-digits and working from the right;
;; 2. double every second digit and sum the resulting digits;
;; 3. sum all of the resulting digits; and
;; 4. divide by 10 and take the remainder.
;; 
;; The candidate passes if the result is 0.
(defn luhn?
  "Does a candidate char-seq pass the Luhn test?"
  [chars]
  (->> (filter digits chars)
    (reverse)
    (map #(%1 %2) (cycle [c->i c->i*]))
    (reduce + 0)
    (#(rem % 10))
    (#(if (zero? %) chars nil))))

;; A potential credit card must have 14 to 16 digits (inclusive).
(defn- correct-len? [chars]
  "Are there [14..16] chars in a seq?"
  (let [n (count (filter digits chars))]
    (and (>= n 14) (<= n 16))))

(defn- candidates
  "See if there are any candidates in a string or char-seq."
  [chars]
  (->> (take-while ccchars chars)
    (reductions conj [])
    (filter correct-len?)
    (sort #(compare (count %2) (count %1)))))

(defn process-line
  ""
  [line]
  (loop [pos 0 in (seq line) to-mask #{}]
    (if in
      (let [c (first in)]
        (if (digits c)
          (let [candidates (candidates in)]
            (if-let [cc (some luhn? candidates)]
              (recur (inc pos) (next in)
                     (into to-mask (range pos (+ pos (count cc)))))
              (recur (inc pos) (next in) to-mask)))
          (recur (inc pos) (next in) to-mask)))
      (->> (seq line)
        (map-indexed #(if (and (to-mask %1) (digits %2)) \X %2))
        (apply str)))))

(defn -main [& args]
  (dorun
    (->> nil
      (make-reader (or (first args) *in*))
      (line-seq)
      (map process-line)
      (map println))))

<commit_msg>Added some Marg friendly docs.<commit_after>;; In answer to the [challenge][CHL] set by <NAME> from [Square][SQR].
;; [CHL]: http://corner.squareup.com/2011/11/luhny-bin.html
;; [SQR]: http://squareup.com/

(ns luhnybin.core
  (:use [clojure.java.io :only [make-reader]]
        [clojure.string  :only [join]])
  (:gen-class))

;; First define a few small helpers…

(def ^:private digits #{ \0 \1 \2 \3 \4 \5 \6 \7 \8 \9 })
(def ^:private extras #{ \- \space })
(def ^:private ccchars (apply conj digits extras))

;; A map of characters to their single digit integer values.
(def ^:private c->i
  { \0 0, \1 1, \2 2, \3 3, \4 4, \5 5, \6 6, \7 7, \8 8, \9 9 })

;; A map of characters to their doubled and digit-summed integer values.
(def ^:private c->i*
  { \0 0, \1 2, \2 4, \3 6, \4 8, \5 1, \6 3, \7 5, \8 7, \9 9, })

;; ### The Luhn Algorithm
;;
;; See [Wikipedia][WIKI] for the gory details, but basically:
;; 
;; 1. ignoring non-digits and working from the right;
;; 2. double every second digit and sum the resulting digits;
;; 3. sum all of the resulting digits; and
;; 4. divide by 10 and take the remainder.
;; 
;; The candidate passes if the result is 0.
;; [WIKI]: http://en.wikipedia.org/wiki/Luhn_algorithm
(defn luhn?
  "Does a candidate char-seq pass the Luhn test?"
  [chars]
  (->> (filter digits chars)
    (reverse)
    (map #(%1 %2) (cycle [c->i c->i*]))
    (reduce + 0)
    (#(rem % 10))
    (#(if (zero? %) chars nil))))

;; ### Searching for Potential CCNs

;; A potential credit card must have 14 to 16 digits (inclusive).
(defn- correct-len? [chars]
  (let [n (count (filter digits chars))]
    (and (>= n 14) (<= n 16))))

;; Find any candidates at the ste start of a string or char seq,
;; sorted longest to shortest.
(defn- leading-candidates
  [chars]
  (->> (take-while ccchars chars)
    (reductions conj [])
    (filter correct-len?)
    (sort #(compare (count %2) (count %1)))))

;; ### The Main Public API: Process Strings, Files, or Streams

(defn process-line
  "Process a line, returning the indices of any potential CC numbers."
  [line]
  (loop [pos 0 in (seq line) to-mask #{}]
    (if in
      (let [c (first in)]
        (if (digits c)
          (let [candidates (leading-candidates in)]
            (if-let [cc (some luhn? candidates)]
              (recur (inc pos) (next in)
                     (into to-mask (range pos (+ pos (count cc)))))
              (recur (inc pos) (next in) to-mask)))
          (recur (inc pos) (next in) to-mask)))
      to-mask)))

(defn mask-line
  "Mask out any digits occurring at the marked positions."
  [line to-mask mask-char]
  (->> (seq line)
    (map-indexed #(if (and (to-mask %1) (digits %2)) mask-char %2))
    (apply str)))

;; Read from the file named in the first argument, or *stdin*.
(defn -main [& args]
  (dorun
    (->> nil
      (make-reader (or (first args) *in*))
      (line-seq)
      (map #(mask-line % (process-line %) \X))
      (map println))))


{'fraction_non_alphanumeric': 0.17520061138708445, 'fraction_numerical': 0.030951471150171952, 'mean_word_length': 2.561224489795918, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 30, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3910561', 'n_tokens_mistral': 2038, 'n_tokens_neox': 1897, 'n_words': 718}