Re: Add a replace method to tuples

On Tue, Mar 15, 2022 at 11:16:04AM +1100, Chris Angelico wrote:
How do we make a new list with a change other than the same slice and concatenation we use with tuples?
alist[:i] + [value] + alist[i+1:]
I mean as an expression, of course we can split it over two statements:
newlist = alist[:] newlist[i] = value
which is fine, but it does lose the expressiveness of an expression :-)
The functional programming style would be mapping most elements to themselves, and the one you're replacing to the replacement.
No, that doesn't work, because that is replacing by value, not by position. You're answering the wrong problem.
(Or, if you need to do it with indices, map the range of integers to either the original element at that index or the replacement.)
You mean this? [value if index == i else alist[i] for index in range(len(alist))] This would be *slightly* less unpythonic: [value if index == i else x for (index, x) in enumerate(alist)] but I would reject both of those in a code review unless you had clear proof that they were significantly faster or more optimal than the standard idioms. Which I am confident you won't have. I'm so confident that they're slower I'm not even going to time it myself before saying I'm confident they're slower! *wink* Famous-last-words-ly y'rs, -- Steve

On 2022-03-15 23:13, Steven D'Aprano wrote:
On Tue, Mar 15, 2022 at 11:16:04AM +1100, Chris Angelico wrote:
How do we make a new list with a change other than the same slice and concatenation we use with tuples?
alist[:i] + [value] + alist[i+1:]
I mean as an expression, of course we can split it over two statements:
newlist = alist[:] newlist[i] = value
which is fine, but it does lose the expressiveness of an expression :-)
The functional programming style would be mapping most elements to themselves, and the one you're replacing to the replacement.
No, that doesn't work, because that is replacing by value, not by position. You're answering the wrong problem.
(Or, if you need to do it with indices, map the range of integers to either the original element at that index or the replacement.)
You mean this?
[value if index == i else alist[i] for index in range(len(alist))]
This would be *slightly* less unpythonic:
[value if index == i else x for (index, x) in enumerate(alist)]
but I would reject both of those in a code review unless you had clear proof that they were significantly faster or more optimal than the standard idioms. Which I am confident you won't have.
I'm so confident that they're slower I'm not even going to time it myself before saying I'm confident they're slower! *wink*
Famous-last-words-ly y'rs,
I'm wondering whether an alterative could be a function for splicing sequences such as lists and tuples which would avoid the need to create and then destroy intermediate sequences: splice(alist, i, 1 + 1, [value]) In Python terms it would be: def splice(sequence, start, end, insertion): return sequence[ : start] + insertion + sequence[end : ] but, as I said, without the intermediate sequences, just allocate to the final length and then shallow copy.

MRAB writes:
I'm wondering whether an alterative could be a function for splicing sequences such as lists and tuples which would avoid the need to create and then destroy intermediate sequences:
splice(alist, i, 1 + 1, [value])
Does this make sense for lists? I don't see how you beat newlist = alist[:] newlist[index_or_slice] = value_or_sequence_respectively (instead of newlist = alist[:i] + [value] + alist[i+1:], which involves creating 4 lists instead of 1). I wonder if it might be reasonable to peephole optimize tmplist = list(atuple) tmplist[index_or_slice] = value_or_sequence_respectively newtuple = tuple(tmplist) Obviously that wouldn't be part of the language, and it would be a PITA for other implementations to mimic for infinitesimal benefit. Steve

On 17/03/2022 05.21, Stephen J. Turnbull wrote:
MRAB writes:
I'm wondering whether an alterative could be a function for splicing sequences such as lists and tuples which would avoid the need to create and then destroy intermediate sequences:
splice(alist, i, 1 + 1, [value])
Does this make sense for lists? I don't see how you beat
newlist = alist[:] newlist[index_or_slice] = value_or_sequence_respectively
(instead of newlist = alist[:i] + [value] + alist[i+1:], which involves creating 4 lists instead of 1). I wonder if it might be reasonable to peephole optimize
Recently (extra-patiently) explained to A.N.Other how indexes/indices and slices can be used on the LHS of an expression. So, I was really hoping that 'splicing' would make sense over the traditional break-and-build pattern. Sadly (for my hopes), from an execution-time perspective, there's nothing between them:-
import timeit as tm l = [ 0 , 1, 2, 3, 4, 5, 6, 7, 8, 9, 'ten', 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ]
joiner = """\ ... def join_list( existing_list ): ... return existing_list[ :10 ] + [ 10 ] + existing_list[ 10+1: ] ... """ tm.timeit( stmt=joiner, number=100_000_000 ) 6.822223100927658
splicer = """\ ... def splice_list( existing_list ): ... new_list = existing_list[ : ] ... new_list[ 10 ] = 10 ... return new_list ... """ tm.timeit( stmt=splicer, number=100_000_000 ) 6.87068138003815
NB yes, outside of the simplified timing-environment, we'd want the index (10) and the value (int( 10 )) to be parameters! Both 'joiner' and 'splicer' require twice len( l ) of storage-space. Most of us will comfortably write 'joiner' in-line (cf the function above) - and when reading later, will recognise the construction for what it is. Some might prefer that the two lines of splice_list() be 'chunked' into a function, but given that splicing is built-in there's no reason why they couldn't be expressed in-line - other than (perhaps) a comparative lack of familiarity in reading/interpreting the two lines for what they're doing! Assuming we're prepared to use an utility-splicer (but not extend the list class directly), if we don't need to create a second list or must ration storage-space, the mutable-list could be directly, um, mutated:-
splice_in_place = """\ ... def splice_list(): ... the_list[ 10 ] = 10 ... """ the_list = l[ : ] tm.timeit( stmt=splice_in_place, number=100_000_000 ) 6.726862345007248
but please be prepared for code-review criticism! Once-again, there's no significant speed-advantage. So, ... If we're still talking about tuples, then the experiments could be repeated with appropriate list() and tuple() coercions. -- Regards, =dn

On Thu, Mar 17, 2022 at 01:21:24AM +0900, Stephen J. Turnbull wrote:
MRAB writes:
I'm wondering whether an alterative could be a function for splicing sequences such as lists and tuples which would avoid the need to create and then destroy intermediate sequences:
splice(alist, i, 1 + 1, [value])
Does this make sense for lists?
I don't see why not.
I don't see how you beat
newlist = alist[:] newlist[index_or_slice] = value_or_sequence_respectively
We know the advantages of an expression versus a statement (or pair of statements). If this was Ruby, we could use a block: function(arg, {newlist = alist[:]; newlist[i] = value}, another_arg) but it isn't and we can't.
(instead of newlist = alist[:i] + [value] + alist[i+1:], which involves creating 4 lists instead of 1).
Indeed, the splice function could call a `__splice__` dunder that specialises this for each type. It is hard to see how to write a completely generic version. -- Steve

On Thu, Mar 17, 2022, 5:23 AM Steven D'Aprano <steve@pearwood.info> wrote: On Thu, Mar 17, 2022 at 01:21:24AM +0900, Stephen J. Turnbull wrote:
MRAB writes:
I'm wondering whether an alterative could be a function for splicing sequences such as lists and tuples which would avoid the need to create and then destroy intermediate sequences:
splice(alist, i, 1 + 1, [value])
Does this make sense for lists?
I don't see why not.
I don't see how you beat
newlist = alist[:] newlist[index_or_slice] = value_or_sequence_respectively
We know the advantages of an expression versus a statement (or pair of statements). If this was Ruby, we could use a block: function(arg, {newlist = alist[:]; newlist[i] = value}, another_arg) but it isn't and we can't.
(instead of newlist = alist[:i] + [value] + alist[i+1:], which involves creating 4 lists instead of 1).
Indeed, the splice function could call a `__splice__` dunder that specialises this for each type. It is hard to see how to write a completely generic version. -- Steve Not a pro or con just an observation: since dictionaries are ordered now people would also (somewhat reasonably, imo) want to be able to splice them, too.

On Thu, Mar 17, 2022 at 08:38:19AM -0400, Ricky Teachey wrote:
Not a pro or con just an observation: since dictionaries are ordered now people would also (somewhat reasonably, imo) want to be able to splice them, too.
Dicts are not sequences. They might preserve insertion order, but that is not the same as being orderable, sortable, or being able to access keys or values by index. There is no reason why one cannot build an indexable dict that supports this functionality, but the trade-offs to allow slicing and indexing of such a dict would be different from the trade-offs desired for builtin dicts in Python. -- Steve

Steven D'Aprano writes:
On Thu, Mar 17, 2022 at 01:21:24AM +0900, Stephen J. Turnbull wrote:
MRAB writes:
I'm wondering whether an alterative could be a function for splicing sequences such as lists and tuples which would avoid the need to create and then destroy intermediate sequences:
splice(alist, i, 1 + 1, [value])
Does this make sense for lists?
Let me rephrase that. Given that we have a splice builtin, yes, I'm sure it would be used for lists as well as immutable sequences. But would the list use case add much motivation for adding it?
I don't see how you beat
newlist = alist[:] newlist[index_or_slice] = value_or_sequence_respectively
We know the advantages of an expression versus a statement (or pair of statements).
In general, yes, but for the uses cases where we want splice(), do we? Most of ~50 cases (many duplicates) I looked at from wfdc's post were assignment statements. I guess some might have been the body of a def replace(tuple, index, value), in which case the calls would have been expressions. But many of them were parts of suites, so the author intended them to be assignments.
participants (5)
-
dn
-
MRAB
-
Ricky Teachey
-
Stephen J. Turnbull
-
Steven D'Aprano