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.
 
  • Post
  • Reply
Wallet
Jun 19, 2006

nullfunction posted:

sorted() gives you the sorted keys, not key-value pairs.

I mean, fundamentally if you iterate over a dictionary directly it gives you a list so passing sorted a dict to iterate over is going to return the list. If you give it d.items() you get them back as tuples but you can redict if you want to.

Python code:
s = dict(sorted(d.items()))

Adbot
ADBOT LOVES YOU

nullfunction
Jan 24, 2005

Nap Ghost
Neat, that's definitely nicer to look at than a dict comprehension. I guess I've never really thought about lists of tuples being transformed back to a dict like that, but it makes a lot of sense.

Dawncloack
Nov 26, 2007
ECKS DEE!
Nap Ghost
I have heard that python isn't the best language for concurrency, parallelism and all that jazz, but I am trying to learn it, so I decided to do some experimentation.

I have created a backuper script to do my regularly scheduled backup. It's an implementation of rsync, meaning that, if something is already there and hasn't been modified, then it's not copied again. This are the key lines of code:

code:
if not (os.path.exists(targetfile) ) or (os.path.getmtime(sourcefile) > (os.path.getmtime(targetfile) ) ):
                            
                    copy2(sourcefile,targetfile)
The little experiment I performed consists in running each of the three versions of the script twice. The first time, USB key is empty, then a series of directories are backed up, then I run the script again so that the script is just going to check that every file is there, but not copy anything.

The multiprocess version has this as the key code:

code:
with multiprocessing.Pool(processes=3) as pool:
        
            pool.map(self.dobackup, self.linux_dirs)
            
            pool.close()
            pool.join() 
The multithreaded version has this

code:
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as pool:
        
                pool.map(self.dobackup, linux_dirs)
This are the results I obtain on a USB key. You can see the two passes of the script. The real time might be one or two seconds higher than strictly running time because there's a choice to make and I was reading forums while I ran the experiment, so perhaps system time is more interesting. I am, obviously, using the time function of linux.

code:
One process - one thread

real		0m46,884s
user		0m0,182s
sys	    	0m0,415s

real		0m48,245s
user		0m0,223s
sys	    	0m0,370s

multiprocess

real		2m2,564s
user		0m0,175s
sys	    	0m1,367s

real		0m41,649s
user		0m0,190s
sys	    	0m0,463s

multithreding

real		2m10,094s
user		0m0,267s
sys	    	0m1,253s

real		0m43,884s
user		0m0,254s
sys	    	0m0,329s
Same experiment ran in my SDD

code:
One process - one thread

real		0m0,959s
user		0m0,093s
sys	    	0m0,124s

real		0m1,189s
user		0m0,106s
sys	    	0m0,109s

multiprocess

real		0m2,158s
user		0m0,207s
sys	    	0m0,471s

real		0m1,248s
user		0m0,126s
sys	    	0m0,128s

multithreading

real		0m2,235s
user		0m0,216s
sys	    	0m0,435s

real		0m1,195s
user		0m0,125s
sys	   	 0m0,092s
With one exception, being the second pass (only checking whether files changed) of the multithreaded version on my SDD,all of the others tries took more system time and more user time than the purely sequential, one-process version.

So... why is that ? Part of the reason of running this experiment was to get some empirical results that would show me whether working with memory is more of an IO problem or a CPU problem.

My ideas:

- Maybe memory a different thing, parallelism/concurrency is not really possible because of the von Neumann chokehold and all I am doing is adding needless complexity?
- Maybe I have just programmed wrong and I am using threads and processes like an rear end?
- Maybe I don't know how to read the results right?
- Maybe I should hang my head in shame and go do crossstitch instead?
- Other?

Thanks for any suggestions and comments.

NinpoEspiritoSanto
Oct 22, 2013




There's a lot of guessing in my reply because you haven't simply shared all of the code. However, I assume "dobackup" is the entire process of walking the filesystem, running the comparison and optionally copying the file. In this case all you're doing in the multiprocessing and multithreaded runs is asking that process to run 3 times so I am unsurprised you saw an increase in time on the first run as it's likely you ran into a race condition with the if path exists check and the files were copied at least twice if not thrice. This would make sense with the subsequent runs as they're all roughly the same time.

