A little bit of golf

classic Classic list List threaded Threaded
27 messages Options
12
Reply | Threaded
Open this post in threaded view
|

A little bit of golf

Hisham
Hi all,

So here's a little problem that some of you might enjoy playing with
in case you're bored.

Let's play "variable expansion". We want to convert "Hello, $planet!"
to "Hello, Earth!" given { planet = "Earth" }, but we also want it to
support "Hello, ${planet}!".

That is, we want to perform "variable expansion" in a string s, so
that given a table of "variables" we want to expand every $foo or
${foo} in s to the value of variables.foo.

HOWEVER, we don't want recursive expansion (ie, if the value of
variables.foo is "$bar", then we _don't_ want it expanded to the value
of variables.bar).

For simplicity, assume that variable names are in the form [A-Za-z0-9_]+

Here's a test script for you to play with:

-----------------------------------------
local variables = {
   foo = "$bar",
   foo_bar = "blabla",
   wee = "${foo}",
   bar = "foo",
}

local function expand_variables(str)
   -- TODO
end

local tests = {
   { from = "hello $foo", to = "hello $bar" },
   { from = "hello ${foo}", to = "hello $bar" },
   { from = "hello $wee", to = "hello ${foo}" },
   { from = "hello$foo_bar $foo", to = "helloblabla $bar" },
   { from = "hel${bar}lo", to = "helfoolo" },
   { from = "hel${foo_bar}lo", to = "helblablalo" },
   { from = "${foo_bar}", to = "blabla" },
   { from = "end${bar}", to = "endfoo" },
   { from = "${bar}end", to = "fooend" },
   { from = "end$foo", to = "end$bar" },
   -- At your discretion: how to deal with...
   -- "$fooend"
   -- "${}"
   -- "${bo{bozo}zo}"
   -- "${bo${bar}zo}"
}

for _, test in ipairs(tests) do
   local exp = expand_variables(test.from)
   if exp == test.to then
      print("OK", test.from, exp)
   else
      print("ERROR", test.from, test.to, exp)
   end
end
-----------------------------------------

Note that the naive approach of chaining two str:gsub() calls like
this won't work:

expand_variables = function(str)
   return str:gsub("%$([A-Za-z0-9_]+)",
variables):gsub("%${([A-Za-z0-9_]+)}", variables)
end

...because this will make the second or third test case fail
(depending on which you expand first).

My first attempt at doing this was the following:

local function expand_variables(str)
   return str:gsub("%$([A-Za-z0-9_{}]+)", function(cap)
      local br, rest = cap:match("^{([^{}]+)}(.*)")
      if br then return (variables[br] or "") .. rest end
      local nobr, rest = cap:match("^([^{}]+)(.*)")
      return (variables[nobr] or "") .. (rest or "")
   end)
end

which I then slightly changed to the following because of the test
cases commented above:

local function expand_variables(str)
   return str:gsub("%$([A-Za-z0-9_{}]+)", function(cap)
      local br, rest = cap:match("^{([^{}]+)}(.*)")
      if br then return (variables[br] or "") .. rest end
      local nobr, rest = cap:match("^([^{}]+)(.*)")
      if nobr then return (variables[nobr] or "") .. rest end
      return "$"..cap
   end)
end

Can you suggest any nicer and/or shorter solution?

Ah! NO LPEG ALLOWED! :)

Cheers,

-- Hisham

Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Daurnimator
On 8 April 2015 at 10:12, Hisham <[hidden email]> wrote:
> Ah! NO LPEG ALLOWED! :)

Why not?

Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Soni "They/Them" L.
In reply to this post by Hisham


On 07/04/15 09:12 PM, Hisham wrote:

> Hi all,
>
> So here's a little problem that some of you might enjoy playing with
> in case you're bored.
>
> Let's play "variable expansion". We want to convert "Hello, $planet!"
> to "Hello, Earth!" given { planet = "Earth" }, but we also want it to
> support "Hello, ${planet}!".
>
> That is, we want to perform "variable expansion" in a string s, so
> that given a table of "variables" we want to expand every $foo or
> ${foo} in s to the value of variables.foo.
>
> HOWEVER, we don't want recursive expansion (ie, if the value of
> variables.foo is "$bar", then we _don't_ want it expanded to the value
> of variables.bar).
>
> For simplicity, assume that variable names are in the form [A-Za-z0-9_]+
>
> Here's a test script for you to play with:
>
> -----------------------------------------
> local variables = {
>     foo = "$bar",
>     foo_bar = "blabla",
>     wee = "${foo}",
>     bar = "foo",
> }
>
> local function expand_variables(str)
>     -- TODO
> end
>
> local tests = {
>     { from = "hello $foo", to = "hello $bar" },
>     { from = "hello ${foo}", to = "hello $bar" },
>     { from = "hello $wee", to = "hello ${foo}" },
>     { from = "hello$foo_bar $foo", to = "helloblabla $bar" },
>     { from = "hel${bar}lo", to = "helfoolo" },
>     { from = "hel${foo_bar}lo", to = "helblablalo" },
>     { from = "${foo_bar}", to = "blabla" },
>     { from = "end${bar}", to = "endfoo" },
>     { from = "${bar}end", to = "fooend" },
>     { from = "end$foo", to = "end$bar" },
>     -- At your discretion: how to deal with...
>     -- "$fooend"
>     -- "${}"
>     -- "${bo{bozo}zo}"
>     -- "${bo${bar}zo}"
> }
>
> for _, test in ipairs(tests) do
>     local exp = expand_variables(test.from)
>     if exp == test.to then
>        print("OK", test.from, exp)
>     else
>        print("ERROR", test.from, test.to, exp)
>     end
> end
> -----------------------------------------
>
> Note that the naive approach of chaining two str:gsub() calls like
> this won't work:
>
> expand_variables = function(str)
>     return str:gsub("%$([A-Za-z0-9_]+)",
> variables):gsub("%${([A-Za-z0-9_]+)}", variables)
> end

expand_variables = function(str)
    return str:gsub("%$({?)([A-Za-z0-9_]+)(}?)", function(a,b,c)
       -- little trick with the lengths, if a matches then it'll be 1, if c
       -- doesn't match it'll be 0 and 1 <= 0 is false, thus returning false
       -- and keeping the match as-is. for both matching, 1 <= 1 is true, so
       -- you get variables[b]. for neither matching, 0 <= 0 is true, so you
       -- get variables[b].
       -- ps: you never asked us to handle "$foo}" so I decided to eat the }
       return #a <= #c and variables[b]
    end)
end


>
> ...because this will make the second or third test case fail
> (depending on which you expand first).
>
> My first attempt at doing this was the following:
>
> local function expand_variables(str)
>     return str:gsub("%$([A-Za-z0-9_{}]+)", function(cap)
>        local br, rest = cap:match("^{([^{}]+)}(.*)")
>        if br then return (variables[br] or "") .. rest end
>        local nobr, rest = cap:match("^([^{}]+)(.*)")
>        return (variables[nobr] or "") .. (rest or "")
>     end)
> end
>
> which I then slightly changed to the following because of the test
> cases commented above:
>
> local function expand_variables(str)
>     return str:gsub("%$([A-Za-z0-9_{}]+)", function(cap)
>        local br, rest = cap:match("^{([^{}]+)}(.*)")
>        if br then return (variables[br] or "") .. rest end
>        local nobr, rest = cap:match("^([^{}]+)(.*)")
>        if nobr then return (variables[nobr] or "") .. rest end
>        return "$"..cap
>     end)
> end
>
> Can you suggest any nicer and/or shorter solution?
>
> Ah! NO LPEG ALLOWED! :)
>
> Cheers,
>
> -- Hisham
>

--
Disclaimer: these emails are public and can be accessed from <TODO: get a non-DHCP IP and put it here>. If you do not agree with this, DO NOT REPLY.


Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Hisham
On 7 April 2015 at 21:24, Soni L. <[hidden email]> wrote:

>
> expand_variables = function(str)
>    return str:gsub("%$({?)([A-Za-z0-9_]+)(}?)", function(a,b,c)
>       -- little trick with the lengths, if a matches then it'll be 1, if c
>       -- doesn't match it'll be 0 and 1 <= 0 is false, thus returning false
>       -- and keeping the match as-is. for both matching, 1 <= 1 is true, so
>       -- you get variables[b]. for neither matching, 0 <= 0 is true, so you
>       -- get variables[b].
>       -- ps: you never asked us to handle "$foo}" so I decided to eat the }
>       return #a <= #c and variables[b]
>    end)
>
> end

Nice! If we _were_ to handle "$bar}" returning "foo}", how would you
do it? The variation I came up with was not as elegant:

local function expand_variables(str)
    return str:gsub("%$({?)([A-Za-z0-9_]+)(}?)", function(a,b,c)
      return (#a == #c and variables[b]) or (a == "" and (variables[b]
or "")..c)
   end)
end

-- Hisham

Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Soni "They/Them" L.


On 07/04/15 09:45 PM, Hisham wrote:

> On 7 April 2015 at 21:24, Soni L. <[hidden email]> wrote:
>> expand_variables = function(str)
>>     return str:gsub("%$({?)([A-Za-z0-9_]+)(}?)", function(a,b,c)
>>        -- little trick with the lengths, if a matches then it'll be 1, if c
>>        -- doesn't match it'll be 0 and 1 <= 0 is false, thus returning false
>>        -- and keeping the match as-is. for both matching, 1 <= 1 is true, so
>>        -- you get variables[b]. for neither matching, 0 <= 0 is true, so you
>>        -- get variables[b].
>>        -- ps: you never asked us to handle "$foo}" so I decided to eat the }
>>        return #a <= #c and variables[b]
>>     end)
>>
>> end
> Nice! If we _were_ to handle "$bar}" returning "foo}", how would you
> do it? The variation I came up with was not as elegant:
>
> local function expand_variables(str)
>      return str:gsub("%$({?)([A-Za-z0-9_]+)(}?)", function(a,b,c)
>        return (#a == #c and variables[b]) or (a == "" and (variables[b]
> or "")..c)
>     end)
> end

local function expand_variables(str)
     return str:gsub("%$({?)([A-Za-z0-9_]+)(}?)", function(a,b,c)
        -- ".." has precedence over "and", so we can drop the () on
        -- #a == #c and (variables[b] .. (#a < #c and c or ""))
        return #a == #c and variables[b] .. (#a < #c and c or "")
    end)
end


>
> -- Hisham
>

--
Disclaimer: these emails are public and can be accessed from <TODO: get a non-DHCP IP and put it here>. If you do not agree with this, DO NOT REPLY.


Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Jonathan Goble
In reply to this post by Hisham
On Tue, Apr 7, 2015 at 8:12 PM, Hisham <[hidden email]> wrote:
>    -- At your discretion: how to deal with...
>    -- "$fooend"
>    -- "${}"
>    -- "${bo{bozo}zo}"
>    -- "${bo${bar}zo}"


I set up the following test cases for those, plus two more:

{ from = "$fooend", to = "$fooend" }, -- ignore undefined vars
{ from = "${}", to = "${}" }, -- ignore empty braces
{ from = "${bo{bozo}zo}", to = "${bo{bozo}zo}" }, -- too weird, ignore
{ from = "${bo${bar}zo}", to = "${bofoozo}" }, -- expand the innermost one
{ from = "test ${foo", to = "test ${foo" }, -- ignore unclosed braces
{ from = "test $bar}bar", to = "test foo}bar" }, -- don't strip the stray brace


With those and the variables and test cases you defined, this shorter
function passes all tests:

local function expand_variables(str)
    return str:gsub("%$(%{?)([A-Za-z0-9_]+)(%}?)", function(open, var, close)
        if #open == 1 and #close == 0 or not variables[var] then
            return "$"..open..var..close
        else return variables[var] .. (#open == 0 and close or "")
    end end)
end

Of course, I could have explicitly checked for open == "{", etc., but
#open == 1 is one keystroke shorter, and this is golf. :-)

Jonathan

Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Jonathan Goble
(I really should check for new messages in the thread before replying... :-P)

On Tue, Apr 7, 2015 at 9:01 PM, Jonathan Goble <[hidden email]> wrote:

> On Tue, Apr 7, 2015 at 8:12 PM, Hisham <[hidden email]> wrote:
>>    -- At your discretion: how to deal with...
>>    -- "$fooend"
>>    -- "${}"
>>    -- "${bo{bozo}zo}"
>>    -- "${bo${bar}zo}"
>
>
> I set up the following test cases for those, plus two more:
>
> { from = "$fooend", to = "$fooend" }, -- ignore undefined vars
> { from = "${}", to = "${}" }, -- ignore empty braces
> { from = "${bo{bozo}zo}", to = "${bo{bozo}zo}" }, -- too weird, ignore
> { from = "${bo${bar}zo}", to = "${bofoozo}" }, -- expand the innermost one
> { from = "test ${foo", to = "test ${foo" }, -- ignore unclosed braces
> { from = "test $bar}bar", to = "test foo}bar" }, -- don't strip the stray brace
>
>
> With those and the variables and test cases you defined, this shorter
> function passes all tests:
>
> local function expand_variables(str)
>     return str:gsub("%$(%{?)([A-Za-z0-9_]+)(%}?)", function(open, var, close)
>         if #open == 1 and #close == 0 or not variables[var] then
>             return "$"..open..var..close
>         else return variables[var] .. (#open == 0 and close or "")
>     end end)
> end
>
> Of course, I could have explicitly checked for open == "{", etc., but
> #open == 1 is one keystroke shorter, and this is golf. :-)
>
> Jonathan

Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Soni "They/Them" L.
In reply to this post by Soni "They/Them" L.


On 07/04/15 09:58 PM, Soni L. wrote:

>
>
> On 07/04/15 09:45 PM, Hisham wrote:
>> On 7 April 2015 at 21:24, Soni L. <[hidden email]> wrote:
>>> expand_variables = function(str)
>>>     return str:gsub("%$({?)([A-Za-z0-9_]+)(}?)", function(a,b,c)
>>>        -- little trick with the lengths, if a matches then it'll be
>>> 1, if c
>>>        -- doesn't match it'll be 0 and 1 <= 0 is false, thus
>>> returning false
>>>        -- and keeping the match as-is. for both matching, 1 <= 1 is
>>> true, so
>>>        -- you get variables[b]. for neither matching, 0 <= 0 is
>>> true, so you
>>>        -- get variables[b].
>>>        -- ps: you never asked us to handle "$foo}" so I decided to
>>> eat the }
>>>        return #a <= #c and variables[b]
>>>     end)
>>>
>>> end
>> Nice! If we _were_ to handle "$bar}" returning "foo}", how would you
>> do it? The variation I came up with was not as elegant:
>>
>> local function expand_variables(str)
>>      return str:gsub("%$({?)([A-Za-z0-9_]+)(}?)", function(a,b,c)
>>        return (#a == #c and variables[b]) or (a == "" and (variables[b]
>> or "")..c)
>>     end)
>> end
>
> local function expand_variables(str)
>     return str:gsub("%$({?)([A-Za-z0-9_]+)(}?)", function(a,b,c)
>        -- ".." has precedence over "and", so we can drop the () on
>        -- #a == #c and (variables[b] .. (#a < #c and c or ""))
>        return #a == #c and variables[b] .. (#a < #c and c or "")
>    end)
> end
>
Wait derp, that == should be <= :P

And to also fit the following cases:

