Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Locked thread
Opinion Haver
Apr 9, 2007

Honestly, that's an odd enough request that I'm curious as to why exactly you want to do this. I'm not saying it's wrong, I'm just curious.

Adbot
ADBOT LOVES YOU

ArcticZombie
Sep 15, 2010
What's your opinions on Python GUI toolkits? I use Linux at home and all Macs at work so I'd like to have good compatibility between those, if that's even an issue anymore, the searching I did turned up some real old results so I don't know what to believe. There was some stuff about Tkinter (I think) looking like crap in comparison but I think it has had an update in that respect. So, Tkinter, PyGTK, wxPython or any others?

Posting Principle
Dec 10, 2011

by Ralp
PySide is pretty nice. Especially since (almost) all existing PyQt tutorials and docs are valid for it.

tef
May 30, 2004

-> some l-system crap ->

QuarkJets posted:

In Python 2.6 I want to take a dictionary and convert all of the keys, which are strings with various cases, to lowercase strings. Right now I am doing it like this:

code:
dict_low = dict((k.lower(), v) for k, v in olddict.items()) 


I just want to change the keys to lowercase, not create a second copy of the dictionary in memory.

Why? You're going to have to create new keys for each value, and the values won't be duplicated between the old and new dicts. Reinserting the keys and deleting them will probably be slower than just creating a new dict, and more cumbersome.

quote:

Does this code do that, and how can I check that this is the case?

You probably want to use .iteritems() rather than .items().

tef
May 30, 2004

-> some l-system crap ->

Jerry SanDisky posted:

PySide is pretty nice. Especially since (almost) all existing PyQt tutorials and docs are valid for it.

The big differences iirc are that PySide uses the new style slots, and also handles qvariants without having to mangle sip.

QuarkJets
Sep 8, 2008

yaoi prophet posted:

Honestly, that's an odd enough request that I'm curious as to why exactly you want to do this. I'm not saying it's wrong, I'm just curious.

I am taking dictionaries as input and trying to read values with specific keys, but there are sometimes inconsistencies in capitalization. Sometimes the dictionaries can be large, but I do have plenty of memory available; I just want to write effective code that doesn't waste time and memory making huge dictionary copies

So I think that what I will do is check whether a key is already lower case, and if it isn't then I'll create a new lowercase key with the old value. That would be better than a full dictionary copy, I think

Emacs Headroom
Aug 2, 2003

QuarkJets posted:

I am taking dictionaries as input and trying to read values with specific keys, but there are sometimes inconsistencies in capitalization. Sometimes the dictionaries can be large, but I do have plenty of memory available; I just want to write effective code that doesn't waste time and memory making huge dictionary copies

So I think that what I will do is check whether a key is already lower case, and if it isn't then I'll create a new lowercase key with the old value. That would be better than a full dictionary copy, I think

It would probably only be better if you have a relatively low proportion on non-lower case keys and making new keys is a rare operation. You can't "change" a key, since that would also change its hash value, you can only make new keys and delete old ones. So it might end up being like 6-of-one or half-dozen of another when choosing between the solutions. You can always profile with your data to see if there's a winner just to make sure.

QuarkJets
Sep 8, 2008

Emacs Headroom posted:

It would probably only be better if you have a relatively low proportion on non-lower case keys and making new keys is a rare operation. You can't "change" a key, since that would also change its hash value, you can only make new keys and delete old ones. So it might end up being like 6-of-one or half-dozen of another when choosing between the solutions. You can always profile with your data to see if there's a winner just to make sure.

Yeah, having to create a lowercase key is relatively uncommon (most come lowercased already). I suppose that I could run some tests and find out for sure whether one way or the other is actually faster on average for my data

QuarkJets fucked around with this message at 02:02 on Feb 12, 2013

QuarkJets
Sep 8, 2008

Thinking deeper, I should specify that the dictionary values are actually numpy arrays each with 1k to 1B entries. There are maybe only 100 keys in the dictionaries, really it's these arrays that are large.

When I create a new key and give it the same value as the old key, then a reference gets passed and I am not actually creating any new arrays in memory, correct? So I don't really even need to worry about deleting the old keys since minimal memory is used by two keys both pointing to the same array

Emacs Headroom
Aug 2, 2003
Yeah I believe that's correct. Assignment is naming, you're just giving these arrays a new name (the member of the dictionary indexed by 'butts' or what have you).

The Insect Court
Nov 22, 2012

by FactsAreUseless

QuarkJets posted:

Thinking deeper, I should specify that the dictionary values are actually numpy arrays each with 1k to 1B entries. There are maybe only 100 keys in the dictionaries, really it's these arrays that are large.

When I create a new key and give it the same value as the old key, then a reference gets passed and I am not actually creating any new arrays in memory, correct? So I don't really even need to worry about deleting the old keys since minimal memory is used by two keys both pointing to the same array

Unless you're going to be vastly increasing the number of keys, it shouldn't be an issue. That said, you can pretty easily do:
code:
for k, v in myDict:
    myDict[transform(k)] = v
    toBeDeleted.append(k)
As long as a value in the dictionary has some key attached to it, it shouldn't get GC'ed. And getting rid of the old keys means you can iterate through the dictionary without having to write code to check the case of the keys.

The Insect Court fucked around with this message at 03:04 on Feb 12, 2013

QuarkJets
Sep 8, 2008

The Insect Court posted:

Unless you're going to be vastly increasing the number of keys, it shouldn't be an issue. That said, you can pretty easily do:
code:
for k, v in myDict:
    myDict[transform(k)] = v
    toBeDeleted.append(k)
As long as a value in the dictionary has some key attached to it, it shouldn't get GC'ed. And getting rid of the old keys means you can iterate through the dictionary without having to write code to check the case of the keys.

Excellent, thanks guys!

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe

The Insect Court posted:

Unless you're going to be vastly increasing the number of keys, it shouldn't be an issue. That said, you can pretty easily do:
code:
for k, v in myDict:
    myDict[transform(k)] = v
    toBeDeleted.append(k)
As long as a value in the dictionary has some key attached to it, it shouldn't get GC'ed. And getting rid of the old keys means you can iterate through the dictionary without having to write code to check the case of the keys.

What is this "toBeDeleted"? Why do the deletions in a separate step? You can just do them as you go. Nothing is going out of scope within the loop, v is still a reference to the dictionary element.

JetsGuy
Sep 17, 2003

science + hockey
=
LASER SKATES

Hammerite posted:

What is this "toBeDeleted"? Why do the deletions in a separate step? You can just do them as you go. Nothing is going out of scope within the loop, v is still a reference to the dictionary element.

The only reason I can see for doing something like that is if you wanted an array of the deleted values before you deleted them. Even then, I agree that they should be deleted as you go.

The Insect Court
Nov 22, 2012

by FactsAreUseless

Hammerite posted:

What is this "toBeDeleted"? Why do the deletions in a separate step? You can just do them as you go. Nothing is going out of scope within the loop, v is still a reference to the dictionary element.

You could, but you'd need some way to make certain that the transformed key isn't the same as the original one, otherwise you could lose entries. So something like:

code:
for k, v in myDict:
    _k = transform(k)
    if _k != k:
        myDict[_k] = v;
        del myDict[k]

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe

The Insect Court posted:

You could, but you'd need some way to make certain that the transformed key isn't the same as the original one, otherwise you could lose entries. So something like:

code:
for k, v in myDict:
    _k = transform(k)
    if _k != k:
        myDict[_k] = v;
        del myDict[k]

This is literally what I posted already, in slightly greater generality.

My Rhythmic Crotch
Jan 13, 2011

Thought I would share this, although it's more of an OS level thing than a Python specific thing.

I was trying to make an accurate rate limiting function, albeit just a simple one, and I found that I could never get good accuracy out of it. After poking around a bit, I realized it must be due to the accuracy of time.sleep(), which I was using to limit rates by blocking code execution. So here is a nifty graph of the percent error for small sleep times using time.sleep().



Machine used was OS X 10.7.4. We now return to your regularly scheduled programming.

babyeatingpsychopath
Oct 28, 2000
Forum Veteran


My Rhythmic Crotch posted:

Thought I would share this, although it's more of an OS level thing than a Python specific thing.

I was trying to make an accurate rate limiting function, albeit just a simple one, and I found that I could never get good accuracy out of it. After poking around a bit, I realized it must be due to the accuracy of time.sleep(), which I was using to limit rates by blocking code execution. So here is a nifty graph of the percent error for small sleep times using time.sleep().



Machine used was OS X 10.7.4. We now return to your regularly scheduled programming.

Isn't that due to time.sleep() giving up the process's current timeslice, so the jitter is due to context swaps? Did you find a function that will give you a specific wall-clock delay? Something like ctypes' nanosleep() or clock_nanosleep(), the latter with time.perf_counter().

JetsGuy
Sep 17, 2003

science + hockey
=
LASER SKATES

My Rhythmic Crotch posted:

Thought I would share this, although it's more of an OS level thing than a Python specific thing.

I was trying to make an accurate rate limiting function, albeit just a simple one, and I found that I could never get good accuracy out of it. After poking around a bit, I realized it must be due to the accuracy of time.sleep(), which I was using to limit rates by blocking code execution. So here is a nifty graph of the percent error for small sleep times using time.sleep().



Machine used was OS X 10.7.4. We now return to your regularly scheduled programming.

This might be fun for us all to test. Could you post the source of the test? I can even add a quick matplotlib block so people don't have to dump it into excel to make the graph.

EDIT:
Nevermind, I realized how stupidly easy this is to code, I'll write one up quick and post it.

JetsGuy fucked around with this message at 19:32 on Feb 13, 2013

My Rhythmic Crotch
Jan 13, 2011

babyeatingpsychopath posted:

Isn't that due to time.sleep() giving up the process's current timeslice, so the jitter is due to context swaps? Did you find a function that will give you a specific wall-clock delay? Something like ctypes' nanosleep() or clock_nanosleep(), the latter with time.perf_counter().
I'm not sure what's actually happening when time.sleep() is called. I have done some realtime stuff in the past, so I know how things generally work in realtime systems when timing is really critical, but no clear idea in this case.

Most people seem to recommend just somehow wrapping or calling nanosleep() - I haven't got around to it yet but I'll try to come up with something.

JetsGuy, it is easy to code, but about halfway down on this page, there is a neat implementation that uses Python's decorator syntax, which I had previously never used.

JetsGuy
Sep 17, 2003

science + hockey
=
LASER SKATES

My Rhythmic Crotch posted:

JetsGuy, it is easy to code, but about halfway down on this page, there is a neat implementation that uses Python's decorator syntax, which I had previously never used.

I didn't use this, and the only reason I haven't posted my source yet is because I'm about a third of the way done my own test. Want to be sure it (kinda) works before I post it! :v:

If we're gonna compare, it's probably best to use the same timing methods.

EDIT:
The test range is 1000 test times, and for each test time, it is averaging over 1000 trials. So it takes a little while, even if the range is 1e-4 to 1e-2. :)

EDIT 2:
Can you tell its a slow work day?

JetsGuy fucked around with this message at 21:24 on Feb 13, 2013

My Rhythmic Crotch
Jan 13, 2011

I tried my same rate limiting code on a different machine, and guess what, it worked much better.

Quad core linux box with 2.6.something kernel with PREEMPT: 1-2% away from requested rate.
Core i5 Macbook Air: 20% under requested rate.

Anyway, I looked around and found this, and was able to get it to compile for Linux but not OS X. So here is a new graph of time.sleep vs nanosleep running on the Linux rig:



Next I could try a kernel without PREEMPT and see if the performance goes to poo poo or not.

[Edit] Yes, your way is much better. It would be cool to do a more thorough treatment of the data. I'm just kind of screwing around right now.

My Rhythmic Crotch fucked around with this message at 23:17 on Feb 13, 2013

Drunk Badger
Aug 27, 2012

Trained Drinking Badger
A Faithful Companion

Grimey Drawer
Is it possible to randomly call a function by using a variable without using a bunch of if statments?

For example, I have function1, function2, function3, etc. I'd have a random integer 'X' selected from 1-3. X=1 runs function1(), X=2 runs function2(), etc. Is there some sort of way to do this all on one line that runs functionX()?

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

Drunk Badger posted:

Is it possible to randomly call a function by using a variable without using a bunch of if statments?

For example, I have function1, function2, function3, etc. I'd have a random integer 'X' selected from 1-3. X=1 runs function1(), X=2 runs function2(), etc. Is there some sort of way to do this all on one line that runs functionX()?

Yeah, you can do that:

code:
In [1]: import random

In [2]: def foo():
   ...:     print "foo"
   ...:     

In [3]: def bar():
   ...:     print "bar"
   ...:     

In [4]: funcs = [foo, bar]

In [5]: funcs
Out[5]: [<function __main__.foo>, <function __main__.bar>]

In [6]: funcs[0]()
foo

In [7]: funcs[1]()
bar

In [8]: for i in range(10):
   ...:     funcs[random.randrange(2)]()
   ...:     
bar
bar
bar
bar
foo
foo
foo
foo
bar
bar

JetsGuy
Sep 17, 2003

science + hockey
=
LASER SKATES

My Rhythmic Crotch posted:

[Edit] Yes, your way is much better. It would be cool to do a more thorough treatment of the data. I'm just kind of screwing around right now.

It's the scientist in me, I can't help but to be like "yes, but we should AVERAGE over MANY N!". :v: Anyway, I wanna stab a bitch, because I got to the end of the test only to find out I forgot to put in a line to stop python from just closing out the graph without saving it.

:argh:

Also, I CONSTANTLY I'm typoing python as pythong. yikes.

Qtotonibudinibudet
Nov 7, 2011



Omich poluyobok, skazhi ty narkoman? ya prosto tozhe gde to tam zhivu, mogli by vmeste uyobyvat' narkotiki

Drunk Badger posted:

Is it possible to randomly call a function by using a variable without using a bunch of if statments?

For example, I have function1, function2, function3, etc. I'd have a random integer 'X' selected from 1-3. X=1 runs function1(), X=2 runs function2(), etc. Is there some sort of way to do this all on one line that runs functionX()?

code:
dict(zip((1,2,3),(function1, function2, function3)))[random.randrange(1,4)]
For even more fun, make the functions anonymous and define them within the tuple that gets zipped up!

Drunk Badger
Aug 27, 2012

Trained Drinking Badger
A Faithful Companion

Grimey Drawer
I think I need to add some more information now that I have a better idea what I'm doing.

In each function, that function returns a random number itself. So what I have is a random number deciding what function gets called that produces another random number. So function1 randomly returns it's own set of random numbers, function2 randomly returns a different set of numbers, etc.

While the examples do produce a result, I'm finding that every function ends up returning the same result as it's called. function1 returns 1 the entire time on one run of the script, and always returns 2 on another run of the same script.

It seems to run the function once, save that value as a permanent result, and just return that number. Instead, I want it to run the function and possibly give me something different.

Suspicious Dish
Sep 24, 2011

2020 is the year of linux on the desktop, bro
Fun Shoe

JetsGuy posted:

Also, I CONSTANTLY I'm typoing python as pythong. yikes.

http://www.pythong.org/

Hed
Mar 31, 2004

Fun Shoe
Welcome to the Pythong Package Index.

JetsGuy
Sep 17, 2003

science + hockey
=
LASER SKATES

Well then.

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe

Drunk Badger posted:

I think I need to add some more information now that I have a better idea what I'm doing.

In each function, that function returns a random number itself. So what I have is a random number deciding what function gets called that produces another random number. So function1 randomly returns it's own set of random numbers, function2 randomly returns a different set of numbers, etc.

While the examples do produce a result, I'm finding that every function ends up returning the same result as it's called. function1 returns 1 the entire time on one run of the script, and always returns 2 on another run of the same script.

It seems to run the function once, save that value as a permanent result, and just return that number. Instead, I want it to run the function and possibly give me something different.

I'm not sure what it is you're trying to work towards, but here is something silly I put together quickly. Maybe it will help you, IDK.

Python code:
import random

def functionmaker (myfunction):
    def returnvalue ():
        return myfunction(random.random())
    return returnvalue

def f0 (x):
    return 0 if x < 0.2 else 1 if x < 0.4 else 2

def f1 (x):
    return 0 if x < 0.5 else 1 if x < 0.75 else 2

def f2 (x):
    return 0 if x < 0.3 else 1 if x < 0.8 else 2

funcs = list(map(functionmaker, [f0, f1, f2]))

def silly ():
    y = 0
    for i in range(10):
        mylist = []
        for j in range(10):
            y = funcs[y]()
            mylist.append(str(y))
        print(' '.join(mylist))

Qtotonibudinibudet
Nov 7, 2011



Omich poluyobok, skazhi ty narkoman? ya prosto tozhe gde to tam zhivu, mogli by vmeste uyobyvat' narkotiki

Drunk Badger posted:

I think I need to add some more information now that I have a better idea what I'm doing.

In each function, that function returns a random number itself. So what I have is a random number deciding what function gets called that produces another random number. So function1 randomly returns it's own set of random numbers, function2 randomly returns a different set of numbers, etc.

While the examples do produce a result, I'm finding that every function ends up returning the same result as it's called. function1 returns 1 the entire time on one run of the script, and always returns 2 on another run of the same script.

It seems to run the function once, save that value as a permanent result, and just return that number. Instead, I want it to run the function and possibly give me something different.

Post code that's breaking. Screwing with the random seed usually shouldn't be necessary.

babyeatingpsychopath
Oct 28, 2000
Forum Veteran


Python code:
from random import *
funcs=[betavariate,gammavariate,gauss,lognormvariate,normalvariate,randint,uniform,vonmisesvariate,weibullvariate]
for i in range(30):
    func=choice(funcs)
    a,b=sample(range(1,30),2)
    a,b=min(a,b),max(a,b)
    funcname=str(func).split()[2] # '<bound method xxxx .....'
    print(funcname,a,b,func(a,b))
This works great. These are all the two-parameter random functions, and I feed random parameters into them randomly.

JetsGuy
Sep 17, 2003

science + hockey
=
LASER SKATES
Ok. I ran it *again* and found out something fun about the system I was running it on.

The minimum sleep time on that machine was ~0.01s!

So all those trials were taking entirely too long because despite what i was telling sleep to do, it was doing a sleep time of 0.01! Anyway, I ported the code over to a Lion machine (the other machine was Snow Leopard). And here's what I got, it's very similar to My Rhythmic Crotch.



I added a quick check to catch (most) cases where your resolution of time won't be tested because of your OS's sleep limits. I'm running a much more fine resolution one now today. I'm expecting the same results, but with those spikes completely gone.

And here's the source:
code:
import time
import timeit
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import sys

#setup testing range
t_start = 1e-4
t_stop = 1e-2
N_events = 1e3
sleep_values = np.linspace(t_start, t_stop, N_events)

#Events Per Test
N_attempts = 1e3

#How Often to Tell User The Code Is Still Running
N_status = 100

#perform quick check to see if system can handle resolution user wants
resolution = np.abs(sleep_values[1] - sleep_values[0])
check_res = timeit.timeit('time.sleep(sleep_chk)', number=1, setup = "sleep_chk = %f" % (resolution,))
if check_res > resolution*2:
	sys.exit("it appears your OS prevents you from running a sleep this precise!\n")

pct = []
counter = 0
for target in sleep_values:
    trial_time = timeit.timeit('time.sleep(target)', number=int(N_attempts),
                               setup="target = %f" % (target,)) / N_attempts

    residual = np.abs(trial_time - target)
    trial_err = residual/target

    pct.append(trial_err)

    counter += 1
    if counter%N_status == 0:
        print "Completed",str(counter),"tests."


pct = np.array(pct)*100.
fig = plt.figure(1, figsize=(12,8))
ax1 = fig.add_subplot(111)
ax1.plot(sleep_values, pct, marker="None",drawstyle="steps-mid",color="blue")
ax1.set_xlabel("Intended Sleep Time (s)")
ax1.set_ylabel("Abs. Error (Pct)")
plt.show()
#This is required to keep the plot up on some systems.
raw_input("Press Enter to exit...\n")

JetsGuy fucked around with this message at 16:24 on Feb 14, 2013

Emacs Headroom
Aug 2, 2003

JetsGuy posted:

Ok. I ran it *again* and found out something fun about the system I was running it on.

The minimum sleep time on that machine was ~0.01s!

I think that might have to do more with the kernel sleep function than with Python. Probably the kernel doesn't want to guarantee that a thread can have control back within 10ms. Maybe it's different for real-time kernels?

JetsGuy
Sep 17, 2003

science + hockey
=
LASER SKATES

Emacs Headroom posted:

I think that might have to do more with the kernel sleep function than with Python. Probably the kernel doesn't want to guarantee that a thread can have control back within 10ms. Maybe it's different for real-time kernels?

Yeah, that's what I figured too. For reference, it turns out on my Lion machine, the minimum sleep time resolution seems to be ~1e-4. I'm somewhat surprised though that there's not much of a warning there.

In any case, on my Lion machine (10.7.5), the minimum seems to be around 1e-4:

code:
n [41]: timeit.timeit('time.sleep(1e-5)',number=1)
Out[41]: 4.792213439941406e-05

In [42]: timeit.timeit('time.sleep(3e-5)',number=1)
Out[42]: 0.00013399124145507812

In [43]: timeit.timeit('time.sleep(5e-5)',number=1)
Out[43]: 7.510185241699219e-05

In [44]: timeit.timeit('time.sleep(6e-5)',number=1)
Out[44]: 7.796287536621094e-05

In [45]: timeit.timeit('time.sleep(8e-5)',number=1)
Out[45]: 0.0001010894775390625

In [46]: timeit.timeit('time.sleep(1e-4)',number=1)
Out[46]: 0.00012183189392089844
That dumb catch I wrote will therefore not catch those since they're not twice the sleep time. Tread with caution.

JetsGuy fucked around with this message at 16:46 on Feb 14, 2013

Drunk Badger
Aug 27, 2012

Trained Drinking Badger
A Faithful Companion

Grimey Drawer

fivre posted:

Post code that's breaking. Screwing with the random seed usually shouldn't be necessary.

Here's an example from a routing simulator I'm building.

code:
class Node:
    def __init__(self, r):
        self.routes = r;
        self.hopcount = 0;

def nexthop(routes):
    return routes[random.randrange(0,len(routes))];

node1 = Node([2]);
node2 = Node([1,3,4,5]);
node3 = Node([2,4,5]);
node4 = Node([2,3,5]);
node5 = Node([2,3,4,6]);
node6 = Node([5]);

noderoute = [nexthop(node1.routes), nexthop(node2.routes), nexthop(node3.routes), nexthop(node4.routes), nexthop(node5.routes), nexthop(node6.routes)]

while 1 == 1:
    print(noderoute[0]);
    print(noderoute[1]);
    print(noderoute[2]);
    print(noderoute[3]);
    print(noderoute[4]);
    print(noderoute[5]);
    print("end");
What I want it to do is give me a new set of numbers on each cycle of the while loop. Running that just produces the same random numbers while the program is run.

Output posted:

2
3
5
2
4
5
end
2
3
5
2
4
5
end
2
3
5
2
4
5
end

It seems to be running the function for each element in 'noderoute = [...', once it gets to that line of code, as I can put a print function and it will display 6 lines of text before it starts putting out the numbers. I know I can just do a bunch of if loops, but I'm wondering if there's a shorter way to do what I want.

BeefofAges
Jun 5, 2004

Cry 'Havoc!', and let slip the cows of war.

The functions you're calling are being evaluated when you assign that list to noderoute, not when you're printing different indexes of noderoute. What you want to do is put the functions themselves in the list, and then call the functions when you iterate through the list in your loop.

Lysidas
Jul 26, 2002

John Diefenbaker is a madman who thinks he's John Diefenbaker.
Pillbug
What are those strange ; characters at the end of your lines of code?

Adbot
ADBOT LOVES YOU

Drunk Badger
Aug 27, 2012

Trained Drinking Badger
A Faithful Companion

Grimey Drawer

Lysidas posted:

What are those strange ; characters at the end of your lines of code?

Things I don't know if I really need because I'm new to this and have no formal training?

  • Locked thread