To make use of concurrency here you'd want to walk the source first and have that data set available, at that point you could iterate over the file list and distribute it to parallel workers/async tasks, e.g. (with trio):

code:
async with trio.open_nursery() as rsync_nursery:
    for file in walked_file_list:
        rsync_nursery.start_soon(async_filecheck_and_copy, file)
Or to take your examples:

code:
with multiprocessing.Pool(processes=3) as pool:
    for linux_dir in self.linux_dirs:
        pool.map(self.dobackup, self.linux_dir)
    pool.close()
    pool.join() 
code:
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as pool:
    for linux_dir in self.linux_dirs:
        pool.map(self.dobackup, linux_dirs)
Without seeing all the code for dobackup I don't know what, if any changes you'd need there. Make sure you're using os.walk for any operations through directories as it's the most performant. It's also unclear why you need a class for this.

In your code you're asking python to do the entire operation three times. What you want to do is break the workload up into three pieces.

If you want to look at the async approach for concurrency I recommend trio and it has documented help on file operations on their readthedocs page.

Dawncloack
Nov 26, 2007
ECKS DEE!
Nap Ghost
To answer your question, every thread/process is given a directory, and isbin charge of walking/copying that entire directory. Linux_dirs is a tuple of directory names.

There's no reason for it to be a class (actually a function of a class iirc) other than forcing myself to do things the object-oriented way to practice it.

I *think* processes are given only one directory and no directory is processed twice, but I am very open to the idea that I did it wrong and/or that there's more optimal ways.

I'm also gonna try your suggestions! Thanks!

QuarkJets
Sep 8, 2008

Instead of multiprocessing use concurrent.futures.ProcessPoolExecutor

It won't solve the problem you see but it's the new thing

QuarkJets
Sep 8, 2008

I also agree with NinpoEspiritoSanto, without seeing what the underlying code is actually doing it's difficult to say what the problem could be. I think the best approach would be to walk with the main process to send changed files to a Queue, and then transfer them with one or two other processes. It would be good to see the current underlying implementation though, because I think your design should see a small performance boost with the SSD in the multiprocessing case

Please show more code

Dawncloack
Nov 26, 2007
ECKS DEE!
Nap Ghost
Ok, more code!

This is the main function that does the walking. It is fed one of the folders that I want to back up and simply walks it, checks for existence, then copies or jumps.

There's one instance of this function in the sequential version, working one folder at a time. In the multiprocessed and multithreaded versions, this function is mapped, in the process/thread pool with a tuple of directory names.

code:
def dobackup(self, dir2backup):
                
        for (subdir, dirs, files) in os.walk(dir2backup):
        
            dir2backupbase = os.path.dirname(dir2backup) # This is so that the files that are fed have their own folders, instead of mixing all of the base levels together.

            targetdir = os.path.join(self.targetrootdir, os.path.relpath(subdir, dir2backupbase), '' ) 
                
            if not os.path.exists(targetdir):
                os.mkdir(targetdir)
                
            for file in files:

                targetfile = os.path.join(targetdir, file)
                sourcefile = os.path.join(subdir, file)
                        # If the file is not in the backup, or the unbackuped version is newer, it is copied.
                if not (os.path.exists(targetfile) ) or (os.path.getmtime(sourcefile) > (os.path.getmtime(targetfile) ) ):
                            
                    copy2(sourcefile,targetfile)
                        
#In this second pass we check the files that are in the backup but not the original. They get placed in a folder called "Old". Note that it's at the same level of indentation as "for file in files"

            trashcandir = os.path.join(targetdir, 'old', '')
                
            if not os.path.exists(trashcandir):
                os.mkdir(trashcandir)   
                    
            for thing in os.listdir(targetdir):
            
                backup_file = os.path.join (targetdir, thing)
                original_file = os.path.join(subdir, thing)
                    
                if os.path.isfile( backup_file ) and not (os.path.exists( original_file ) ):
                            
                    copy2(backup_file, trashcandir + '/' + thing)
                    print(targetfile + ' moved to /old/')	
Then at the very end there's a "main" section. The sequential version is composed of a "for folder in <tuple of folders>; dobackup(folder)".

In the multithread/process version I create another function called "start" that launches the pools as I copied and pasted above. What I posted above is the entirety of the start function.

And if you are a glutton for punishment you can also check the entirety of my little script, including finding out which removable disks are mounted, mixing to distinct lists of directories and other silly stuff. Any comments and criticism welcome. Es mi dia primero, to quote Homer Simpson.

Sequential version
Multiprocess version
Multithreaded version

QuarkJets
Sep 8, 2008

That code is pretty hard to read

I decided to reimplement this problem in very simple terms: 10 GB of data moved around on a single NVMe drive

t = 33.7 s for single process
t = 33.5 s for multi process walk + transfer
t = 30.2 s for multi process transfer only

Why isn't the performance any better? Probably hardware reasons, I'd guess. I believe that the *nix rsync utility is a single threaded application for this reason


Python code:
from concurrent.futures import ProcessPoolExecutor
import os
from shutil import copy
from timeit import default_timer as timer

source_dir0 = whatever
dest_dir0 = whatever


def file_needs_backup(source_file, dest_file):
    if not os.path.exists(dest_file):
        return True
    if os.path.getmtime(source_file) > os.path.getmtime(dest_file):
        return True
    return False


def single_process_rsync(source_dir, dest_dir):
    for root, dirs, files in os.walk(source_dir):
        for dir_name in dirs:
            base_dir_source = os.path.join(root, dir_name)
            base_dir_dest = base_dir_source.replace(source_dir, dest_dir)
            if not os.path.isdir(base_dir_dest):
                os.mkdir(base_dir_dest)
        for file in files:
            target_file = os.path.join(root, file)
            dest_file = target_file.replace(source_dir, dest_dir)
            if file_needs_backup(target_file, dest_file):
                print(f'copying {target_file} to {dest_file}')
                copy(target_file, dest_file)
    return f'Done backing up {source_dir} to {dest_dir}'
                
                
def multi_process_walk_rsync(source_dir, dest_dir):
    source_subdirs = [os.path.join(source_dir, name) for name in os.listdir(source_dir) if os.path.isdir(os.path.join(source_dir, name))]
    dest_subdirs = [dir_name.replace(source_dir, dest_dir) for dir_name in source_subdirs]
    for dir_name in dest_subdirs:
        if not os.path.exists(dir_name):
            os.mkdir(dir_name)
    with ProcessPoolExecutor(max_workers=2) as executor:
        for future in executor.map(single_process_rsync, source_subdirs, dest_subdirs):
            print(future)


def multi_process_rsync(source_dir, dest_dir):
    target_files = []
    dest_files = []
    for root, dirs, files in os.walk(source_dir):
        for dir_name in dirs:
            base_dir_source = os.path.join(root, dir_name)
            base_dir_dest = base_dir_source.replace(source_dir, dest_dir)
            if not os.path.isdir(base_dir_dest):
                os.mkdir(base_dir_dest)
        for file in files:
            target_file = os.path.join(root, file)
            dest_file = target_file.replace(source_dir, dest_dir)
            if file_needs_backup(target_file, dest_file):
                target_files.append(target_file)
                dest_files.append(dest_file)
    
    with ProcessPoolExecutor(max_workers=2) as executor:
        for future in executor.map(copy, target_files, dest_files):
            pass
  

def main():
    start = timer()
    multi_process_rsync(source_dir0, dest_dir0)
    end = timer()
    print(f't = {end - start} s')


if __name__ == '__main__':
    main()

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug
I managed to land my first dev title (half dev, half my current specialty) in a mostly Python group at a big company. I know the first rule of software engineering is 'do what your team does as long as it's not insane' but are there any other tips in terms of like real-world stuff I should consider?

a foolish pianist
May 6, 2007

(bi)cyclic mutation

Falcon2001 posted:

I managed to land my first dev title (half dev, half my current specialty) in a mostly Python group at a big company. I know the first rule of software engineering is 'do what your team does as long as it's not insane' but are there any other tips in terms of like real-world stuff I should consider?

Get real familiar with the testing infrastructure first thing.

CarForumPoster
Jun 26, 2013

⚡POWER⚡

a foolish pianist posted:

Get real familiar with the testing infrastructure first thing.

“Internal group where the devs are half Python dev half something else” and “test infrastructure” have very little overlap ime

May still be good advice tho

Foxfire_
Nov 8, 2010

QuarkJets posted:

That code is pretty hard to read

I decided to reimplement this problem in very simple terms: 10 GB of data moved around on a single NVMe drive