{ from = "$fooend", to = "$fooend" }, -- ignore undefined vars
{ from = "${}", to = "${}" }, -- ignore empty braces
{ from = "${bo{bozo}zo}", to = "${bo{bozo}zo}" }, -- too weird, ignore
{ from = "${bo${bar}zo}", to = "${bofoozo}" }, -- expand the innermost one
{ from = "test ${foo", to = "test ${foo" }, -- ignore unclosed braces
{ from = "test $bar}bar", to = "test foo}bar" }, -- don't strip the stray brace

Should probably do "and variables[b] and variables[b] .. (#a < #c and c or "")" instead.

Final result:

local function expand_variables(str)
     return str:gsub("%$({?)([A-Za-z0-9_]+)(}?)", function(a,b,c)
        return #a <= #c and variables[b] and variables[b] .. (#a < #c and c or "")
    end)
end


>
>>
>> -- Hisham
>>
>

--
Disclaimer: these emails are public and can be accessed from <TODO: get a non-DHCP IP and put it here>. If you do not agree with this, DO NOT REPLY.


Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Hisham
In reply to this post by Jonathan Goble
On 7 April 2015 at 22:01, Jonathan Goble <[hidden email]> wrote:

> On Tue, Apr 7, 2015 at 8:12 PM, Hisham <[hidden email]> wrote:
>>    -- At your discretion: how to deal with...
>>    -- "$fooend"
>>    -- "${}"
>>    -- "${bo{bozo}zo}"
>>    -- "${bo${bar}zo}"
>
>
> I set up the following test cases for those, plus two more:
>
> { from = "$fooend", to = "$fooend" }, -- ignore undefined vars
> { from = "${}", to = "${}" }, -- ignore empty braces
> { from = "${bo{bozo}zo}", to = "${bo{bozo}zo}" }, -- too weird, ignore
> { from = "${bo${bar}zo}", to = "${bofoozo}" }, -- expand the innermost one
> { from = "test ${foo", to = "test ${foo" }, -- ignore unclosed braces
> { from = "test $bar}bar", to = "test foo}bar" }, -- don't strip the stray brace
>
>
> With those and the variables and test cases you defined, this shorter
> function passes all tests:
>
> local function expand_variables(str)
>     return str:gsub("%$(%{?)([A-Za-z0-9_]+)(%}?)", function(open, var, close)
>         if #open == 1 and #close == 0 or not variables[var] then
>             return "$"..open..var..close
>         else return variables[var] .. (#open == 0 and close or "")
>     end end)
> end

Cool variation on the idea!

I like your additional test cases; I originally left those open in
case someone came up with a cool solution that depended on some
stranger behavior in some of those.

In terms of functionality, I think I'd prefer "$fooend" to expand to
"" (undefined vars expand to empty string a la bash); this would
result in the following (even shorter!) variation for Soni L.'s latest
solution:

local function expand_variables(str)
    return str:gsub("%$({?)([A-Za-z0-9_]+)(}?)", function(a,b,c)
           return #a <= #c and (variables[b] or "") .. (#a < #c and c or "")
   end)
end

-- Hisham

Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Sean Conner
In reply to this post by Daurnimator
It was thus said that the Great Daurnimator once stated:
> On 8 April 2015 at 10:12, Hisham <[hidden email]> wrote:
> > Ah! NO LPEG ALLOWED! :)
>
> Why not?

  Way too easy.

        lpeg = require "lpeg"
        id = lpeg.R("AZ","az","09","__")^1 / variables
        expand_variables = lpeg.Cs((
                   (lpeg.P"${") / "" * id * (lpeg.P"}"/"")
                 + (lpeg.P"$")  / "" * id
                 + lpeg.C(lpeg.P(1))
               )^0)

  -spc (Could be made even smaller ... )


Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Andrew Starks


On Tuesday, April 7, 2015, Sean Conner <[hidden email]> wrote:
It was thus said that the Great Daurnimator once stated:
> On 8 April 2015 at 10:12, Hisham <<a href="javascript:;" onclick="_e(event, &#39;cvml&#39;, &#39;h@hisham.hm&#39;)">h@...> wrote:
> > Ah! NO LPEG ALLOWED! :)
>
> Why not?

  Way too easy.

        lpeg = require "lpeg"
        id = lpeg.R("AZ","az","09","__")^1 / variables
        expand_variables = lpeg.Cs((
                   (lpeg.P"${") / "" * id * (lpeg.P"}"/"")
                 + (lpeg.P"$")  / "" * id
                 + lpeg.C(lpeg.P(1))
               )^0)

  -spc (Could be made even smaller ... )



