# Find the result of some assignment statements

In this challenge, the goal is to find the values of some variables after a number of assignments are done. An example input:

a = 5
b = 4
c = a = b
a = 2
b = a


This would result in:

a = 2
b = 2
c = 4


Each statement will be one of the following:

• A variable name ([a-z_]+)
• A numeric value ([0-9]+)
• An assignment operation, with a variable name on the left and a statement on the right

You may assume that the input will be a list of statements, formatted however you want. Variable names will have differing lengths (if you need a hard value to gold within, assume 16 chars max).

Note that statements can contain more or less than one assignment (such as a, 23, or a = b = c = 4), and that variables can appear that are never assigned to. Assume no undefined variables are used as values in an assignment (such as a = undefined_variable), and that no variable will be on both sides of an assignment (such as a = a or a = a = 1).

You can take input any way you wish (such as a string with a character to delimit statements, a list formatted as [["a", 5], ["b", "a"]], etc.), and output can be in any consistent format (such as a hash map of names to values, or a list of values in the order that the variables first appeared).

Test cases:

a = 5                     -> a = 5
b = 512, c = a = 2        -> a = 2, b = 512, c = 2
def, 2, e = 8, 101        -> e = 8
->
a                         ->
fgh = 4, i = 3, fgh = i   -> fgh = 3, i = 3
j = k = l = m = n = 14    -> j = 14, k = 14, l = 14, m = 14, n = 14
s = t = u = 6, t = v = 7  -> s = 6, t = 7, u = 6, v = 7
o = 3, o = p              -> [undefined]
q = r                     -> [undefined]
w = w = 2                 -> [undefined]
x = 4, x = x              -> [undefined]


This is , so shortest answer per language wins!

• Also note to answers that eval and the like is unlikely to work, as the variable could be named like a keyword in your language. – Sisyphus Sep 21 '20 at 23:13
• Should I handle empty input (zero statements)? – Bubbler Sep 22 '20 at 0:26
• @Bubbler That's a tough one. I think I'm going to say yes, and it would have the same output as 23 or a would (nothing). I think most answers already work that way anyway. – Redwolf Programs Sep 22 '20 at 2:05

# APL (Dyalog Unicode), 22 bytes

{n⊣⍵{0::0⋄⍵⍎⍺}¨n←⎕NS⍬}


Try it online!

Takes a list of statements in the form of a←b←3, and returns a namespace which is essentially a hashmap of variable names to values. You can't print all the contents of it directly, but you can inspect individual variables like ns.somevar or list all names using ns.⎕NL ¯2.

Oh, and APL doesn't have any alphanumeric-only keywords!

{n⊣⍵{0::0⋄⍵⍎⍺}¨n←⎕NS⍬}  ⍝ ⍵: list of statements
n←⎕NS⍬   ⍝ Create an empty namespace
⍵{        }¨         ⍝ For each statement...
⍵⍎⍺           ⍝ Try executing the statement inside the namespace
0::0⋄              ⍝ ignoring any errors (undefined name)
n⊣                     ⍝ Return the populated namespace

• Right language for the job I guess! – caird coinheringaahing Sep 22 '20 at 0:11
• what does the SRC THIS part do? – Jonah Sep 22 '20 at 0:43
• @Jonah It's not SRC THIS, it's ⎕SRC (system function) ⎕THIS (system variable). Normally a function should be defined on its own line as f←{...}. To use the code section on TIO as the byte counter, the raw function {...} without assignment is fetched as source code using ⎕SRC ⎕THIS and eval'd into a function, then assigned to f. – Bubbler Sep 22 '20 at 0:53
• -1: {⊃⍵{0::⍵⋄⍵⊣⍵⍎⍺}¨⎕NS⍬} Try it online! (though a full program works, ⊃⎕{0::⍵⋄⍵⊣⍵⍎⍺}¨⎕NS⍬, it is awkward to use) – Adám Sep 22 '20 at 5:58
• @Adám Doesn't work with zero-statement input. – Bubbler Sep 22 '20 at 6:00

# Python 3 2, 8075 69 bytes

-5 bytes thanks to @Sisyphus
-6 bytes thanks to @xnor

g={}
for s in input():
k=s.pop()
for n in s:g[n]=g.get(k,k)
print g


Try it online!

Takes input as a list of lists of terms, returns a dict of variable name to value.

## Explanation

def f(x,g={}):    # Save a few bytes by defining g as a default argument.
for s in x:
k=s.pop(-1)     # Take the last term, which is the value we'll be using.
for n in s:     # For all *other* values:
g[n]=g.get(k,k)    # .get(k, k) means "get the value called k, if not found use k raw" (numbers will not be found)
return g


