In this StackOverFlow question, I created an employee database and provided a select-by-first function. How can I write a select-by-first-pattern where the record is returned if any part of the string/symbol matches?
(select-by-first-pattern 'ev); Returns all records containing "ev" (eg. Steve)
Here's the code needed to construct the database: (UPDATED: Included suggested solution)
(require 'cl)
(defvar *emp-db* nil)
(defun add-record (emp) (push emp *emp-db*))
(defstruct employee age first-name last-name sex children)
(add-record (make-employee))
(add-record (make-employee :age 34
:last-name 'farquharson
:first-name 'alice
:sex 'female))
(add-record (make-employee :age 43
:last-name 'jobs
:first-name 'steve
:sex 'male))
(add-record (make-employee :age 53
:last-name 'ballmer
:first-name 'steve
:sex 'male))
(defun select-by-first (first-name)
(remove-if-not
#'(lambda (employee)
(equal (employee-first-name employee) first-name))
*emp-db*))
;; ---------- Answer below ----------
;;
(defun select-by-first-pattern (first-name)
(remove-if-not
#'(lambda (employee)
(if (string-match first-name (symbol-name (employee-first-name employee))) t nil))
*emp-db*))
(print (select-by-first-pattern "al")); Returns alice's record
(print (select-by-first-pattern "ev")); Returns records of the two Steve's
(print (select-by-first-pattern "ee")); Returns nil
If you want to do partial or pattern matches, you really should be using strings. I don't have any experience with Common Lisp proper, but Emacs has a wealth of regex-matching functions.
If you really are stuck with symbols as inputs, you could use symbol-name (in Elisp at least) to get the name of the symbol as a string. Either way, you're going to end up comparing strings, so you might as well use them to begin with.
Use SYMBOL-NAME to convert a symbol to a string, by preserving the original case:
> (symbol-name '|Alice|)
"Alice"
For simple string searches, you can use the SEARCH function:
> (search "li" (symbol-name '|Alice|))
1
> (search "li" (symbol-name '|Dave|))
NIL
For complex pattern matching, probably this library can help.
Related
I'm learning Clojure and enjoying it but find an inconsistency in Records that puzzles me: why doesn't the default map constructor (map->Whatever) check for data integrity when creating a new Record? For instance:
user=> (defrecord Person [first-name last-name])
#<Class#46ffda99 user.Person>
user=> (map->Person {:first-name "Rich" :last-name "Hickey"})
#user.Person {:first-name "Rich" :last-name "Hickey"}
user=> (map->Person {:first-game "Rich" :last-name "Hickey"})
#user.Person {:first-game "Rich" :first-name nil :last-name "Hickey"}
I believe the Map is not required to define all the fields in the Record definition and it is also allowed to contain extra fields that aren't part of the Record definition. Also I understand that I can define my own constructor which wraps the default constructor and I think a :post condition can then be used to check for correct (and comprehensive) Record creation (have not been successful in getting that to work).
My question is: Is there an idiomatic Clojure way to verify data during Record construction from a Map? And, is there something that I'm missing here about Records?
Thank you.
I think your comprehensiveness requirement is already quite specific, so nothing built-in I know of covers this.
One thing you can do nowadays is use clojure.spec to provide an s/fdef for your constructor function (and then instrument it).
(require '[clojure.spec.alpha :as s]
'[clojure.spec.test.alpha :as stest])
(defrecord Person [first-name last-name])
(s/fdef map->Person
:args (s/cat :map (s/keys :req-un [::first-name ::last-name])))
(stest/instrument `map->Person)
(map->Person {:first-name "Rich", :last-name "Hickey"})
(map->Person {:first-game "Rich", :last-name "Hickey"}) ; now fails
(If specs are defined for ::first-name and ::last-name those will be checked as well.)
Another option is to use Plumatic Schema to create a wrapper "constructor" function specifying the allowed keys. For example:
(def FooBar {(s/required-key :foo) s/Str (s/required-key :bar) s/Keyword})
(s/validate FooBar {:foo "f" :bar :b})
;; {:foo "f" :bar :b}
(s/validate FooBar {:foo :f})
;; RuntimeException: Value does not match schema:
;; {:foo (not (instance? java.lang.String :f)),
;; :bar missing-required-key}
The first line defines a schema that accepts only maps like:
{ :foo "hello" :bar :some-kw }
You wrapper constructor would look something like:
(def NameMap {(s/required-key :first-name) s/Str (s/required-key :last-name) s/Str})
(s/defn safe->person
[name-map :- NameMap]
(map->Person name-map))
or
(s/defn safe->person-2
[name-map]
(assert (= #{:first-name :last-name} (set (keys name-map))))
(map->Person name-map))
Common Lisp allows any lisp object to serve as a hash table key. But what if you only want to use part of an object as the key. For example, in
(defstruct action
(name nil :type symbol)
(parameters nil :type list)
(time 0 :type fixnum)
(database nil :type hash-table))
the time slot is inappropriate for equalp hashing situations. What is a good strategy for accessing a hash table using a (partial) lisp object as key? One approach might use a key like (list (action-name act1) (action-parameters act1) (action-database act1)), but this seems rather inefficient. Another approach might create a substructure to the action defstruct with just the three appropriate slots, and use that substructure as a key, but this seems somewhat ad-hoc just for the purpose of hash table access. Are there other methods that could work better?
I will use Common Lisp library from here, as for example
cl-custom-hash-table
Then going to your code, first when you create an action like this:
CL-USER> (setq action-1 (make-action
:parameters '(1 2 3)
:time 45))
produce this error:
The value NIL is not of the expected type HASH-TABLE.
[Condition of type TYPE-ERROR]
so you need to change your definition to something like this:
CL-USER> (defstruct action
(name nil :type symbol)
(parameters nil :type list)
(time 0 :type fixnum)
(database (make-hash-table) :type hash-table))
ACTION
CL-USER> (setq action-1 (make-action
:parameters '(1 2 3)
:time 45))
#S(ACTION :NAME NIL :PARAMETERS (1 2 3) :TIME 45 :DATABASE #<HASH-TABLE :TEST EQL size 0/60 #x3020012E708D>)
Then you should define a function for equal time or what ever you need as follow:
accesing to the data
CL-USER> (action-time action-1)
45
create another action
CL-USER> (setq action-2 (make-action
:parameters '(1 2 3)
:time 102))
#S(ACTION :NAME NIL :PARAMETERS (1 2 3) :TIME 102 :DATABASE #<HASH-TABLE :TEST EQL size 0/60 #x3020012FE58D>)
create the function for testing
CL-USER> (defun equal-actions-by-time (a1 a2) (= (action-time a1) (action-time a2)))
EQUAL-ACTIONS-BY-TIME
define the hash-function:
CL-USER> (defun hash-action (a) (action-time a))
HASH-ACTION
create your hash
CL-USER> (define-custom-hash-table-constructor make-action-hash :test equal-actions-by-time :hash-function hash-action)
MAKE-ACTION-HASH
CL-USER> (defparameter *foo-hash* (make-action-hash) "variable for stackoverflow")
*FOO-HASH*
try it:
CL-USER> (setf (gethash action-1 *foo-hash*) 1
(gethash action-2 *foo-hash*) 10)
10
CL-USER> (gethash action-1 *foo-hash*)
1
T
You can avoid using a library if the distribution will work in implementations that support custom TEST/HASH functions natively, if not you can use with-custom-hash-table
In the optimus case you can work as follow:
CL-USER> (defparameter *foo-hash* (make-hash-table :test 'equal-actions-by-time :hash-function 'hash-action))
*FOO-HASH*
CL-USER> (setf (gethash action-1 *foo-hash*) 1
(gethash action-2 *foo-hash*) 10)
10
CL-USER> (gethash action-1 *foo-hash*)
1
T
Suppose that I have some font-spec in a variable my-font-spec. E.g.
(setq my-font-spec (font-spec :family "XYZ"
:height 120
:weight 'normal
:width 'normal))
I want to pass the attributes in this font-spec as the &rest arguments to set-face-attribute. IOW, I want to, in effect, invoke
(set-face-attribute some-face nil
:family "XYZ"
:height 120
:weight 'normal
:width 'normal)
but do so without spelling out the attributes (as I've done above), but rather indirectly, through some function of my-font-spec.
I'm not sure why, but calling (font-face-attributes my-font-spec) doesn't return the value of the :height attribute you specified:
(:family "XYZ" :weight normal :width normal)
If it did, you could just call:
(apply 'set-face-attribute some-face nil (font-face-attributes my-font-spec))
But to make sure to get the values of all the specified attributes, you can instead get a full list of attributes using the face-attribute-name-alist variable, retrieve the value of each (if present) from my-font-spec, and then apply them to some-face:
(let (props)
(mapcar #'(lambda (attrval)
(let* ((attr (car attrval))
(prop (font-get my-font-spec attr)))
(if prop
(progn (push prop props)
(push attr props))))) face-attribute-name-alist)
(apply 'set-face-attribute some-face nil props))
class Foo
attr_accessor :a,
:time, # ms since epoch
:b,
:c
end
In text mode, the variables listed after 'a' would indent as written above, but in ruby mode they would instead be flush with 'attr_accessor'. How can I get ruby mode to indent like text mode in this situation? Note that I'd like to be able to select the whole file and hit c-m-\ to get the above indentation in addition to all the other ruby-mode.el indentation rules.
This hack should work in the majority of cases.
(defadvice ruby-indent-line (after line-up-args activate)
(let (indent prev-indent arg-indent)
(save-excursion
(back-to-indentation)
(when (zerop (car (syntax-ppss)))
(setq indent (current-column))
(skip-chars-backward " \t\n")
(when (eq ?, (char-before))
(ruby-backward-sexp)
(back-to-indentation)
(setq prev-indent (current-column))
(skip-syntax-forward "w_.")
(skip-chars-forward " ")
(setq arg-indent (current-column)))))
(when prev-indent
(let ((offset (- (current-column) indent)))
(cond ((< indent prev-indent)
(indent-line-to prev-indent))
((= indent prev-indent)
(indent-line-to arg-indent)))
(when (> offset 0) (forward-char offset))))))
Example:
class Comment < ActiveRecord::Base
after_create :send_email_to_author,
:if => :author_wants_emails?,
:unless => Proc.new { |comment| comment.post.ignore_comments? }
end
From Remi (in comments):
Note that Emacs will correctly indent class Foo attr_accessor(:a, :time, # ms since epoch :b, :c) end – Rémi Dec 11 '10 at 8:50
You can add parens and have it indent properly -- I'm adding this here because I'm looking for unanswered questions, and this one comes up (incorrectly, since it has been answered in the comments).
When using Emacs 24.4 or newer, your example will be indented like this by default.
class Foo
attr_accessor :a,
:time, # ms since epoch
:b,
:c
end
In text mode, the variables listed after 'a' would indent as written above, but in ruby mode they would instead be flush with 'attr_accessor'. How can I get ruby mode to indent like text mode in this situation? Note that I'd like to be able to select the whole file and hit c-m-\ to get the above indentation in addition to all the other ruby-mode.el indentation rules.
This hack should work in the majority of cases.
(defadvice ruby-indent-line (after line-up-args activate)
(let (indent prev-indent arg-indent)
(save-excursion
(back-to-indentation)
(when (zerop (car (syntax-ppss)))
(setq indent (current-column))
(skip-chars-backward " \t\n")
(when (eq ?, (char-before))
(ruby-backward-sexp)
(back-to-indentation)
(setq prev-indent (current-column))
(skip-syntax-forward "w_.")
(skip-chars-forward " ")
(setq arg-indent (current-column)))))
(when prev-indent
(let ((offset (- (current-column) indent)))
(cond ((< indent prev-indent)
(indent-line-to prev-indent))
((= indent prev-indent)
(indent-line-to arg-indent)))
(when (> offset 0) (forward-char offset))))))
Example:
class Comment < ActiveRecord::Base
after_create :send_email_to_author,
:if => :author_wants_emails?,
:unless => Proc.new { |comment| comment.post.ignore_comments? }
end
From Remi (in comments):
Note that Emacs will correctly indent class Foo attr_accessor(:a, :time, # ms since epoch :b, :c) end – Rémi Dec 11 '10 at 8:50
You can add parens and have it indent properly -- I'm adding this here because I'm looking for unanswered questions, and this one comes up (incorrectly, since it has been answered in the comments).
When using Emacs 24.4 or newer, your example will be indented like this by default.