Skip to main content
added 1396 characters in body
Source Link
Stéphane Chazelas
  • 586.9k
  • 96
  • 1.1k
  • 1.7k

The more patterns you have, the more pattern matchings must be performed. Even with ;;, patterns are matched in turn one after the other; the difference is that it stops after the first match, but for a given value of $var, you can end up doing as many matches as with ;;&/&| like when none matches or only the last on matches.

If it's only about fixed strings, you can make it more efficient by doing hash table lookups.

Which for each $var, does one hash table lookup instead of 6 pattern matches.

It's essentially constructing the same kind of case construct as in Ed's answer except it uses a more efficient hash lookup.

Another (micro-)optimisation could be to avoid the evaluation of the actions during the for var loop by constructing functions for each value of that associative array whose code will be evaluated once ahead of time instead of at each iteration of the $lots_of_values:

typeset -A actions
IFS=, n=0
not_found() print -ru2 No action registered for $var

for values action (
  1         'action 1'
  2         'action 2'
  1,3       'action 3'
  4         'action 4'
  1,5       'action 5'
  1,2,3,4,5 'action 6'
) for value ($=values)
  functions[${actions[$value]=f$((++n))}]+=$'\n'$action

for var ($lots_of_values) ${actions[$var]-not_found} "$@"

Here using the special $functions special associative that maps function names to their body.

We'll have two hash lookups per iteration, one for the lookup of the $actions which results in a function name (f1 to f5) and then a lookup of the function based on that name which is an internal hash lookup by the shell, then the pre-compiled code of the action is run.

The more patterns you have, the more pattern matchings must be performed. If it's only about fixed strings, you can make it more efficient by doing hash table lookups.

Which for each $var, does one hash table lookup instead of 6 pattern matches.

The more patterns you have, the more pattern matchings must be performed. Even with ;;, patterns are matched in turn one after the other; the difference is that it stops after the first match, but for a given value of $var, you can end up doing as many matches as with ;;&/&| like when none matches or only the last on matches.

If it's only about fixed strings, you can make it more efficient by doing hash table lookups.

Which for each $var, does one hash table lookup instead of 6 pattern matches.

It's essentially constructing the same kind of case construct as in Ed's answer except it uses a more efficient hash lookup.

Another (micro-)optimisation could be to avoid the evaluation of the actions during the for var loop by constructing functions for each value of that associative array whose code will be evaluated once ahead of time instead of at each iteration of the $lots_of_values:

typeset -A actions
IFS=, n=0
not_found() print -ru2 No action registered for $var

for values action (
  1         'action 1'
  2         'action 2'
  1,3       'action 3'
  4         'action 4'
  1,5       'action 5'
  1,2,3,4,5 'action 6'
) for value ($=values)
  functions[${actions[$value]=f$((++n))}]+=$'\n'$action

for var ($lots_of_values) ${actions[$var]-not_found} "$@"

Here using the special $functions special associative that maps function names to their body.

We'll have two hash lookups per iteration, one for the lookup of the $actions which results in a function name (f1 to f5) and then a lookup of the function based on that name which is an internal hash lookup by the shell, then the pre-compiled code of the action is run.

deleted 12 characters in body
Source Link
Stéphane Chazelas
  • 586.9k
  • 96
  • 1.1k
  • 1.7k

Yes, when using bash's ;;& (added in 2008, alongside ksh's &;), equivalent to zsh's ;| (added in 2007; ksh's ;& added in 1997) for subsequent patterns to still be tested even if the current one matched, you generally want to add it after all patterns, which ensures all patterns are tested.

Then:

# bash
case $var in
  (A) action1;;&
  (B) action2;;&
esac
# zsh
case $var in
  (A) action1;|
  (B) action2;|
esac

(strictly speaking, like for ;; in a standard case statement, the last one before esac is not necessary; a newline or ; is enough).

Becomes the same as ksh-style:

# ksh / bash / zsh / yash
if [[ $var = A ]]; then
  action1