Note that it never actually differentiates between numbers and variables, just trusts that the input won't try to assign to a number. This means you can actually use it to assign to a number - this input:

[9, 5],
['b', 9],
['c', 'a', 'b'],
['a', 2],
['b', 9]


Will result in this output:

{9: 5, 'b': 5, 'c': 5, 'a': 2}

• This fails for the third test case – caird coinheringaahing Sep 21 '20 at 23:21
• @cairdcoinheringaahing Yes, see edit. – Artemis Sep 21 '20 at 23:21
• Bad choice of test case, it still fails for variables with no assignment – caird coinheringaahing Sep 21 '20 at 23:23
• You can iterate over input() directly, and also use that pop uses the -1 index as default: Try it online!, – xnor Sep 22 '20 at 3:24
• Note that your original answer could have been 35 bytes with exec(x,{},g), since exec does not add __builtins__ to the locals dictionary. (This is still invalid) – ovs Sep 22 '20 at 7:16

# J, 66 bytes

33 bytes for the _ =: 1 special case …

(rplc&('a0';'_')@}.~&_6;".)&>@r0[0!:110@rplc&('_';'a0')[r0=:4!:5@1


Try it online!

### How it otherwise works

(_6&}.;".)&>@r0[0!:110[r0=:4!:5@1


It's a mess! m!:n are special functions, that do stuff depending on m and n.

• r0=:4!:5@1: "4!:5 (1) produces a list of global names assigned since the last execution of 4!:5." Store as r0, so we can execute it again cheaply while it won't be overwritten.
• 0!:110 execute input string as script, ignoring any output/errors (so predefined values won't cause harm.)
• r0 execute 4!:5@1 again, get boxed list of changed variables
• &> unbox and …
• ". execute each variable to get its value
• _6}&. drop last 6 characters from the variable (which contain the namespace _base_.)
• ; join name and result together
• Fails for _ =: 1 – Sisyphus Sep 22 '20 at 1:10
• @Sisyphus _ isn't a valid variable name in J; it's the built in number infinity. – Jonah Sep 22 '20 at 1:13
• @Jonah however the spec requires you support [a-z_]+, no? – Sisyphus Sep 22 '20 at 1:13
• You're right. It's only impossible with this approach, not with J itself. – Jonah Sep 22 '20 at 1:16
• @Sisyphus Ugh, and I thought I was handling all edge cases. Fixed for now, maybe tomorrow I'll have a better approach than just substituting… – xash Sep 22 '20 at 1:36

# JavaScript (ES6), 81 bytes

Expects a string in the format described in the challenge. Returns an array of [name, value] pairs.

s=>Object.keys(o={},eval(s.replace(/[_-z]+/g,"o.X$&"))).map(k=>[k.slice(1),o[k]])  Try it online! ### How? We define an object o initially empty and add the prefix "o.X" to all variable names in the input string. Example: /* before */ "s = t = u = 6, t = v = 7" /* after */ "o.Xs = o.Xt = o.Xu = 6, o.Xt = o.Xv = 7"  We need the leading X to prevent the reserved property __proto__ from being overridden this way. Provided that the input string is in the expected format -- which is guaranteed by the challenge rules -- the transformed string can be safely eval()'uated. We then iterate on the keys of o to build a list of pairs consisting of 1) the key name without the leading X and 2) the final value associated to the key. Without the __proto__ issue, this could be done in just 45 bytes without any post-processing: s=>(eval(s.replace(/[_-z]+/g,"o.$&",o={})),o)


Try it online!

• I notice on MDN Object.keys take one arg, so are we relying on the 2nd arg being ignored and left to right arg evaluation? – Jonah Sep 22 '20 at 12:40
• @Jonah Yes and yes. :-) – Arnauld Sep 22 '20 at 12:47

# Wolfram Language (Mathematica), 5551 43 bytes

($=<||>;($@#2=#/.$)&~Fold~Reverse@#&/@#;$)&


Try it online!

-8 thanks to w123

• You can shorten the lookup code $@#/._@__:># to #/.$ -- Associations can be used like rules. – w123 Sep 23 '20 at 15:08

# Python 3.9rc2, 67 bytes

def f(x):
g={}
for*u,k in x:g|={n:g.get(k,k)for n in u}
return g


No TIO link, as TIO doesn't support Python 3.9.

Borrows ideas from Artemis' answer, with the following improvements:

• We can use an iterable unpack *u,k in the for loop.
• In Python 3.9 we can merge dicts using a|=b, which is much shorter than the old a.update(b) and {**a,**b} methods.
• Having a defaulted function parameter which is mutable means the function is not reusable without passing that default, so I think it has to go in the function body instead, right? – Jonathan Allan Sep 22 '20 at 12:28
• @JonathanAllan, I don't know Python but wouldn't g only exist within the scope of the function, meaning it would reset to the default {} with each subsequent call? – Shaggy Sep 22 '20 at 19:21
• @Shaggy no, I think it's in the caller's scope. Indeed a function like this is not re-usable. Here is a simple example of a mutable object (a dictionary) which does not get set back to the default on the second call. – Jonathan Allan Sep 22 '20 at 22:37
• @JonathanAllan indeed you're right. I've fixed this (luckily it only cost a byte). I think I've been caught by this even in non-golf contexts before! – Sisyphus Sep 23 '20 at 0:24

# Python 3, 159141152 128 bytes

def f(s):
g={}
for k in s:
if'='in k:
*v,l=k.split('=')
for r in v:
try:g[r]=int(l)
except:g[r]=g[l]
return g


Try it online!

-18 bytes thanks to pxeger

+11 bytes thanks to Shaggy for pointing out a bug

-24 bytes thanks to ovs

Python really isn't my strong suit for golfing :/ Note the use of tabs rather than spaces, so the indentation levels are still a single byte each. Takes input as a list of lines with assignments separated by = (no spaces) and returns a dictionary of variables and values

• 141 bytes by replacing the filter with an if-statement, and you also don't need the return statement – pxeger Sep 22 '20 at 7:59
• @pxeger Nice, edited that in! Thanks – caird coinheringaahing Sep 22 '20 at 12:47
• I think this might suffer from the same problem Jonathan raised here. – Shaggy Sep 23 '20 at 21:47
• @Shaggy So it does. Corrected – caird coinheringaahing Sep 23 '20 at 21:56
• Do you really need ast.literal_eval? I think you can just replace it with int. – ovs Sep 23 '20 at 22:15

# Batch, 331317 72 bytes

@setlocal
@for /f "delims==" %%a in ('set')do @set %%a=
@set/a%*
@set


Takes a comma-separated list of assignments on the command line. Explanation:

@setlocal


Don't modify the parent environment.

@for /f "delims==" %%a in ('set')do @set %%a=


Delete all variables, including predefined variables such as PATH. We're only using shell builtins, so we don't need them.

@set/a%*


Evaluate the assignments.

@set


List all of the resulting variables.

• I already have an environment variable beginning with _ which would confuse the code otherwise I guess you could assume a clean environment where such environment variables do not exist? Though it'll make it harder to test the code. – Bubbler Sep 22 '20 at 0:23
• @Bubbler I know that full programs don't normally have to worry about reusability because their state doesn't normally persist, but Batch scripts are more like functions in that regard, so I feel I shouldn't rely on the caller resetting the environment every time. – Neil Sep 22 '20 at 8:38
• @Bubbler Actually I could just clean the environment myself, after I use setlocal to avoid destroying it. – Neil Sep 24 '20 at 6:51
• @Bubbler ... and taking that to the logical extreme, I don't need any of the original environment at all, so... – Neil Sep 24 '20 at 7:24

# SNOBOL4 (CSNOBOL4), 183 bytes

	T =TABLE()
N	X =INPUT	:F(O)
R	X SPAN(&LCASE '_') . Y (' ' | RPOS(0)) . Z ='T<"' Y '">' Z	:S(R)
EVAL(X)	:(N)
O	A =CONVERT(T,'ARRAY')
I	I =I + 1
OUTPUT =A<I,1> ' = ' A<I,2>	:S(I)
END


Try it online!

Takes input separated by newlines with spaces between the =, and returns in the same format.

# Ruby, 63 bytes

def f(a)
a.reduce({}){|m,x|*r,k=x
r.map{|y|m[y]=m[k]||k}
m}
end


Try it online!

I rarely golf in Ruby (tips appreciated) but I use it for work, and I liked Artemis's clean answer so much that I decided to see what a translation into ruby would look like.

# JavaScript, 52 88 bytes

+36 bytes to handle a single fecking edge case :\

a=>a.map(a=>a.map(k=>o[0+k]=o[0+v]|v,v=a.pop()),o={})&&JSON.stringify(o).split0.join


Try it online!

• Unfortunately JS is the wrong language for this challenge because of its 'dicts are objects' paradigm: __proto__=1 fails. – Sisyphus Sep 22 '20 at 7:26
• @Sisyphus, I would assume that we don't have to handle variable names that aren't valid in our chosen language. Otherwise this is going to be impossible in a hell of a lot of languages. – Shaggy Sep 22 '20 at 8:35
• @Shaggy Multiple answers have been invalidated because they use eval, exec or similar and thus fail for keywords. – Artemis Sep 22 '20 at 12:30
• @Artemisstilldoesn'ttrustSE, well feck it, anyway :( Implemented a quick and far too bulky fix for now. – Shaggy Sep 22 '20 at 16:18
• @RedwolfPrograms, that's what the 0s are doing. I'm prepending a 0 to each variable name in the map and then later removing them with the split & join. – Shaggy Sep 22 '20 at 16:34

# Retina 0.8.2, 85 bytes

G=
+=(.+(=.+))
$2¶$1
+rm(^\4=(.+)¶(.+¶)*?.+=)(.+)1$2 +m^(.+)=.+¶((.+¶)*\1=)$2


Try it online! Link includes test suite that converts the input from comma separated to newline separated assignments with no spaces. Explanation:

G=


Ignore statements that have no assignments.

+=(.+(=.+))
$2¶$1


Split up assignment chains into individual assignments.

+rm(^\4=(.+)¶(.+¶)*?.+=)(.+)1$2  Substitute the values of variables used on the right-hand side of assignments. The matching is performed right-to-left so that the most recent value is used. +m^(.+)=.+¶((.+¶)*\1=)$2


Remove superseded assignments.

# Java 10, 137 bytes

a->{var r=new java.util.TreeMap();for(var p:a)for(int l=p.length-1,i=l;i-->0;)r.put(p[i],p[l]instanceof Long?p[l]:r.get(p[l]));return r;}


Input as a Object-matrix (variables as Strings, values as Longs), output as a sorted HashMap.

Try it online.

Explanation:

a->{                            // Method with Object-matrix parameter & TreeMap return
var r=new java.util.TreeMap();//  Create the result sorted HashMap
for(var p:a)                  //  Loop over each Object-list of the input-matrix:
for(int l=p.length-1,       //   Integer l, set to the last index of the list
i=l;i-->0;)             //   Inner loop i in the range (length-1, 0]:
r.put(                    //    Add to the result TreeMap:
p[i],                  //     The i'th value of the list as key
p[l]instanceof Long?   //     If the last item is a Long:
p[l]                  //      Use that last item as value
:                      //     Else:
r.get(p[l]));         //      Get the value of this last item from the
//      result-Map, and use that as value
return r;}                    //  Return the resulting TreeMap (sorted HashMap)


# Red, 74 69 bytes

func[b][context collect[forall b[if set-word? first t: b/1[keep t]]]]


Try it online!

Takes the input as a list of lists, in each of them = replaced with : (Red has set-words rather than assignment operator)

# Scala, 98 bytes

_./:(Map[String,String]()){case(m,a::b)=>val x=m.getOrElse(a,a);(m/:b.map(_->x))(_+_)case(m,_)=>m}


Try it online!

The statements have to be reversed (List("2","b") for "b=2"). The solutions below can't handle empty input.

# Scala, 96 94 bytes

_./:(Map[String,String]()){(m,l)=>val x=m.getOrElse(l.last,l.last);(m/:l.init.map(_->x))(_+_)}


Try it online!

Takes a List[List[String]] and returns a Map[String,String]

## Scala, 86 bytes

This is shorter, but the statements are reversed

_./:(Map[String,String]()){case(m,a::b)=>val x=m.getOrElse(a,a);(m/:b.map(_->x))(_+_)}


Try it online!

# 05AB1E, 30 29 bytes

εRćÐþÊiU¯ʒXk_}θθ}δ‚€ˆ}¯.¡н}€θ


Ugh.. :/ Not the right language for the job.

Input as a list of lists.

Explanation:

ε              # For each list in the (implicit) input-list:
R             #  Reverse the list
ć            #  Extract its head; pop and push remainder-list and first item separated
#  to the stack
Ð           #  Triplicate this value
þ          #  Pop one copy, and only leave its digits
Êi        #  If the top two copies are NOT the same (so it's not an integer):
U       #   Pop and store the last copy in variable X
¯      #   Push the global_array
ʒ     #   Filter it by:
Xk   #    Where the index of X
_  #    Is 0 (thus the key of the pair)
}θ    #   After the filter: leave the last pair
θ   #   Pop and leave its value
}        #  Close the if-statement
δ       #  For each value in the remainder-list:
‚      #   Pair it with the top value
€     #  Then for-each pair in this list:
ˆ    #   Add this pair to the global_array
}¯             # After the outer for-each: push the global_array
.¡           # Group this list of pairs by:
н          #  Its first value (the key)
}€          # After the group-by: map over each group:
θ         #  And only leave the last pair
# (after which the top of the stack is output implicitly as result)