Right. Too easy and too readable. :)

-Andrew
Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Rena
On Tue, Apr 7, 2015 at 11:06 PM, Andrew Starks <[hidden email]> wrote:

>
>
> On Tuesday, April 7, 2015, Sean Conner <[hidden email]> wrote:
>>
>> It was thus said that the Great Daurnimator once stated:
>> > On 8 April 2015 at 10:12, Hisham <[hidden email]> wrote:
>> > > Ah! NO LPEG ALLOWED! :)
>> >
>> > Why not?
>>
>>   Way too easy.
>>
>>         lpeg = require "lpeg"
>>         id = lpeg.R("AZ","az","09","__")^1 / variables
>>         expand_variables = lpeg.Cs((
>>                    (lpeg.P"${") / "" * id * (lpeg.P"}"/"")
>>                  + (lpeg.P"$")  / "" * id
>>                  + lpeg.C(lpeg.P(1))
>>                )^0)
>>
>>   -spc (Could be made even smaller ... )
>>
>>
>
> Right. Too easy and too readable. :)
>
> -Andrew

That's your idea of readable? Yikes.

Maybe not shortest, but somewhat clear solution:
local vars = setmetatable({foo="bar"}, {
    __index = function(self, k)
        return type(k) == 'string' and rawget(self, k:match('{?([%w_]+)}?'))
    end,
})
function expand_variables(str)
    return str:gsub('%$(%S+)', vars)
end

--
Sent from my Game Boy.

Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Sean Conner
In reply to this post by Andrew Starks
It was thus said that the Great Andrew Starks once stated:

> On Tuesday, April 7, 2015, Sean Conner <[hidden email]> wrote:
>
> > It was thus said that the Great Daurnimator once stated:
> > > On 8 April 2015 at 10:12, Hisham <[hidden email] <javascript:;>> wrote:
> > > > Ah! NO LPEG ALLOWED! :)
> > >
> > > Why not?
> >
> >   Way too easy.
> >
> >         lpeg = require "lpeg"
> >         id = lpeg.R("AZ","az","09","__")^1 / variables
> >         expand_variables = lpeg.Cs((
> >                    (lpeg.P"${") / "" * id * (lpeg.P"}"/"")
> >                  + (lpeg.P"$")  / "" * id
> >                  + lpeg.C(lpeg.P(1))
> >                )^0)
> >
> >   -spc (Could be made even smaller ... )
> >
> >
> >
> Right. Too easy and too readable. :)

  Okay, how about:

re = require "re"

expand_variables = re.compile([[
        expand          <-      {~ (full / partial / .)* ~}
        full            <-      '${' -> '' id '}' -> ''
        partial         <-      '$'  -> '' id
        id              <-      [A-Za-z-0-9_]+ -> variables
  ]],{ variables = variables })

  -spc (A more or less literal translation ... )



Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Dirk Laurie-2
In reply to this post by Hisham
2015-04-08 2:12 GMT+02:00 Hisham <[hidden email]>:

> Hi all,
>
> So here's a little problem that some of you might enjoy playing with
> in case you're bored.
>
> Let's play "variable expansion". We want to convert "Hello, $planet!"
> to "Hello, Earth!" given { planet = "Earth" }, but we also want it to
> support "Hello, ${planet}!".
>
> That is, we want to perform "variable expansion" in a string s, so
> that given a table of "variables" we want to expand every $foo or
> ${foo} in s to the value of variables.foo.
>
> HOWEVER, we don't want recursive expansion (ie, if the value of
> variables.foo is "$bar", then we _don't_ want it expanded to the value
> of variables.bar).

I.e. you wish to remove the restriction on the third line
of the docstring of ml.expand (from Microlight).

--- expand a string containing any `${var}` or `$var`.
-- If the former form is found, the latter is not looked for, so
-- pick _either one_ of these forms consistently!
-- Substitution values should be only numbers or strings.
-- @param s the string
-- @param subst either a table or a function (as in `string.gsub`)
-- @return expanded string

> Can you suggest any nicer and/or shorter solution?
>
> Ah! NO LPEG ALLOWED! :)

math.randomseed(os.clock())
function password()  return (("."):rep(32):gsub(".",
  function() return string.char(math.random(256)-1) end))
end

Now make two tables: one containing the original keys
and a password for each, the other containing those
passwords as keys with the original values.

Three times gsub:
  the ${var} forms with the first table,
  the $var forms with the first table,
  the passwords with the second table.

If you are scared this might not work, better not use
ATMs any more either.

Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Matthew Wild
In reply to this post by Hisham
On 8 April 2015 at 01:12, Hisham <[hidden email]> wrote:

> Hi all,
>
> So here's a little problem that some of you might enjoy playing with
> in case you're bored.
>
> Let's play "variable expansion". We want to convert "Hello, $planet!"
> to "Hello, Earth!" given { planet = "Earth" }, but we also want it to
> support "Hello, ${planet}!".
>
> That is, we want to perform "variable expansion" in a string s, so
> that given a table of "variables" we want to expand every $foo or
> ${foo} in s to the value of variables.foo.
...
> Can you suggest any nicer and/or shorter solution?

Well, it's the middle of the night and I can't sleep, but this passes
all your tests, and all other tests posted in the thread so far
(except "$undefined" expands to "" rather than "$undefined", as you
requested):

local function expand_variables(str)
   return (str:gsub("%$([_%w]+)", "${%1}"):gsub("%${([_%w]+)}", function (v)
        return variables[v] or "";
   end));
end

Regards,
Matthew

Reply | Threaded
Open this post in threaded view
|

A little bit of golf

Andrew Starks
In reply to this post by Rena


On Tuesday, April 7, 2015, Rena <<a href="javascript:_e(%7B%7D,&#39;cvml&#39;,&#39;hyperhacker@gmail.com&#39;);" target="_blank">hyperhacker@...> wrote:
On Tue, Apr 7, 2015 at 11:06 PM, Andrew Starks <[hidden email]> wrote:
>
>
> On Tuesday, April 7, 2015, Sean Conner <[hidden email]> wrote:
>>
>> It was thus said that the Great Daurnimator once stated:
>> > On 8 April 2015 at 10:12, Hisham <[hidden email]> wrote:
>> > > Ah! NO LPEG ALLOWED! :)
>> >
>> > Why not?
>>
>>   Way too easy.
>>
>>         lpeg = require "lpeg"
>>         id = lpeg.R("AZ","az","09","__")^1 / variables
>>         expand_variables = lpeg.Cs((
>>                    (lpeg.P"${") / "" * id * (lpeg.P"}"/"")
>>                  + (lpeg.P"$")  / "" * id
>>                  + lpeg.C(lpeg.P(1))
>>                )^0)
>>
>>   -spc (Could be made even smaller ... )
>>
>>
>
> Right. Too easy and too readable. :)
>
> -Andrew

That's your idea of readable? Yikes.

Maybe not shortest, but somewhat clear solution:
local vars = setmetatable({foo="bar"}, {
    __index = function(self, k)
        return type(k) == 'string' and rawget(self, k:match('{?([%w_]+)}?'))
    end,
})
function expand_variables(str)
    return str:gsub('%$(%S+)', vars)
end

--
Sent from my Game Boy.


Something about PCRE/Lua Patterns makes me want to remind my mom that I'm still using the computer and to please get off of the phone before I loose my PPP connection. 

-Andrew
Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Roberto Ierusalimschy
In reply to this post by Matthew Wild
> Well, it's the middle of the night and I can't sleep, but this passes
> all your tests, and all other tests posted in the thread so far
> (except "$undefined" expands to "" rather than "$undefined", as you
> requested):
>
> local function expand_variables(str)
>    return (str:gsub("%$([_%w]+)", "${%1}"):gsub("%${([_%w]+)}", function (v)
>         return variables[v] or "";
>    end));
> end

That would be my answer, too, except that I would use "%b{}" in the
second gsub. (That changes a little the meaning; it allows arbitrary
"names" inside the brackets, as long as they have balanced brackets.)

-- Roberto

Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Matthew Wild
On 8 April 2015 at 13:55, Roberto Ierusalimschy <[hidden email]> wrote:

>> Well, it's the middle of the night and I can't sleep, but this passes
>> all your tests, and all other tests posted in the thread so far
>> (except "$undefined" expands to "" rather than "$undefined", as you
>> requested):
>>
>> local function expand_variables(str)
>>    return (str:gsub("%$([_%w]+)", "${%1}"):gsub("%${([_%w]+)}", function (v)
>>         return variables[v] or "";
>>    end));
>> end
>
> That would be my answer, too, except that I would use "%b{}" in the
> second gsub. (That changes a little the meaning; it allows arbitrary
> "names" inside the brackets, as long as they have balanced brackets.)

I wrote %b{} first, but it wasn't clear that it fitted Hisham's
intentions (only matching identifier-like names). I think in practice
either work though.

Regards,
Matthew

Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

William Ahern
In reply to this post by Rena
On Wed, Apr 08, 2015 at 12:13:17AM -0400, Rena wrote:

> On Tue, Apr 7, 2015 at 11:06 PM, Andrew Starks <[hidden email]> wrote:
> >
> >
> > On Tuesday, April 7, 2015, Sean Conner <[hidden email]> wrote:
> >>
> >> It was thus said that the Great Daurnimator once stated:
> >> > On 8 April 2015 at 10:12, Hisham <[hidden email]> wrote:
> >> > > Ah! NO LPEG ALLOWED! :)
> >> >
> >> > Why not?
> >>
> >>   Way too easy.
> >>
> >>         lpeg = require "lpeg"
> >>         id = lpeg.R("AZ","az","09","__")^1 / variables
> >>         expand_variables = lpeg.Cs((
> >>                    (lpeg.P"${") / "" * id * (lpeg.P"}"/"")
> >>                  + (lpeg.P"$")  / "" * id
> >>                  + lpeg.C(lpeg.P(1))
> >>                )^0)
> >>
> >>   -spc (Could be made even smaller ... )
> >
> > Right. Too easy and too readable. :)
> >
> > -Andrew
>
> That's your idea of readable? Yikes.
>
> Maybe not shortest, but somewhat clear solution:
> local vars = setmetatable({foo="bar"}, {
>     __index = function(self, k)
>         return type(k) == 'string' and rawget(self, k:match('{?([%w_]+)}?'))
>     end,
> })
> function expand_variables(str)
>     return str:gsub('%$(%S+)', vars)
> end

Now modify yours to support escaping $, like "Hello \$planet!". Note should
also permit escaping the escape character, "Hello \\$planet!".




Reply | Threaded
Open this post in threaded view
|

Re: A little bit of golf

Jonathan Goble
On Wed, Apr 8, 2015 at 2:38 PM, William Ahern
<[hidden email]> wrote:
> Now modify yours to support escaping $, like "Hello \$planet!". Note should
> also permit escaping the escape character, "Hello \\$planet!".

Thinking out loud here: How about capturing a possible run of escape
characters immediately prior to the "$"? Then check the length of that
capture, mod 2. If even, all are escaped, so continue with expansion
as normal, prepending the capture to the return value if anything
other than false is returned. If the capture length is odd, then the
final escape character is escaping the "$", so return false
immediately.

I would think that this would also work when escaping is done by
doubling the "$", as in Python's string.Template. [1]

This is all off the top of my head and untested, though.

[1] https://docs.python.org/2/library/string.html#template-strings

12