t = 33.7 s for single process
t = 33.5 s for multi process walk + transfer
t = 30.2 s for multi process transfer only

Why isn't the performance any better? Probably hardware reasons, I'd guess. I believe that the *nix rsync utility is a single threaded application for this reason
Magnetic disk is much slower than solid state disk
Solid state disk is much slower than main RAM
Main RAM is much slower than L3 cache
L3 cache is slower than L2 cache
L2 is slower than L1
L1 is slower than CPU registers

Copying a file is essentially no CPU work if the underlying copy is implemented sanely. The CPU is telling the disk controller "Copy this sector of data to main memory, interrupt me when you're done", taking a nap, telling the disk controller to copy in the other direction and napping again, then repeating that till everything's copied. Having more or faster CPUs won't help because they're napping 99% of the time anyway

It's like you've got one guy with a shovel and ten people standing around telling him where to dig next. It's not faster than one guy with the shovel and one boss.


e: also, unrelatedly, you have to do import multiprocessing; multiprocessing.set_start_method('spawn') on Unix to get not-broken behavior. The default ('fork') violates fork()'s specification and may randomly deadlock depending on what other code in your process is doing.

Foxfire_ fucked around with this message at 04:36 on Oct 5, 2021

fuf
Sep 12, 2004

haha
Can anyone tell me why this works:

Python code:
cnopts = pysftp.CnOpts()
cnopts.hostkeys = None

with pysftp.Connection('host.net', username='goons', port=722, password='123', cnopts=cnopts) as sftp:
But this doesn't?

Python code:
cnopts = pysftp.CnOpts()
cnopts.hostkeys = None

cinfo = {
    'host': 'host.net',
    'username': 'goons',
    'port': 722,
    'password': '123',
    'cnopts': cnopts
}

with pysftp.Connection(cinfo) as sftp:
The second shows:
code:
Failed to load HostKeys from C:\Users\peepeepoopoo\.ssh\known_hosts.  You will need to explicitly load HostKeys (cnopts.hostkeys.load(filename)) or disableHostKey checking (cnopts.hostkeys = None).
I don't get the difference?

p.s. I know it's a bad idea to use hostKeys = None, this is just for some quick testing

Data Graham
Dec 28, 2009

📈📊🍪😋



Probably need to expand cinfo as kwargs, no?

Python code:
with pysftp.Connection(**cinfo) as sftp:

fuf
Sep 12, 2004

haha
Oh nice, that worked. Thank you!

Dawncloack
Nov 26, 2007
ECKS DEE!
Nap Ghost

QuarkJets posted:

That code is pretty hard to read

thanks everybody for your explanationa. I get now why it doesn't go any faster.

And, QJ, I'm going to read your example carefully and also read a book on writing better code. Sorry it was hard to read. If you have pointers on what not to dp, great, but as Ibsaid I'll read on the topic a bit.

QuarkJets
Sep 8, 2008

If you run flake8 it will give you all kinds of style suggestions and other modifications, it's good. Pycharm will do the same but will also make it easy to make changes.

CarForumPoster
Jun 26, 2013

⚡POWER⚡

QuarkJets posted:

If you run flake8 it will give you all kinds of style suggestions and other modifications, it's good. Pycharm will do the same but will also make it easy to make changes.

This!

D34THROW
Jan 29, 2012

RETAIL RETAIL LISTEN TO ME BITCH ABOUT RETAIL
:rant:
So just for my own edification, I wrote a quick and dirty script to encode things in a cryptogram-puzzle style format (random mapping based on ord() and chr()); I'm gonna post it here and ask for critiques on how to better handle it. It's all contained in a single python file cipher.py. My kid was interested in it so I did this :shobon:

Python code:
# Imports
import random

# Get the cleartext.
cleartext = input("Enter the message to encode: ").upper()
ascii_codes = []
shifted_codes = []
# Punctuation codes.
punctuation = []
for i in range(31, 65):
    punctuation.append(i)
for i in range(91, 97):
    punctuation.append(i)
# Convert it to ASCII codes.
for i in range(0, len(cleartext)):
    ascii_codes.append(ord(cleartext[i]))
    if ord(cleartext[i]) not in punctuation:
        shifted_codes.append(ord(cleartext[i])-65)
    else:
        shifted_codes.append(ord(cleartext[i]))
