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 _)
        ...
Tom Huntington
  • 2,260
  • 10
  • 20
  • 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. – Kelly Bundy Jan 27 '23 at 06:34
  • @KellyBundy I'm a big believer in aggressive inlining. The key to writing readable code is spellings (i.e. naming) and controlling the amount of inlining / extraction. Most people just write code with a whole lot of useless/unnecessary named variables, its very cluttered, which detracts from the spellings that have value. You should only name a variable (or function), when it adds value. – Tom Huntington Jan 27 '23 at 07:14
  • Ok, but what are you doing with the list? The accepted answer for example assigns it to `out` anyway, in which case it would be a lot better to just assign a copy to it and modify it. Do you have an example case where you wouldn't want that? – Kelly Bundy Jan 27 '23 at 07:23
  • @KellyBundy Its all taste, maybe you really want to inline the `out` variable `print(rng[:key] + [value] + rng[key+1:])`, when you are calling `reduce`/`accumulate` you pretty much always are wanting to inline `initial` arg. Or maybe you are just debugging and its quicker to write. That's the best I can come up with. If you really want to know where this question comes from I've been reading these [one-liner solutions](https://github.com/kaathewise/aoc2022/blob/main/17.py) and this guy came up with `rng.insert(key, rng.pop(key) or value)` and I was trying to improve on it. – Tom Huntington Jan 27 '23 at 08:03
  • 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... – Kelly Bundy Jan 27 '23 at 08:44
  • @KellyBundy I added, to the OP, the code that actually motivated this question. I don't know if its more readable, it this case. – Tom Huntington Jan 27 '23 at 21:56
  • 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]`. – Kelly Bundy Jan 27 '23 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. – Kelly Bundy Jan 27 '23 at 22:08
  • @KellyBundy Yes, I actually did it over two lines `(write_shape:=list(kv_cache.shape))).__setitem__(-2, x.shape[-1])`. Also I've been refactoring the [oneliners](https://github.com/tom-huntington/aoc2022) I think there are some pretty cool tricks to be learned in them – Tom Huntington Jan 27 '23 at 22:21
  • 1
    Oh gawd... the new version in edit 2 is despicable. That's not code, that's a puzzle :-D – Kelly Bundy Jan 27 '23 at 22:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/251448/discussion-between-tom-huntington-and-kelly-bundy). – Tom Huntington Jan 27 '23 at 23:19

3 Answers3

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
Kelly Bundy
  • 23,480
  • 7
  • 29
  • 65
  • I didn't know that the second one would work. The first one looks better than list concatenation – Tom Huntington Jan 27 '23 at 22:03
  • 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 – Tom Huntington Jan 27 '23 at 22:26
  • 1
    @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. – Kelly Bundy Jan 27 '23 at 23:16
  • 1
    @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. – Michael M. Jan 27 '23 at 23:22
  • 1
    @MichaelM. Good, thanks :-). What list length and key did you use, though? – Kelly Bundy Jan 27 '23 at 23:31
  • @KellyBundy Oh, I tested just tested with with the 2 element list that OP provided. I just now tried it with the `__setattr__()` solution and found that to be better in longer lists. However, again, I did not test memory usage so I'm not sure which one is better overall. – Michael M. Jan 27 '23 at 23:34
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.

Michael M.
  • 10,486
  • 9
  • 18
  • 34
  • 1
    Thanks, I'm not sure which answer to prefer – Tom Huntington Jan 27 '23 at 04:29
  • @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. – Michael M. Jan 27 '23 at 04:35
  • 1
    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 – Tom Huntington Jan 27 '23 at 04:40
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 _.

Tom Huntington
  • 2,260
  • 10
  • 20