Функционально разбить строку по пробелам, сгруппировать по кавычкам! - PullRequest
7 голосов
/ 02 декабря 2010

Как написать идиоматический функциональный код в Clojure [1], как написать функцию, которая разбивает строку по пробелу, но сохраняет кавычки без изменений?Быстрое решение, конечно, состоит в использовании регулярных выражений, но это должно быть возможно без них.На первый взгляд кажется довольно сложно!Я написал подобное на императивных языках, но мне хотелось бы увидеть, как работает функциональный рекурсивный подход.

Быстрая проверка того, что должна делать наша функция:

"Hello there!"  -> ["Hello", "there!"]
"'A quoted phrase'" -> ["A quoted phrase"]
"'a' 'b' c d" -> ["a", "b", "c", "d"]
"'a b' 'c d'" -> ["a b", "c d"]
"Mid'dle 'quotes do not concern me'" -> ["Mid'dle", "quotes do not concern me"]

Iне возражайте, если интервал между кавычками изменится (так что сначала можно будет использовать простое расщепление по пробелам).

"'lots    of   spacing' there" -> ["lots of spacing", "there"] ;is ok to me

[1] На этот вопрос можно ответить на общем уровне, но я думаю, что функционалподход в Clojure можно легко перевести на Haskell, ML и т. д.

Ответы [ 7 ]

7 голосов
/ 03 декабря 2010

Вот версия, возвращающая ленивый последовательность слов / строк в кавычках:

(defn splitter [s]
  (lazy-seq
   (when-let [c (first s)]
     (cond
      (Character/isSpace c)
      (splitter (rest s))
      (= \' c)
      (let [[w* r*] (split-with #(not= \' %) (rest s))]
        (if (= \' (first r*))
          (cons (apply str w*) (splitter (rest r*)))
          (cons (apply str w*) nil)))
      :else
      (let [[w r] (split-with #(not (Character/isSpace %)) s)]
        (cons (apply str w) (splitter r)))))))

Тестовый прогон:

user> (doseq [x ["Hello there!"
                 "'A quoted phrase'"
                 "'a' 'b' c d"
                 "'a b' 'c d'"
                 "Mid'dle 'quotes do not concern me'"
                 "'lots    of   spacing' there"]]
        (prn (splitter x)))
("Hello" "there!")
("A quoted phrase")
("a" "b" "c" "d")
("a b" "c d")
("Mid'dle" "quotes do not concern me")
("lots    of   spacing" "there")
nil

Если одинарные кавычки во входных данных не совпадают должным образомвсе, начиная с заключительной одинарной кавычки, считается одним словом:

user> (splitter "'asdf")
("asdf")

Обновление: Другая версия в ответ на комментарий edbond, с лучшей обработкой цитатысимволы внутри слов:

(defn splitter [s]
  ((fn step [xys]
     (lazy-seq
      (when-let [c (ffirst xys)]
        (cond
         (Character/isSpace c)
         (step (rest xys))
         (= \' c)
         (let [[w* r*]
               (split-with (fn [[x y]]
                             (or (not= \' x)
                                 (not (or (nil? y)
                                          (Character/isSpace y)))))
                           (rest xys))]
           (if (= \' (ffirst r*))
             (cons (apply str (map first w*)) (step (rest r*)))
             (cons (apply str (map first w*)) nil)))
         :else
         (let [[w r] (split-with (fn [[x y]] (not (Character/isSpace x))) xys)]
           (cons (apply str (map first w)) (step r)))))))
   (partition 2 1 (lazy-cat s [nil]))))

Тестовый прогон:

user> (doseq [x ["Hello there!"
                 "'A quoted phrase'"
                 "'a' 'b' c d"
                 "'a b' 'c d'"
                 "Mid'dle 'quotes do not concern me'"
                 "'lots    of   spacing' there"
                 "Mid'dle 'quotes do no't concern me'"
                 "'asdf"]]
        (prn (splitter x)))
("Hello" "there!")
("A quoted phrase")
("a" "b" "c" "d")
("a b" "c d")
("Mid'dle" "quotes do not concern me")
("lots    of   spacing" "there")
("Mid'dle" "quotes do no't concern me")
("asdf")
nil
5 голосов
/ 02 декабря 2010

Это решение в haskell, но основная идея должна быть применима и в clojure.
Два состояния синтаксического анализатора (внутри или снаружи кавычек) представлены двумя взаимно рекурсивными функциями.

splitq = outside [] . (' ':)

add c res = if null res then [[c]] else map (++[c]) res

outside res xs = case xs of
    ' '  : ' '  : ys -> outside res $ ' ' : ys
    ' '  : '\'' : ys -> res ++ inside [] ys
    ' '  : ys        -> res ++ outside [] ys
    c    : ys        -> outside (add c res) ys
    _                -> res

inside res xs = case xs of
    ' '  : ' ' : ys -> inside res $ ' ' : ys
    '\'' : ' ' : ys -> res ++ outside [] (' ' : ys)
    '\'' : []       -> res
    c    : ys       -> inside (add c res) ys
    _               -> res
3 голосов
/ 03 декабря 2010

Был в состоянии изменить Брайана, чтобы использовать батут, чтобы он не исчерпал пространство стека. Как правило, вместо выполнения их выполняйте функции slurp-word и parse*, а затем измените parse на trampoline

(defn slurp-word [words xs terminator]
  (loop [res "" xs xs]
    (condp = (first xs)
        nil  ;; end of string after this word
      (conj words res)

      terminator ;; end of word
      #(parse* (conj words res) (rest xs))

      ;; else
      (recur (str res (first xs)) (rest xs)))))

(defn parse* [words xs]
  (condp = (first xs)
      nil ;; end of string
    words

    \space  ;; skip leading spaces
    (parse* words (rest xs))

    \' ;; start quoted part
    #(slurp-word words (rest xs) \')

    ;; else slurp until space
    #(slurp-word words xs \space)))

    (defn parse [s]
      (trampoline #(parse* [] s)))


(defn test-parse []
  (doseq [x ["Hello there!"
             "'A quoted phrase'"
             "'a' 'b' c d"
             "'a b' 'c d'"
             "Mid'dle 'quotes do not concern me'"
             "'lots    of   spacing' there"
             (apply str (repeat 30000 "'lots    of   spacing' there"))]]
    (prn (parse x))))
3 голосов
/ 02 декабря 2010

Вот версия Clojure.Это, вероятно, уносит стек для очень больших входов.Регулярное выражение или реальный генератор парсера было бы гораздо более кратким.

(declare parse*)
(defn slurp-word [words xs terminator]
  (loop [res "" xs xs]
    (condp = (first xs)
      nil  ;; end of string after this word
      (conj words res)

      terminator ;; end of word
      (parse* (conj words res) (rest xs))

      ;; else
      (recur (str res (first xs)) (rest xs)))))

(defn parse* [words xs]
  (condp = (first xs)
    nil ;; end of string
    words

    \space  ;; skip leading spaces
    (parse* words (rest xs))

    \' ;; start quoted part
    (slurp-word words (rest xs) \')

    ;; else slurp until space
    (slurp-word words xs \space)))

(defn parse [s]
  (parse* [] s))

Ваш ввод:

user> (doseq [x ["Hello there!"
                 "'A quoted phrase'"
                 "'a' 'b' c d"
                 "'a b' 'c d'"
                 "Mid'dle 'quotes do not concern me'"
                 "'lots    of   spacing' there"]]
        (prn (parse x)))

["Hello" "there!"]
["A quoted phrase"]
["a" "b" "c" "d"]
["a b" "c d"]
["Mid'dle" "quotes do not concern me"]
["lots    of   spacing" "there"]
nil
2 голосов
/ 02 декабря 2010

Например, есть fnparse , который позволяет функционально писать синтаксический анализатор.

1 голос
/ 03 декабря 2010

О, мои ответы, похоже, превосходят мои теперь, когда я успешно сдал тесты. Во всяком случае, я публикую его здесь, чтобы попросить несколько комментариев по поводу идиоматизации кода.

Я набросал псевдо-хаскель:

pl p w:ws = | if w:ws empty
               => p
            | if w begins with a quote
               => pli p w:ws
            | otherwise
               => pl (p ++ w) ws

pli p w:ws = | if w:ws empty
                => p
             | if w begins with a quote
                => pli (p ++ w) ws
             | if w ends with a quote
                => pl (init p ++ (tail p ++ w)) ws
             | otherwise
                => pli (init p ++ (tail p ++ w)) ws

Хорошо, плохо назван. Там

  • Функция pl обрабатывает слова , а не в кавычках
  • Функция pli (я как во внутреннем) обрабатывает цитируемые фразы
  • Параметр (список) p - это уже обработанная (выполненная) информация
  • Параметр (список) w:ws - информация для обработки

Я перевел псевдо таким образом:

(def quote-chars '(\" \')) ;'

; rewrite .startsWith and .endsWith to support multiple choices
(defn- starts-with?
  "See if given string begins with selected characters."
  [word choices]
  (some #(.startsWith word (str %)) choices))

(defn- ends-with?
  "See if given string ends with selected characters."
  [word choices]
  (some #(.endsWith word (str %)) choices))

(declare pli)
(defn- pl [p w:ws]
    (let [w (first w:ws)
          ws (rest w:ws)]
     (cond
        (nil? w)
            p
        (starts-with? w quote-chars)
            #(pli p w:ws)
        true
            #(pl (concat p [w]) ws))))

(defn- pli [p w:ws]
    (let [w (first w:ws)
          ws (rest w:ws)]
     (cond
        (nil? w)
            p
        (starts-with? w quote-chars)
            #(pli (concat p [w]) ws)
        (ends-with? w quote-chars)
            #(pl (concat 
                  (drop-last p)
                  [(str (last p) " " w)])
                ws)
        true
            #(pli (concat 
                  (drop-last p)
                  [(str (last p) " " w)])
                ws))))

(defn split-line
    "Split a line by spaces, leave quoted groups intact."
    [input]
    (let [splt (.split input " +")]
        (map strip-input 
            (trampoline pl [] splt))))

Не очень Clojuresque, подробности. Также я полагаюсь на регулярное выражение в разбиении и удалении кавычек, поэтому из-за этого я должен заслужить некоторые отрицательные результаты.

1 голос
/ 02 декабря 2010

Используйте регулярное выражение:

 (defn my-split [string]
  (let [criterion " +(?=([^']*'[^']*')*[^']*$)"]
   (for [s (into [] (.split string criterion))] (.replace s "'" ""))))

Первый символ в регулярном выражении - это символ, по которому вы хотите разбить строку - здесь это как минимум один пробел.

А если вы хотите изменить символ цитирования, просто поменяйте каждый символ на что-то другое, например /".

РЕДАКТИРОВАТЬ: Я только что увидел, что вы явно упомянули, что вы не хотите использовать регулярное выражение. Извините!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...