Skip to content

Instantly share code, notes, and snippets.

@ericnormand
Created December 28, 2020 15:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericnormand/3a39e9093692b1ee3dbbb612d739e468 to your computer and use it in GitHub Desktop.
Save ericnormand/3a39e9093692b1ee3dbbb612d739e468 to your computer and use it in GitHub Desktop.
408 - PurelyFunctional.tv Newsletter

Consecutive numbers

Write a function that takes a string of digits. Try to break up the digits into consecutive integers. If you can, return them, otherwise, return nil.

Examples

(consec "121314") ;=> [12 13 14]
(consec "121315") ;=> nil
(consec "444445") ;=> [444 445]
(consec "12") ;=> [1 2]
(consec "1") ; throws error

Thanks to this site for the challenge idea where it is considered Expert in JavaScript.

Please submit your solutions as comments on this gist.

@miner
Copy link

miner commented Dec 30, 2020

I think (consec "1") should return nil, not throw.

@miner
Copy link

miner commented Dec 30, 2020

(require '[clojure.string :as str])

(defn consec [nstr]
  (let [parse-inc (fn [nstr result]
                    (let [target (inc (peek result))
                          tstr (str target)]
                      (when (str/starts-with? nstr tstr)
                        (if (= nstr tstr)
                          (conj result target)
                          (recur (subs nstr (count tstr)) (conj result target))))))]
    (if (str/starts-with? nstr "0")
      (parse-inc (subs nstr 1) [0])
      (some (fn [end] (parse-inc (subs nstr end) [(Long/parseLong (subs nstr 0 end))]))
            (range 1  (inc (quot (count nstr) 2)))))))

Revised to fix bug with input "0123". Revised again to use some instead of (first (keep ...)).

@miner
Copy link

miner commented Dec 31, 2020

A leading "0" caused a problem with my original submission. It's worth testing with inputs like "0123" and "010910". Note that (Long/parseLong "010") returns 10, ignoring the leading zero. Also, (read-string "010") returns 8 as the leading zero is Clojure/Java notation for octal numbers. For this challenge I would not expect to support octal. My interpretation is that a leading zero can only be a single digit zero and all digits are decimal.

@kschltz
Copy link

kschltz commented Dec 31, 2020

(defn iter-str [s digits]
  (loop [first-n (Long/valueOf (subs s 0 digits))
         rem-str (second (re-find (re-pattern (str first-n "(.*)")) s))
         inc-seq [first-n]]
    (if-not (seq rem-str)
      inc-seq
      (let [nxt-n (-> (str "^" (inc first-n)) re-pattern (re-find rem-str))]
        (if-not (seq nxt-n)
          nil
          (recur (Long/valueOf nxt-n)
                 (second (re-find (re-pattern (str nxt-n "(.*)")) rem-str))
                 (conj inc-seq (Long/valueOf nxt-n))))))))

(defn consec [s]
  (let [s-len (-> s count (/ 2) Math/ceil int inc)]
    (->> s-len
         (range 1)
         (reduce (fn [a b]
                   (if a
                     (reduced a)
                     (iter-str s b))) nil))))``

@mauricioszabo
Copy link

A slightly bigger version that brutes-force the process:

(defn- possibilities [a-str]
  (let [str-size (count a-str)]
    (for [slice-size (range 1 (inc (/ str-size 2)))
          :let [number-of-consecs (/ str-size slice-size)
                first-number (-> a-str (subs 0 slice-size) Long/parseLong)]]
      (->> (iterate inc first-number)
           (take number-of-consecs)))))

(defn- find-match [a-str possibility]
  (when-let [how-many (->> possibility
                           (reductions str "")
                           (map-indexed vector)
                           (filter #(-> % second (= a-str)))
                           ffirst)]
    (take how-many possibility)))

(defn consec [a-str]
  (when (-> a-str count (< 2)) 
    (throw (ex-info "String must have at least 2 digits" {:str a-str})))
  (->> a-str
       possibilities
       (map #(find-match a-str %))
       (filter identity)
       first))

@steffan-westcott
Copy link

@miner I also found problems with strings with a leading 0 that also contained 8 or 9 😞 Here's another try, which should also cope with long strings. Here I'm using the str/starts-with? idea used by other answers here, rather than building up an entire string based on the initial number only:

(require '[clojure.string :as str])

(defn consec' [s first-num-len]
  (let [first-num (read-string (subs s 0 first-num-len))]
    (loop [n (inc first-num)
           nums [first-num]
           s' (subs s first-num-len)]
      (if (empty? s')
        nums
        (let [nstr (str n)]
          (when (str/starts-with? s' nstr)
            (recur (inc n) (conj nums n) (subs s' (count nstr)))))))))

(defn consec [s]
  (if (str/starts-with? s "0")
    (consec' s 1)
    (some #(consec' s %) (range 1 (-> s count (quot 2) inc)))))

@sztamas
Copy link

sztamas commented Dec 31, 2020

(defn consecutives? [s fi]
  (loop [s s
         xs (iterate inc fi)]
    (if (blank? s)
      (first xs)
      (when (starts-with? s (str (first xs)))
        (recur (subs s (count (str (first xs)))) (rest xs))))))

(defn consec [s]
  (when-not (re-matches #"\d{2,}" s)
    (throw (IllegalArgumentException. "must be a string of (at least 2) digits")))
  (let [firsts (->> (range (quot (count s) 2))
                    (map (comp #(Integer/parseInt %) (partial subs s 0) inc)))]
    (some #(when-let [last (consecutives? s %)] (vec (range % last)))
          firsts)))

@miner
Copy link

miner commented Dec 31, 2020

@steffan-westcott Good point about input such as "08" potentially being an issue for code using read-string as that would be an illegal octal number. Very large input strings could also cause errors if the code tries to read an integer that is bigger than a long. But I'm not going to support that.

@arthuronunes
Copy link

(require '[clojure.string :as str])

(defn ->sequence [value n]
  (let [initial-number (-> (subs value 0 n) Long/parseLong)]
    (loop [numbers-seq [initial-number]
           remaining-value (subs value n)]
      (if (empty? remaining-value)
        numbers-seq
        (let [last-number (last numbers-seq)
              next-number (inc last-number)
              next-number-str (str next-number)
              number-size (count next-number-str)]
          (when (str/starts-with? remaining-value next-number-str)
            (recur (conj numbers-seq next-number)
                   (subs remaining-value number-size))))))))

(defn consec [value]
  (let [start-with-zero? (str/starts-with? value "0")
        size (count value)
        limit (if start-with-zero? 1 (quot size 2))]
    (when (<= size 1)
      (throw (ex-info "String must be 2 or more characters" {:value value})))
    (loop [n 1]
      (when (<= n limit)
        (let [result (->sequence value n)]
          (if result
            result
            (recur (inc n))))))))

@MilanLempera
Copy link

(defn consec [str]
  (let [str-length (count str)]
    (when (< str-length 2)
      (throw (Exception. "consec requires at least 2 chars in input")))
    (let [max-length (quot str-length 2)
          lengths (range 1 (inc max-length))
          series (for [length lengths]
            (->> str
              (partition length)
              (map #(clojure.string/join "" %))
              (map #(Integer/parseInt %)))
          )
          consec-series (for [serie series
                              :let [snums (partition 2 1 serie)]
                              :when (every? (fn [[a b]] (= (inc a) b)) snums)]
        serie)]
      (first consec-series))))

@charJe
Copy link

charJe commented Feb 4, 2021

I chose to return nil on cases like "1" instead of error.

(defn consec [string]
  (some
   (fn [num]
     (loop [nums (list (Integer/parseInt (subs string 0 num)))
            num-string (subs string num)]
       (let [num (first nums)]
         (cond
           (and (empty? num-string)
                (< 1 (count nums)))
           , (reverse nums)
           :else
           , (let [next-num (Integer/parseInt (subs num-string 0 (count (str (+ 1 num)))))]
               (when (and (count (str (+ 1 num)))
                          (= (+ 1 num)
                             next-num))
                 (recur (cons next-num nums)
                        (subs num-string (count (str (+ 1 num)))))))))))
   (range 1 (+ 1 (quot (count string) 2)))))

@prairie-guy
Copy link

(defn split-by [ds k]                                                                                                                                 
  "(split-by "111213" 2) -> (11 12 13)"                                                                                                               
  (map (comp read-string join) (partition k ds)))

(defn ascending? [ns]                                                                                                                                 
  "(ascending? '(11 12 13)) -> (11 12 13)"                                                                                                            
  (if (= #{1} (set (map - (rest ns ) ns)))                                                                                                            
    ns))

(defn consec? [ds]
  "(consec? "111213") -> [11 12 13]"
  (->> (range 1 (inc (quot (count ds) 2)))
       (map (partial split-by ds))
       (filter ascending?)
       (first)
       vec))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment