What is the idiomatic way to get something like that?
((fn [coll] (function-body)) [:a :b :c :d])
-> [[:a :b][:a :c][:a :d][:b :c][:b :d][:c :d]]
I can only do this in this way.
#(for [i (range (count %))
j (range (inc i) (count %))]
[(nth % i) (nth % j)])
But this is ugly, and at bigger collections very slow. I would like to avoid a loop/recur
Using recursion explicitly ...
(defn pairs [coll]
(if-let [[x & xs] (seq coll)]
(lazy-cat (map #(vector x %) xs) (pairs xs))))
For example,
(pairs [:a :b :c :d])
; ([:a :b] [:a :c] [:a :d] [:b :c] [:b :d] [:c :d])
This is a lazy sequence. You can use vec to pour it into a vector if need be: a decision better left to the client, I think.
Here's a lazy solution.
user> (def data [:a :b :c :d])
#'user/data
user> (defn loopy [data]
(lazy-seq (if (seq data)
(cons (map #(vector (first data) %) (rest data))
(loopy (rest data)))
nil)))
#'user/loopy
user> (apply concat (loopy data))
([:a :b] [:a :c] [:a :d] [:b :c] [:b :d] [:c :d])
this produces a lazy sequence where each element it a lazy sequence of creating a vector of the current element into a vector with each of the rest of the elements. The next segment is generated lazily when this segment is empty by the call to (apply concat ).
This is along the lines of Juan Manuel's answer, I find it a bit more readable using list comprehension for the last part. The anonymous function generates the pairs [(first s) x]l for all the elements in (rest s).
(defn pairs [xs]
(->> xs
(iterate rest)
(take-while seq)
(mapcat #(for [x (rest %)]
[(first %) x]))))
I've found an ugly pipeline that solves the problem. I don't find it much elegant but I haven't been able to beautify it.
(->> [:a :b :c :d]
(iterate rest)
(take-while not-empty)
(mapcat (fn [[f & r]] (map #(vector f %) r)))
vec)
Let's analyze the function in the mapcat when given list [:a :b :c :d]. It computes all vectors with first element :a and second element succesive elements taken from [:b :c :d], that is([:a :b] [:a :c] [:a :d])`.
The first part of the pipeline, constructs the sequence of all the rests od the original vector ([:a :b :c :d](:b :c :d)(:c :d)(:d) () () () ...) which is stopped at the first empty rest.
Then the mapcat plugs this list in the combining function described before, and the vec at the end, converts the sequence into a vector.
This can be more readable by introducing some auxiliar functions:
(defn rests [coll]
(take-while not-empty (iterate rest coll)))
(defn pairfirst [[f & r]]
(map #(vector f %) r))
(defn pairs [coll]
(mapcat pairfirst (rests coll)))
for is already lazy, and filter is too. So one way to do this, if the elements have a natural order, is to produce the cartesian product, and then filter on sort order.
(defn pairs [c]
(->> (for [x c, y c] [x y])
(filter #(apply <= %))))
(def c (range 0 1e7))
(time (nth (pairs c) 0)) ;; ~ 0.07 ms
(time (nth (pairs c) 1e4)) ;; ~ 6 ms
(time (nth (pairs c) 1e7)) ;; ~ 2600 ms
I think your use of nth might be slowing the collection iteration down:
(def pairs2
#(for [i (range (count %))
j (range (inc i) (count %))]
[(nth % i) (nth % j)]))
(def c (range 0 1e7))
(time (nth (pairs2 c) 0)) ;; ~ 960 ms
(time (nth (pairs2 c) 1e4)) ;; ~ 1514 ms
(time (nth (pairs2 c) 1e7)) ;; ~ ¯\_(ツ)_/¯
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 months ago.
Improve this question
I have a table that I'd like to sort by letter grade. The issue is that it sorts letters before letters with symbols. For example, it sorts: A, A+, A-, B, B+, B-, etc., but I would like it to sort A+, A, A-, B+, B, B-, etc. Is there a way to set do this?
As idea you can check last letter of grade and depending on its value add some number to grade
You can add scope to your model
scope :order_by_grade, -> do
sql = <<~SQL
CASE RIGHT(grade, 1)
WHEN '-' THEN grade || 3
WHEN '+' THEN grade || 1
ELSE grade || 2
END
SQL
order(sql)
end
And then apply to your model Work.order_by_grade
As another idea you can define some constant in the model with all variants and use index
And may be better sort from the worst to the best as ASC and from the best to the worst as DESC -- it's your choice
GRADES = %w[A+ A A- B+ ...]
scope :order_by_grade, -> do
sql = 'CASE grade '
GRADES.reverse_each.with_index do |grade, index|
sql << sanitize_sql_array(['WHEN ? THEN ?', grade, index])
end
sql << ' END'
order(sql)
end
And then apply to your model Work.order_by_grade or even Work.order_by_grade.reverse_order
You may write the following.
arr = ["A", "B-", "B+", "A-", "B", "A+"]
order = { '+' => 0, nil => 1, '-' => 2 }
arr.sort_by { |s| [s[0], order[s[1]]] }
#=> ["A+", "A", "A-", "B+", "B", "B-"]
Two arrays are compared for ordering with the method Array#<=>. See especially the third paragraph at the link.
Suppose
s1 = 'A'
s2 = 'A+'
are being compared. For s1 we compute the array
[s[0], order[s[1]]]
#=> ['A', order[nil]]
#=> ['A', 1]
For s2
[s[0], order[s[1]]]
#=> ['A', order['+']]
#=> ['A', 0]
As
['A', 0] <=> ['A', 1]
#=> -1
'A+' is ordered before 'A'.
I tried the following but it threw this error. I wish to only create a new person node if there isnt a person node in the existing database that has the exact same properties.
org.neo4j.driver.exceptions.ClientException: Invalid input 'R': expected
MERGE (n:Person{id: abc.id})
MERGE (m:Place{place:def.id})
MERGE (o:Thing{id:abcd.id})
WITH n,m,o
OPTIONAL MATCH (n) – [:present_at] -> x with n,m,o, collect (distinct x) as known_place
OPTIONAL MATCH (m) – [:is] -> y with n,m,o, collect (distinct y) as known_thing
FOREACH (a in ( CASE WHEN NOT m IN known_place THEN [1] ELSE [] END ) CREATE (n)-[:present_at] ->(m))
FOREACH (a in ( CASE WHEN NOT o IN known_thing THEN [1] ELSE [] END ) CREATE (m)-[:is] ->(o))
That error was caused by a missing | in each of your FOREACH clauses. For example, this would fix that syntax error:
FOREACH (a in ( CASE WHEN NOT m IN known_place THEN [1] ELSE [] END ) | CREATE (n)-[:present_at] ->(m))
FOREACH (a in ( CASE WHEN NOT o IN known_thing THEN [1] ELSE [] END ) | CREATE (m)-[:is] ->(o))
However, your query would still have numerous other syntax errors.
In fact, the entire query could be refactored to be simpler and more efficient:
WITH {id: 123} AS abc, {id: 234} as def, {id: 345} AS abcd
MERGE (n:Person{id: abc.id})
MERGE (m:Place{place: def.id})
MERGE (o:Thing{id: abcd.id})
FOREACH (a in ( CASE WHEN NOT EXISTS((n)–[:present_at]->(m)) THEN [1] END ) | CREATE (n)-[:present_at]->(m))
FOREACH (a in ( CASE WHEN NOT EXISTS((m)–[:is]->(o)) THEN [1] END ) | CREATE (m)-[:is]->(o))
I have a Model M in my rails code. It has a field F which can have 4 values D, J, M and Z
If I use a scope like this, it would sort data by field F alphabetically:
default scope {order (F: :asc)}
I have 2 questions here:
I don't want to sort data alphabetically on F. I would like data to be displayed with this particular order of F instead. I want ALWAYS the records containing value M for field F first, follow by records having value J, D and then Z in this order. How can I achieve this?
Suppose I would like to display records having J first and then sort the rest of the records by alphabetical order of field F, how can I do that?
You can sort with a CASE statement
order("CASE WHEN F = 'M' THEN 0 WHEN F = 'J' THEN 1 WHEN F = 'D' THEN 2 ELSE 3 END")
Alternatively (if it's only "M" that needs to be first and the rest can be alphabetical)
order("CASE WHEN F = 'M' THEN 0 ELSE 1 END, F")
If I have two collections, how might I zip them together?
with [1,2,3] as nums, ['a', 'b', 'c'] as letters
... wat do? ...
return zipped // [{a: 1}, {b: 2}, {c: 3}]
It may not be possible to dynamically assign map keys (e.g., using the items in letters). But this query will return something similar to what you want (using collections instead of maps):
WITH [1,2,3] as nums, ['a', 'b', 'c'] as letters
RETURN EXTRACT(i IN RANGE(0, LENGTH(nums) - 1) | [letters[i], nums[i]]) AS result;
The result is:
[["a",1],["b",2],["c",3]]
Can someone help me that how to use OR operator in where condition in ActiveRecord in Rails.
I want like below,
x=[1,2,3]
y=['a','b','c']
Z.where(:name => y OR :val => x)
Here in table Z we have two fields called name and val. And i need to fetch those record where name in ('a','b','c') OR val in (1,2,3).
You can do this with the String argument to where.
Z.where('name IN (?) OR val IN (?)', y, x)
Using this parameterized format, y and x will be sanitized automatically.