# Create the ciphermap and the clearmap.
clear_map = []
encryption_map = []
for i in range(65, 91):
    clear_map.append(i)
    encryption_map.append(i)
# Shuffle the encryption map.
for i in range(0, 6):
    random.shuffle(encryption_map)
# Map the shifted cleartext to the encryption map.
cipher_text = []
for i in range(0, len(shifted_codes)):
    if shifted_codes[i] not in punctuation:
        position = shifted_codes[i]
        cipher_text.append(encryption_map[position])
    else:
        cipher_text.append(shifted_codes[i])
# Convert it to the text.
for i in range(0, len(cipher_text)):
    cipher_text[i] = chr(cipher_text[i])
# Join it to a string.
ciphertext = ''.join(cipher_text)
print(f"\n{cleartext} = {ciphertext}")
Less interested in style and convention and more actual coding practices, like if there's a way to combine the two for loops for filling punctuation into one.

DoctorTristan
Mar 11, 2006

I would look up into your lifeless eyes and wave, like this. Can you and your associates arrange that for me, Mr. Morden?
code:
punctuation = [j for j in range(31,65)] + [j for j in range(91,97)]

HappyHippo
Nov 19, 2003
Do you have an Air Miles Card?

DoctorTristan posted:

code:
punctuation = [j for j in range(31,65)] + [j for j in range(91,97)]
code:
list(range(31,65)) + list(range(91,97))

QuarkJets
Sep 8, 2008

I recommend reading Clean Code by Robert Martin, which has a lot of good and timeless advice applicable to all software languages. For example, you should assign the magic number 65 to a well-named variable. And the comment that says "Imports" is useless, may as well remove it

You should try to use more list comprehension and more direct iteration, by which I mean try not to use an index as often. Like this:

Python code:

ord_shift = 65
ascii_codes = [ord(c) for c in cleartext]
shifted_codes = [c for c in ascii_codes if c in punctuation else c - ord_shift]

D34THROW
Jan 29, 2012

RETAIL RETAIL LISTEN TO ME BITCH ABOUT RETAIL
:rant:
That dumbass feeling you get when you realize why the logging module you spent so much time unfucking is hosed again and not spitting log_debug output to the stream handler like it should?

That dumbass feeling when you realize you had a DEBUG_MODE constant in the module that you weren't setting based on the --debug command-line argument?

Yeah, I know that feeling. :doh:

DearSirXNORMadam
Aug 1, 2009
Edit: lol nvm figured it out. underscore vs dash version name fuckery. x_x

too simple to leave up for posterity

DearSirXNORMadam fucked around with this message at 20:04 on Oct 14, 2021

mr_package
Jun 13, 2000
Is there a quick fix in PyCharm for type inspection warnings that are actually ok? For example I'm using yarl for URLs and requests.get(yarl.URL("http://asdf.net") gives warning about type not being Union[str, bytes], but it works fine. I see this from time to time ("with open(pathlib.Path)" gave similar error for a while). Would be nice if there was an easy way to tell PyCharm "this is ok".

Or, perhaps it is not; maybe even though it works we want to explicitly cast as str if that is what requests is telling us it wants to use?

cinci zoo sniper
Mar 15, 2013




mr_package posted:

Is there a quick fix in PyCharm for type inspection warnings that are actually ok? For example I'm using yarl for URLs and requests.get(yarl.URL("http://asdf.net") gives warning about type not being Union[str, bytes], but it works fine. I see this from time to time ("with open(pathlib.Path)" gave similar error for a while). Would be nice if there was an easy way to tell PyCharm "this is ok".

Or, perhaps it is not; maybe even though it works we want to explicitly cast as str if that is what requests is telling us it wants to use?

Requests doesn’t ask for that, URL just has to have a string representation - it will decode if bytestring, otherwise cast to string whatever you pass. The entire module is untyped, so I suspect this is a PyCharm inference issue. You can suppress inspections for a line by adding a comment right above it:

Python code:
# noinspection INSPECTION_NAME

Rocko Bonaparte
Mar 12, 2002

Every day is Friday!
Has anybody here ever directly used a slice object directly for something? Slicing sure, but I mean actually creating a slice() and going nuts with it.

QuarkJets
Sep 8, 2008

Rocko Bonaparte posted:

Has anybody here ever directly used a slice object directly for something? Slicing sure, but I mean actually creating a slice() and going nuts with it.