fi
if [[ $var = B ]]; then
  action2
fi

In zsh, you could also do:

for pattern action (
  A 'action1'
  B 'action2'
)  if [[ $var = $~pattern ]] eval -- $action

The more patterns you have, the more pattern matchings must be performed. If it's only about fixed strings, you can make it more efficient by doing hash table lookups.

Taking your example, still in zsh:

typeset -A actions
IFS=,
for values action (
  1         'action 1'
  2         'action 2'
  1,3       'action 3'
  4         'action 4'
  1,5       'action 5'
  1,2,3,4,5 'action 6'
) for value ($=values) actions[$value]+=$action$'\n'

# then it's just a matter of:
for var ($lots_of_values) eval -- $actions[$var]

Which for each $var, does one hash table lookup instead of 6 pattern matches.

Yes, when using bash's ;;&, equivalent to zsh's ;| for subsequent patterns to still be tested even if the current one matched, you generally want to add it after all patterns, which ensures all patterns are tested.

Then:

# bash
case $var in
  (A) action1;;&
  (B) action2;;&
esac
# zsh
case $var in
  (A) action1;|
  (B) action2;|
esac

Becomes the same as ksh-style:

# ksh / bash / zsh / yash
if [[ $var = A ]]; then
  action1
fi
if [[ $var = B ]]; then
  action2
fi

In zsh, you could also do:

for pattern action (
  A 'action1'
  B 'action2'
)  if [[ $var = $~pattern ]] eval -- $action

The more patterns you have, the more pattern matchings must be performed. If it's only about fixed strings, you can make it more efficient by doing hash table lookups.

Taking your example, still in zsh:

typeset -A actions
IFS=,
for values action (
  1         'action 1'
  2         'action 2'
  1,3       'action 3'
  4         'action 4'
  1,5       'action 5'
  1,2,3,4,5 'action 6'
) for value ($=values) actions[$value]+=$action$'\n'

# then it's just a matter of:
for var ($lots_of_values) eval -- $actions[$var]

Which for each $var, does one hash table lookup instead of 6 pattern matches.

Yes, when using bash's ;;& (added in 2008, alongside ksh's &;), equivalent to zsh's ;| (added in 2007; ksh's ;& added in 1997) for subsequent patterns to still be tested even if the current one matched, you generally want to add it after all patterns, which ensures all patterns are tested.

Then:

# bash
case $var in
  (A) action1;;&
  (B) action2;;&
esac
# zsh
case $var in
  (A) action1;|
  (B) action2;|
esac

(strictly speaking, like for ;; in a standard case statement, the last one before esac is not necessary; a newline or ; is enough).

Becomes the same as ksh-style:

# ksh / bash / zsh / yash
if [[ $var = A ]]; then
  action1
fi
if [[ $var = B ]]; then
  action2
fi

In zsh, you could also do:

for pattern action (
  A 'action1'
  B 'action2'
)  if [[ $var = $~pattern ]] eval -- $action

The more patterns you have, the more pattern matchings must be performed. If it's only about fixed strings, you can make it more efficient by doing hash table lookups.

Taking your example, still in zsh:

typeset -A actions
IFS=,
for values action (
  1         'action 1'
  2         'action 2'
  1,3       'action 3'
  4         'action 4'
  1,5       'action 5'
  1,2,3,4,5 'action 6'
) for value ($=values) actions[$value]+=$action$'\n'

# then it's just a matter of:
for var ($lots_of_values) eval -- $actions[$var]

Which for each $var, does one hash table lookup instead of 6 pattern matches.

deleted 12 characters in body
Source Link
Stéphane Chazelas
  • 586.9k
  • 96
  • 1.1k
  • 1.7k

Yes, when using bash's ;;&, equivalent to zsh's ;| for subsequent patterns to still be tested even if the current one matched, you generally want to add it after all patterns, which ensures all patterns are tested.

Then:

# bash
case $var in
  (A) action1;;&
  (B) action2;;&
esac
# zsh
case $var in
  (A) action1;|
  (B) action2;|
esac

Becomes the same as ksh-style:

# ksh / bash / zsh / yash
if [[ $var = A ]]; then
  action1
fi
if [[ $var = B ]]; then
  action2
fi

In zsh, you could also do:

for pattern action (
  A 'action1'
  B 'action2'
)  if [[ $var = $~pattern ]] eval -- $action

The more patterns you have, the more pattern matchings must be performed. If it's only about fixed strings, you can make it more efficient by doing hash table lookups.

Taking your example, still in zsh:

typeset -A actions
IFS=,
for values action (
  1           'action 1'
  2           'action 2'
  '1|3'1,3       'action 3'
  4           'action 4'
  '1|5'1,5       'action 5'
  '1|2|3|4|5'1,2,3,4,5 'action 6'
) for value (${(s[|])values}$=values) actions[$value]+=$action$'\n'

# then it's just a matter of:
for var ($lots_of_values) eval -- $actions[$var]

Which for each $var, does one hash table lookup instead of 6 pattern matches.

Yes, when using bash's ;;&, equivalent to zsh's ;| for subsequent patterns to still be tested even if the current one matched, you generally want to add it after all patterns, which ensures all patterns are tested.

Then:

# bash
case $var in
  (A) action1;;&
  (B) action2;;&
esac
# zsh
case $var in
  (A) action1;|
  (B) action2;|
esac

Becomes the same as ksh-style:

# ksh / bash / zsh / yash
if [[ $var = A ]]; then
  action1
fi
if [[ $var = B ]]; then
  action2
fi

In zsh, you could also do:

for pattern action (
  A 'action1'
  B 'action2'
)  if [[ $var = $~pattern ]] eval -- $action

The more patterns you have, the more pattern matchings must be performed. If it's only about fixed strings, you can make it more efficient by doing hash table lookups.

Taking your example, still in zsh:

typeset -A actions
for values action (
  1           'action 1'
  2           'action 2'
  '1|3'       'action 3'
  4           'action 4'
  '1|5'       'action 5'
  '1|2|3|4|5' 'action 6'
) for value (${(s[|])values}) actions[$value]+=$action$'\n'

# then it's just a matter of:
for var ($lots_of_values) eval -- $actions[$var]

Which for each $var, does one hash table lookup instead of 6 pattern matches.

Yes, when using bash's ;;&, equivalent to zsh's ;| for subsequent patterns to still be tested even if the current one matched, you generally want to add it after all patterns, which ensures all patterns are tested.

Then:

# bash
case $var in
  (A) action1;;&
  (B) action2;;&
esac
# zsh
case $var in
  (A) action1;|
  (B) action2;|
esac

Becomes the same as ksh-style:

# ksh / bash / zsh / yash
if [[ $var = A ]]; then
  action1
fi
if [[ $var = B ]]; then
  action2
fi

In zsh, you could also do:

for pattern action (
  A 'action1'
  B 'action2'
)  if [[ $var = $~pattern ]] eval -- $action

The more patterns you have, the more pattern matchings must be performed. If it's only about fixed strings, you can make it more efficient by doing hash table lookups.

Taking your example, still in zsh:

typeset -A actions
IFS=,
for values action (
  1         'action 1'
  2         'action 2'
  1,3       'action 3'
  4         'action 4'
  1,5       'action 5'
  1,2,3,4,5 'action 6'
) for value ($=values) actions[$value]+=$action$'\n'

# then it's just a matter of:
for var ($lots_of_values) eval -- $actions[$var]

Which for each $var, does one hash table lookup instead of 6 pattern matches.

added 562 characters in body
Source Link
Stéphane Chazelas
  • 586.9k
  • 96
  • 1.1k
  • 1.7k
Loading
Source Link
Stéphane Chazelas
  • 586.9k
  • 96
  • 1.1k
  • 1.7k
Loading