0

I'm looking for a single expression, mutates an element and returns the modified list

The following is a bit verbose

# key=0; value=3; rng=[1,2]
[(v if i != key else value) for i, v in enumerate(rng)]

Edit:

I'm looking for a way to inline the following function in a single expression

def replace(rng: List, key: int, value):
    a = list(rng) 
    a[key] = value
    return a

Edit 2: the code that actually motivated this question

class TextDecoder(nn.Module):
    def forward(self, x: Tensor, kv_cache: Tensor):
        kv_cache_write = torch.zeros((_:=list(kv_cache.shape))).__setitem__(-2, x.shape[-1]) or _)
        ...
11
  • I'm curious, what do you want this for, where you can't or don't want to just use the first two lines of your replace function? Depending on that context, we might still be able to do better than the current answers. Commented Jan 27, 2023 at 6:34
  • 1
    Hmm, you talk about "writing readable code" and then refer me to a 649-characters oneliner? :-) Your examples seem odd/artificial, would need a real use case to convince me. That AOC solution is one, but neither its t.insert(h+i,t.pop(h+i)|x[i]) nor the rng.insert(key, rng.pop(key) or value) return the mutated list, they just mutate it... Commented Jan 27, 2023 at 8:44
  • 1
    Hmm, I doubt I know anyone but you who'd prefer that (or the other alternatives shown so far) over kv_cache_write = torch.zeros(list(kv_cache.shape)) kv_cache_write[-2] = x.shape[-1]. Commented Jan 27, 2023 at 21:58
  • 1
    Well, I do like single-line assignments without further modifications. Such a second line might make me wonder whether the value is finished or even more changes are ahead. That's an advantage of the oneliners. I have sometimes done oneliner multi-assigns like inside my answer's second way, which would here just be kv_cache_write, kv_cache_write[-2] = torch.zeros(list(kv_cache.shape)), x.shape[-1]. But I think I'd only really do that when the target name and the expressions are shorter than that. Here I'd prefer the standard two-liner. Commented Jan 27, 2023 at 22:08
  • 1
    Oh gawd... the new version in edit 2 is despicable. That's not code, that's a puzzle :-D Commented Jan 27, 2023 at 22:23

3 Answers 3

2

Maybe better than list concatenation:

[*rng[:key], value, *rng[key+1:]]

Another:

[a for a, a[key] in [(rng[:], value)]][0]

Or if you are just assigning the result to a variable (as discussed) you can do:

a, a[key] = rng[:], value
Sign up to request clarification or add additional context in comments.

6 Comments

I didn't know that the second one would work. The first one looks better than list concatenation
I like it better than a = list(rng); a[key] = value, but I think I prefer (a:=rng).__setitem(key, value) since you don't repeat a as I prefer long variable names
@TomHuntington I added a "maybe" to the edit, as I wouldn't make the "better" claim. I personally do like it better and I'm confident it takes less memory, at least for large lists, but I'm not entirely sure about speed. I'd have to benchmark and reread how it's implemented.
@KellyBundy I tested your code and the other answers with timeit. Your first solution isn't too much different than my answer, and your second answer appears to run slower than both. However, your last solution runs faster than any other solution or answer. I didn't test memory usage, but +1 for speed.
@MichaelM. Good, thanks :-). What list length and key did you use, though?
|
1

Try list concatenation:

key = 0
value = 3
rng = [1, 2]

out = rng[:key] + [value] + rng[key+1:]
print(out)

rng[:key] is a copy of the list up to the key (exclusive), [value] is a new list where the only element is value, and rng[key+1] is a copy of the list from the key on (exclusive). Concatenate these together, and you get a copy where the key is replaced.

3 Comments

Thanks, I'm not sure which answer to prefer
@TomHuntington I'd say both are okay, but I've seen my answer in other code before and mine is definitely more common. Also, I don't know much about leaks, but the other answer says that a is leaked.
I think I prefer mine because __setitem__ spells out explicitly what's going on. On the other hand, if its common idiom I'll accept this
0
# key=0; value=3; rng=[1,2]
print(rng.__setitem__(key, value) or rng)

If you don't want to modify the original list rng, you can do

print((_:=list(rng)).__setitem__(key, value) or _)

unfortunately this leaks the variable _.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.