Yes.

Walh Hara
May 11, 2012

mr_package posted:

Is there a quick fix in PyCharm for type inspection warnings that are actually ok? For example I'm using yarl for URLs and requests.get(yarl.URL("http://asdf.net") gives warning about type not being Union[str, bytes], but it works fine. I see this from time to time ("with open(pathlib.Path)" gave similar error for a while). Would be nice if there was an easy way to tell PyCharm "this is ok".

Or, perhaps it is not; maybe even though it works we want to explicitly cast as str if that is what requests is telling us it wants to use?

In IntelliJ you can do this with alt+enter, it's likely the same in pycharm.

Rocko Bonaparte
Mar 12, 2002

Every day is Friday!

For what? It seems so bizarre.

QuarkJets
Sep 8, 2008

Rocko Bonaparte posted:

For what? It seems so bizarre.

I do a lot of CV and it's pretty nice to be able to programatically slice a few arrays in the same complicated way, you can accomplish the same thing with an index array but that's extremely impractical if you have a 10 GB image or something. It's like wrapping blocks of code with a function, but just for slice notation

All slicing notation does is create a slice object, so if you've ever sliced a bunch of arrays with identical notation then you could have used a slice object to improve those lines of code.

QuarkJets fucked around with this message at 02:07 on Nov 6, 2021

Hadlock
Nov 9, 2004

If I want a dead simple, easy to use cross-platform 2d graphics api (not full screen) what is my go-to these days? The only other thing I've used so far is tkinter and it always felt really clunky. I guess there's QT but I haven't revisited that in ages

Basically I want a windowed "app" with some text down the left side with readouts of various metrics, maybe 1 or 2 graphs, and on the right is a big square with i guess 20 balls sprites bouncing around inside, off the walls as a sort of visualization of the "simulation" :airquote: I don't think I'd ever have more than 100 sprites on the screen

cinci zoo sniper
Mar 15, 2013




As they say, it’s not a crime to want things. :v:

OnceIWasAnOstrich
Jul 22, 2006

Kivy? HTML Canvas and Javascript?

OnceIWasAnOstrich fucked around with this message at 14:36 on Nov 6, 2021

Hadlock
Nov 9, 2004

How is kivy vs like, pygame or whatever

I refuse to touch html or JavaScript

OnceIWasAnOstrich
Jul 22, 2006

Hadlock posted:

How is kivy vs like, pygame or whatever


There is a lot more to it and it is much better maintained. It's a lot more batteries-included for UI stuff especially. Pygame is one (of several) of the backends Kivy will use for rendering. It is also genuinely not very hard to package it for various platforms and handle touch control.

cinci zoo sniper
Mar 15, 2013




OnceIWasAnOstrich posted:

Kivy? HTML Canvas and Javascript?

This Kivy thing looks very cool, I’ve never heard of it before. Thank you for bringing it up!

OnceIWasAnOstrich
Jul 22, 2006

I've never done any serious work with it but I did throw together a quick gimmick android app with a few buttons and a constantly-updating visual with it once. It came together extremely smoothly and quickly considering I just don't make GUIs as a rule.

Without a bunch of aesthetics work it does have A Look that makes it very obvious it isn't native anything.

Adbot
ADBOT LOVES YOU

galenanorth
May 19, 2016

I'm going through HackerRank problems, and I just finished the Nested List problem successfully after two failed submissions involving missing edge cases. I'm thinking about how to write code which tests a function, but without having to create a separate file or import anything that requires installation, because setting that up sounds like it'd eat up some extra time for HackerRank problems or screening tests with a time limit. If I'm really stumped at finding edge cases and coming up with custom input which helps me figure out what to do, I'd try writing a function which generates random arguments within constraints. In this case, it did help me find the edge case

Python code:
[['SIaEO', 57.16], ['ybBls', 86.24], ['HZBRg', 7.95], ['xEfKq', 7.95]]
I thought I'd caught the tied score scenario, but my program only worked with a tie in the first two pairs. It briefly occurred to me while I wrote the if-block with less than signs that I needed to account for the equals scenario, but it slipped my mind and I forgot. Anyway, I'm pasting my code for that problem in order to get advice on improving my testing technique:

https://pastebin.com/0pRj8r5Q

galenanorth fucked around with this message at 16:19 on Nov 7, 2021

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply