Find key that matches value in zsh associative array? - zsh

In a regular array, I can use (i) or (I) to search for the index of entries matching a given value (first match from the start or end of the array, respectively):
list=(foo bar baz)
echo $list[(i)bar]
# => 2
This doesn't work for associative arrays, to get (one of) the key(s) where a value is found:
declare -A hash=([foo]=bar [baz]=zoo)
echo $hash[(i)bar]
# => no output
Is there another mechanism for doing this, other than manually looping through?

The (r) subscript flag combined with the (k) parameter flag should give you
what you want:
declare -A hash=([foo]=bar [baz]=zoo)
echo ${(k)hash[(r)bar]}
# => foo
The man page section on the (r) subscript flag only talks about returning
values and ignores this usage, so it's hard to find.

Here is something completely disgusting:
% declare -A hash=([foo]=bar [baz]=zoo)
% echo ${${(kA)hash}[${${(A)hash[#]}[(i)bar]}]}
foo
Basically, it consists of two parts:
${${(A)hash[#]}[(i)bar]}, which computes the index of bar in an anonymous array consisting of the values of the associative array.
${${(kA)hash}[...]}, which indexes the anonymous array consisting of the keys of the associative array using the numerical index computed by the previous expansion.
I'm not aware of a short equivalent to the I flag, and I too am surprised that the seemingly obvious extension to associative arrays doesn't exist.

Related

Why jq list contructor and object constructor behaves differently for generators?

I want to get the Cartesian product of 2 lists, and tried following way:
echo [[1,2,3],[4,5,6]] | jq '[.[0][],.[1][]]'
I expected to get [[1,4],[1,5],[1,6],[2,4],[2,5],....], but what I really get is [1,2,3,4,5,6]
But using the following 2 commands, I get the Cartesian product output,
echo [[1,2,3],[4,5,6]] | jq '(.[0][] | tostring) + "," + (.[1][] | tostring)'
echo [[1,2,3],[4,5,6]] | jq '{"x": .[0][], "y": .[1][]}'
My questions is: why does comma behave differently from "+"? why does list constructor behave differently from object constructor?
Because thats how the , operator in jq works. When two filters are separated by , then the same input will be fed into both and the output value streams will be concatenated in order. In your case, both the filters return the corresponding array elements at indices 0 and 1 respectively and the result is collected into an array.
As for Object construction, the manual makes it clear that,
If one of the expressions produces multiple results, multiple dictionaries will be produced.
On an input
{"user":"stedolan","titles":["JQ Primer", "More JQ"]}
the expression
{user, title: .titles[]}
produces two outputs, one for each value in the array titles
{"user":"stedolan", "title": "JQ Primer"}
{"user":"stedolan", "title": "More JQ"}
So going back to your original attempts,
In the first case, each expression runs on the original input array separately and the results are just combined together into the bigger one
The third case falls into the case of object creation, when one of the expression produces multiple results. Since both your expressions generate multiple results, your result dictionary is formed as cartesian product of the two arrays
The second case is almost similar to 3), but the operations happen over string types i.e. each integer type converted to string before the cartesian product is generated. Note that this produces a result that is neither a list or a dictionary
You can still generate cartesian product on the arrays, when you do this
.[0][] as $x | .[1][] as $y | [$x,$y]
and put the whole thing into an array if you put the above filter inside [..] as
[.[0][] as $x | .[1][] as $y | [$x,$y]]
The reason this works, is because of Variable binding operator with the syntax expression as $variable.
The expression exp as $x | ... means for each value of expression exp, run the rest of the pipeline with the entire original input, and with $x set to that value. Thus as functions as something of a foreach loop. So with our example, for each value in $x and each value in $y, we form the result [$x, $y], which will be the resulting cartesian product.

filename expansion on assigning a non-array variable

This is about Zsh 5.5.1.
Say I have a glob pattern which expands to exactly one file, and I would like to assign this file to a variable. This works:
# N: No error if no files match. D: Match dot files. Y1: Expand to exactly one entry.
myfile=(*(NDY1))
and echo $myfile will show the file (or directory). But this one does not work:
myfile=*(NDY1)
In the latter case, echo $myfile holds the pattern, i.e. *(NDY1).
Of course I could do some cheap trick, such as creating a chilprocess via
myfile=$(echo *(NDY1))
but is there a way to do the assinment without such tricks?
By default, zsh does not do filename expansion in scalar assignment, but the option GLOB_ASSIGN could help. (This option is provided as for backwards compatibility only.)
local myfile=''
() {
setopt localoptions globassign
myfile=*(NDY1)
}
echo $myfile
;#>> something
Here are some descriptions in zsh docs:
The value of a scalar parameter may also be assigned by writing:
name=value
In scalar assignment, value is expanded as a single string, in which the elements of arrays are joined together; filename expansion is not performed unless the option GLOB_ASSIGN is set.
--- zshparam(1), Description, zsh parameters
GLOB_ASSIGN <C>
If this option is set, filename generation (globbing) is performed on the right hand side of scalar parameter assignments of the form 'name=pattern (e.g. foo=*'). If the result has more than one word the parameter will become an array with those words as arguments. This option is provided for backwards compatibility only: globbing is always performed on the right hand side of array assignments of the form name=(value) (e.g. foo=(*)) and this form is recommended for clarity; with this option set, it is not possible to predict whether the result will be an array or a scalar.
--- zshoptions(1), GLOB_ASSIGN, Expansion and Globbing, Description Of Options, zsh options

Filtering zsh array by wildcard

Given a Zsh array myarray, I can make out of it a subset array
set -A subarray
for el in $myarray
do
if [[ $el =~ *x*y* ]]
then
subarray+=($el)
fi
done
which, in this example, contains all elements from myarray which have somewhere an x and an y, in that order.
Question:
Given the plethora of array operations available in zsh, is there an easier way to achieve this? I checked the man page and the zsh-lovers page, but couldn't find anything suitable.
This should do the trick
subarray=(${(M)myarray:#*x*y*z})
You can find the explanation in the [section about Parameter Expansion] in the zsh manpage. It is a bit hidden as ${name:#pattern} without the flag (M) does the opposite of what you want:
${name:#pattern}
If the pattern matches the value of name, then substitute the empty string; otherwise, just substitute the value of name. If name is an array the matching array elements are removed (use the (M) flag to remove the non-matched elements).

pl/pgsql merging or combining arrays

How to merge/combine arrays in pl/pgsql?
For example I have an 3 arrays: {1,2,3}, {"a","b","c"}, and {32,43,23}
After merging I need to get:
{{1,"a",32}, {2,"b",43}, {3,"c",23}}
My version of PostgreSQL is 9.0
It sounds like you want an n-argument zip function, as found in some functional languages and languages with functional extensions.
In this case you can't do exactly what yu want, because those arrays are of hetrogeneous types. PostgreSQL arrays must be of homogenous type, so this won't work. The desired result you show is an invalid array.
You could create an array of ROWs (anonymous records), or cast all the values to text.
For example:
SELECT array_agg(ROW(a,b,c))
FROM (
SELECT
unnest('{1,2,3}'::integer[]),
unnest('{"a","b","c"}'::text[]),
unnest('{32,43,23}'::integer[])
)
x(a,b,c);
will produce:
{"(1,a,32)","(2,b,43)","(3,c,23)"}
which is an array of three rowtypes cast to text. It will be awkward to work with because Pg has only very limited support for anonymous records; most importantly in this case you cannot cast a text value to RECORD(integer,text,integer), you must actually CREATE TYPE and cast to the defined type.
Because of that limitation, you may instead want to cast all the values to text and use a two-dimensional array of text. You'd expect to be able to do that with a simple array_agg, but frustratingly this fails:
SELECT array_agg(ARRAY[a::text,b,c::text])
FROM (
SELECT
unnest('{1,2,3}'::integer[]),
unnest('{"a","b","c"}'::text[]),
unnest('{32,43,23}'::integer[])
)
x(a,b,c);
producing:
ERROR: could not find array type for data type text[]
because array_agg doesn't support arrays as input. You need to define another variant of array_agg that takes a text[] input. I wrote one a while ago but can't find it now; I'll try to locate it and update if I find it. In the mean time you can work around it by casting the inner array to text:
SELECT array_agg(ARRAY[a::text,b,c::text]::text)
FROM (
SELECT
unnest('{1,2,3}'::integer[]),
unnest('{"a","b","c"}'::text[]),
unnest('{32,43,23}'::integer[])
)
x(a,b,c);
producing output like:
{"{1,a,32}","{2,b,43}","{3,c,23}"}
... OK, I haven't found the one I wrote, but here's an example from Erwin that does the job fine. Try this:
CREATE AGGREGATE array_agg_mult (anyarray) (
SFUNC = array_cat
,STYPE = anyarray
,INITCOND = '{}'
);
SELECT array_agg_mult(ARRAY[ARRAY[a::text,b,c::text]])
FROM (
SELECT
unnest('{1,2,3}'::integer[]),
unnest('{"a","b","c"}'::text[]),
unnest('{32,43,23}'::integer[])
)
x(a,b,c);
Output:
{{1,a,32},{2,b,43},{3,c,23}}

Flex Associative Array

User inputs a comma separated string and i d like to make a an associative array out of it as follows :
Input : 4,3,3,2,2
Output : Array{"0"=>4,"1"="3","2"=>3,"3"=>2,"4"=>2}
I can create an array by input.text.split(",");
But I d like to make it an associative array as above, how to do this?
Thanks.
Well, besides the fact that an array like the one you've got there is already 'associative' -- associated with numbers starting at 0. So:
yourArray[0] // will be 4
yourArray[3] // will be 2
If you want to associate with something else -- like strings -- then you might want to look into the Dictionary class.

Resources