Maths on values of properties that may not exist - gremlin

Here is a query (gremlin-python; tinkerpop v3.3.3) that inserts a node with properties 'positive' and 'negative', then subtracts one from the other and outputs the result:
g.withSack(0).addV('test').property('positive', 2).property('negative', 3) \
.sack(assign).by('positive') \
.sack(minus).by('negative') \
.sack().next()
Out[13]: -1
Trying the same query except with one of those properties missing produces an error:
g.withSack(0).addV('test').property('positive', 2) \
.sack(assign).by('positive') \
.sack(minus).by('negative') \
.sack().next()
Out[13]: ... GremlinServerError: 500: The property does not exist as the key has no associated value for the provided element: v[36]:negative
I can get around this by coalescing the four possible cases:
g.withSack(0).addV('test').property('positive', 2) \
.coalesce(has('positive').has('negative').sack(assign).by('positive').sack(minus).by('negative').sack(), \
has('positive').values('positive'), \
has('negative').sack(minus).by('negative').sack(), \
sack() \
).next()
It sure is ugly though - is there a neater solution? Ideally there would be the possibility to insert a default value in the absence of a property. I've tried using the 'math' step as well, but it's only slightly neater and doesn't avoid the problem of non-existent properties. To be clear, In the case of multiple traversers, I want a result for each traverser.

I think if you do math() or sack() to solve this, you should probably consider going with the idea of having "required" properties on these vertices on which you intend to do these calculations. That should make things much easier. I do feel like math() would be neater, though you said otherwise:
g.V().as('a').out('knows').as('b').
math("a - b").
by(coalesce(values('hasSomeValue'), constant(0))).
by(coalesce(values('missingValue'), constant(0)))
That's pretty straightforward though perhaps your examples were meant for simplicity and you have a lot more complexity to consider.
I suppose Gremlin could be changed to allow for a second parameter in the by() as some default if the first traversal did not return anything, thus:
g.V().as('a').out('knows').as('b').
math("a - b").
by(values('hasSomeValue'), constant(0)).
by(values('missingValue'), constant(0))
Saves some typing I'd suppose, but I'm not sure that it is as clear to read as with the use of coalesce(). I think I like the explicit use of coalesce() better.

Related

group_by behavior when using --stream

Having (simplified for learning) input file:
{"type":"a","id":"1"}
{"type":"a","id":"2"}
{"type":"b","id":"1"}
{"type":"c","id":"3"}
I'd like to turn it into:
{
"a": [1,2],
"b": [1],
"c": [3]
}
via using --stream option, not needed here, just for learning. Or at least it does not seem that viable to use group_by or reduce without it on bigger files (even few G seems to be rather slow)
I understand that I can write smth like:
jq --stream -cn 'reduce (inputs|select(length==2)) as $i([]; . + ..... )' test3
but that would just process the data per line(processed item in stream), ie I can either see type or id, and this does not have place where to create pairing. I can cram it to one big array, but that opposite of what I have to do.
How to create such pairings? I don't even know how to create(using --stream):
{"a":1}
{"a":2}
...
I know both (first target transformation, and the one above this paragraph) are probably some trivial usage of for each, I have some working example of one here, but all it's .accumulator and .complete keywords(IIUC) are now just magic. I understood it once, but ... Sorry for trivial questions.
UPDATE regarding performace:
#pmf provided in his answer 2 solutions: streaming and non streaming. Thanks for that, I was able to write non-streaming version, but not the streaming one. But when testing it, the streaming variant was (I'm not 100% sure now, but ...) 2-4 times slower. Makes sense if data does not fit into memory, but luckily in my case, they do. So I ran the non streaming version for ~1G file on laptop, but not actually that slow i7-9850H CPU # 2.60GHz. For my surprise it wasn't done withing 16hours so I killed it as not viable solution for my usecase of potentially a lot bigger input files. Considering simplicity of input, I decided to write pipeline just via using bash, grep,sed,paste and tr, and eventhough it was using some regexes, and was overally inefficient as hell, and without any parallelism, the whole file was correctly crunched in 55 seconds. I understand that character manipulation is faster than parsing json, but that much difference? Isn't there some better approach while still parsing json? I don't mind spending more cpu power, but if I'm using jq, I'd like to use it's functions and process json as json, not just chars just as I did it with bash.
In the "unstreamed" case I`d use
jq -n 'reduce inputs as $i ({}; .[$i.type] += [$i.id | tonumber])'
Demo
With the --stream option set, just re-create the streamed items using fromstream:
jq --stream -n 'reduce fromstream(inputs) as $i ({}; .[$i.type] += [$i.id | tonumber])'
{
"a": [1,2],
"b": [1],
"c": [3]
}

How can I determine why this `test` does not match for tmux's session_name?

I'm trying to give my tmux panes individual titles. Since there is nothing built into tmux to assign titles, I'm using a function that will receive various properties of the pane and then lookup the title that I want based on those properties, and echo it out.
However, the test inside the function is not working as expected. Even when the session_name "portal" is passed in, it does not match the string "portal", even though the output is always exactly "portal".
I've removed all irrelevant code from the function to show just exactly the failing match:
tmux_pane_title() {
local session_name=$1
# ...
if [[ "$session_name" = "portal" ]] && echo ".${session_name}." || echo "-${session_name}-"
# ...
}
tmux set pane-border-format "#P: `tmux_pane_title \"#{session_name}\" \"#{pane_current_command}\" \"#{pane_current_path}\"` "
It always echos out "-portal-", showing the $1 is in fact "portal", but it does not match "portal" in the test.
I have tried using sed to remove newlines, but it made no difference.
However if I hard-code "portal" into the tmux format for pane-border-format it will suddenly work, suggesting there's some weird control character hidden in the name preventing it from working when I pass in session_name
tmux set pane-border-format "#P: `tmux_pane_title \"portal\" \"#{pane_current_command}\" \"#{pane_current_path}\"` "
If that's the case, how can I find and eliminate the control character? (And why would it be there? I did not enter anything weird into my tmuxinator.yml file for the session name.)
I've already tried removing control characters like this:
local session_name=$(echo $1 | tr -d "[:cntrl:]")
If that's not the case, how can I figure out what is breaking this function?
P.S. I'm on tmux 3.1b.
This is NOT an answer to the question, but it is a step in the direction of a solution to the problem that led me to ask the question. If you have the same problem, this may be helpful.
Although I'm still interested in solving the mystery related to the failing test, I found a way to set my pane titles more easily1.
There is a pane_title property that can be used in your pane-border-format:
tmux set pane-border-format "#{pane_title}"
If you set this format, then you can set the title with printf and escape sequences:
printf '\033]2;%s\033\\' 'your desired title'
(I had read about the printf technique elsewhere, both on and off SO, but it fails if you don't have the proper format including pane_title. No where else did I see these two things mentioned together. Without the combination, it fails. Assuming that the default format is set is not a safe assumption.)
A complete answer is still useful, so I could do things like this:
tmux set pane-border-format " #P: #{?pane_title,#{pane_title},`tmux_pane_title \"#{session_name}\" \"#{pane_current_command}\" \"#{pane_current_path}\"`} "
That would choose an explicit title if one were set, and default to the function to choose one. So please don't close this question as a duplicate of others that relate to setting tmux pane titles. IT IS DIFFERENT.
1In other words, I solved the problem but I didn't answer the question. Many people think SO is a problem-solving site, but it describes itself as a question & answer site. I doubt if many people have given this distinction much thought, but they are very different things.

How to combine two Vim commands into one (command not keybinding)

I've found few Stack Overflow questions talking about this, but they are all regarding only the :nmap or :noremap commands.
I want a command, not just a keybinding. Is there any way to accomplish this?
Use-case:
When I run :make, I doesn't saves automatically. So I'd like to combine :make and :w. I'd like to create a command :Compile/:C or :Wmake to achieve this.
The general information about concatenating Ex command via | can be found at :help cmdline-lines.
You can apply this for interactive commands, in mappings, and in custom commands as well.
Note that you only need to use the special <bar> in mappings (to avoid to prematurely conclude the mapping definition and execute the remainder immediately, a frequent beginner's mistake: :nnoremap <F1> :write | echo "This causes an error during Vim startup!"<CR>). For custom commands, you can just write |, but keep in mind which commands see this as their argument themselves.
:help line-continuation will help with overly long command definitions. Moving multiple commands into a separate :help :function can help, too (but note that this subtly changes the error handling).
arguments
If you want to pass custom command-line arguments, you can add -nargs=* to your :command definition and then specify the insertion point on the right-hand side via <args>. For example, to allow commands to your :write command, you could use
:command -nargs=* C w <args> | silent make | redraw!
You can combine commands with |, see help for :bar:
command! C update | silent make | redraw!
However, there is a cleaner way to achieve what you want.
Just enable the 'autowrite' option to automatically write
modified files before a :make:
'autowrite' 'aw' 'noautowrite' 'noaw'
'autowrite' 'aw' boolean (default off)
global
Write the contents of the file, if it has been modified, on each
:next, :rewind, :last, :first, :previous, :stop, :suspend, :tag, :!,
:make, CTRL-] and CTRL-^ command; and when a :buffer, CTRL-O, CTRL-I,
'{A-Z0-9}, or `{A-Z0-9} command takes one to another file.
Note that for some commands the 'autowrite' option is not used, see
'autowriteall' for that.
This option is mentioned in the help for :make.
I have found a solution after a bit of trial and error.
Solution for my usecase
command C w <bar> silent make <bar> redraw!
This is for compiling using make and it prints output only if there is nonzero output.
General solution
command COMMAND_NAME COMMAND_TO_RUN
Where COMMAND_TO_RUN can be constructed using more than one command using the following construct.
COMMAND_1_THAN_2 = COMMAND_1 <bar> COMMAND_2
You can use this multiple times and It is very similar to pipes in shell.

zsh: using "less -R" as READNULLCMD

Now, I'm pretty sure of the limitation here. But let's step back.
The simple statement
READNULLCMD="less -R"
doesn't work, generating the following error:
$ <basic.tex
zsh: command not found: less -R
OK. Pretty sure this is because, by default, zsh doesn't split string variables at every space. Wherever zsh is using this variable, it's using $READNULLCMD where it should be using ${=READNULLCMD}, to ensure the option argument is properly separated from the command by a normal space. See this discussion from way back in 1996(!):
http://www.zsh.org/mla/users/1996/msg00299.html
So, what's the best way around this, without setting SH_WORD_SPLIT (which I don't want 99% of the time)?
So far, my best idea is assigning READNULLCMD to a simple zsh script which just calls "less -R" on STDIN. e.g.
#!/opt/local/bin/zsh
less -R /dev/stdin
Unfortunately this seems to be a non-starter as less used in this fashion for some reason misses the first few lines on input from /dev/stdin.
Anybody have any better ideas?
The problem is not that less doesn't read its environment variables (LESS or LESSOPEN). The problem is that the READNULLCMD is not invoked as you might think.
<foo
does not translate into
less $LESS foo
but rather to something like
cat foo | less $LESS
or, perhaps
cat foo $LESSOPEN | less $LESS
I guess that you (like me) want to use -R to obtain syntax coloring (by using src-hilite-lesspipe.sh in LESSOPEN, which in turn uses the "source-highlight" utility). The problem with the latter two styles of invocation is that src-hilite-lesspipe.sh (embedded in $LESSOPEN) will not receive a filename, and hence it will not be able to deduce the file type (via the --infer-lang option to "source-highligt"). Without a filename suffix, "source-highlight" will revert to "no highlighting".
You can obtain syntax coloring in READNULLCMD, but in a rather useless way. This by specifying the language explicitly via the --lang-def option. However, you'll have as little clue as "source-higlight", since the there's no file name when the data is passed anonymously through the pipe. Maybe there's a way to do a on-the-fly heuristic parser and deduce it by contents, but then you've for sure left this little exercise.
export LESS=… may be a good solution exclusively for less and if you want such behavior the default in all cases, but if you want more generic one then you can use functions:
function _-readnullcmd()
{
less -R
}
READNULLCMD=_-readnullcmd
(_- or readnullcmd have no special meaning just the former never appears in any distributed zsh script and the latter indicates the purpose of the function).
Set the $LESS env var to the options you always want to have in less.
So don't touch READNULLCMD and use export LESS="R" (and other options you want) in your zshrc.

zsh make **/*.cpp **/*.cxx **/*.hpp not result in error

I have "v" aliased to "vim **/*.cpp **/*.hpp **/*.cxx"
Problem is, if I'm in a directory without any *.cxx files, zsh treats this as an error. Is there anyway to tell zsh to create the absence of **/*.cxx files as "" instead of an error?
It sounds like you want:
set -o NULL_GLOB
Another variation that may be of interest is:
set -o CSH_NULL_GLOB
They work slightly different when all the patterns fail to expand. When at least one pattern successfully expands, the two are the same. But if none of the patterns expand, NULL_GLOB will still run the command while CSH_NULL_GLOB will return an error.

Resources