It looks like there is not a simple way to create a derived sequence without a certain element like how there is a way to do it with addition :+, +;. e.g. I expected
seq :- element
Only less direct approaches involving filtering.
Why is this?
It looks like there is not a simple way to create a derived sequence without a certain element like how there is a way to do it with addition :+, +;. e.g. I expected
seq :- element
Only less direct approaches involving filtering.
Why is this?
The most important reason is interop with postfix emoticons in the Lightbend Emoji library.
println("hello, world" :-)
The other reason is that +:
(which the editor wants to autocomplete to ) is by analogy to
::
for List
.
It has a corresponding extractor object. However, Seq
is considered not specific enough for “serious applications” where you care about performance characteristics.
To strip the head, as when overtightening a screw or bolt, use xs.drop(1)
, or similarly for dropRight
.
All that is aside from a general bias against adding operator symbols without due cause.
However, I sympathize with the desire for symmetry and regularity.
println(`¯\_(ツ)_/¯`)
Note that Set
does have -
:
Welcome to Scala 3.7.0 (21.0.7, Java OpenJDK 64-Bit Server VM).
scala> Set(1, 2, 3) - 2
val res0: Set[Int] = Set(1, 3)
with Seq
, it’s less clear that -
would make sense as an operator since the item in question might appear multiple times.
Should :-
remove the first aparece of the element? The last one? All elements? A random element? Should fail if there is no element?
I would also add that I rarely would need this.
Plus, the performance of this operation is probably O(N). So, if I would need to do this regularly such that filter
would be annoying then I may rather use a different data structure.
My immediate intuition was that it should remove the first occurrence of the element, and the more I interrogate this intuition the more easy to defend it seems.
:+
has the behavior of adding duplicate individuals, so we should expect it’s inverse -:
to also handle by individuals. And we are working with a Seq
not a Set
so this is already the precedent; just as you can add an item multiple times, you can remove it multiple times.No, “the sequence of (1,2,3) without 8” is still (1,2,3). Idempotent operations are not failed operation, they are just trivial truths. It’s inversely analogous to using filter
with a predicate which all elements satisfy, resulting in an unchanged collection; should we throw an exception because nothing was “truly filtered”? No, because the filter was applied successfully, and the -:
would be applied successfully, even if the results are unchanged.
scala> val xs = 1 to 10
val xs: scala.collection.immutable.Range.Inclusive = Range 1 to 10
scala> xs.patch(from = xs.indexOf(4), other = Nil, replaced = 1)
val res0: IndexedSeq[Int] = Vector(1, 2, 3, 5, 6, 7, 8, 9, 10)
scala> xs.span(_ < 4) match { case (a, b) => a ++ b.drop(1) }
val res1: IndexedSeq[Int] = Vector(1, 2, 3, 5, 6, 7, 8, 9, 10)
or mutably
scala> val xs = ArrayDeque.from(1 to 10)
val xs: scala.collection.mutable.ArrayDeque[Int] = ArrayDeque(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> xs.subtractOne(4)
val res3: scala.collection.mutable.ArrayDeque[Int] = ArrayDeque(1, 2, 3, 5, 6, 7, 8, 9, 10)
or
scala> val xs = ArrayDeque.from(1.to(10).toList)
val xs: scala.collection.mutable.ArrayDeque[Int] = ArrayDeque(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> val (a, b) = xs.span(_ != 4)
val a: scala.collection.mutable.ArrayDeque[Int] = ArrayDeque(1, 2, 3)
val b: scala.collection.mutable.ArrayDeque[Int] = ArrayDeque(4, 5, 6, 7, 8, 9, 10)
scala> a ++ b.drop(1)
val res5: scala.collection.mutable.ArrayDeque[Int] = ArrayDeque(1, 2, 3, 5, 6, 7, 8, 9, 10)
But there is a way, only not using a symbolic operator. Seq
has a diff
member:
In your case you can use seq.diff(Seq(element))
.
I think compared to +:
you might want to be aware even the implementation is not trivial, while prepending head is O(1), any element removal needs to be O(N) for a general unsorted sequence.
We? You may very well expect that, but I don’t. :+
appends an element regardless of its value (and thus any notion of equivalence and thus duplication) to the lbs sequence. Contrast with Set
, as @SethTisue pointed out.
If you’re looking to debate some abstract point about inverse operations, look for how to deconstruct a sequence into an initial part and final element (likewise for the first part and tail). At which point you will move off Seq
and on to List
or the (presumably immutable) Deque
(from the Typelevel library).
If you’ve got an actual use-case where you intend some pattern of interaction with your putative collection type, best to spell it out explicitly in your question. From what you’ve described you want to be able to add and remove elements according to some notion of equivalence. I’m not sure as to whether the order of addition of the elements is unimportant to you: in which case maybe a set or a bag is probably what you want.
If you feel the lack of some operator in your code base, you can always write an extension operator method that wraps, say the code that @som-snytt contributed.
While I also agree that the operation should not fail “per se”. The point I am trying to make is that, for a Seq
, thinking of an operation that is the inverse of :+
& +:
doesn’t make much sense at all.
:+
& +:
always returns a modified collection, but a hypothetical :-
& -:
may not. The already existing tail
and init
always return a modified collection, but don’t care about the input, and actually fail on empty collections (which makes sense).
Sadly, there is not really a simple way to filter a single element.
The cleaner and most efficient solution is using a var
and break
for LinearSeqs
. Or, a combination of firstIndexOf
& patch
for IndexedSeqs
.
And, if you want to remove it from the back, then it is even harder.
However, as I said before, IME, it is rare to need this. Usually, this comes on very contrived code golfing exercises. And then, usually is better to just model the data differently.
This brings me to the point that @sageserpent-open was made.
If you have a real use case for this, then it is better to discuss that. But, I have a feeling that you just think the operation is missing and thus want to see it added, just because.
But, the stdlib does not and should not work like that. We should add features that make sense and are generally useful. Not just add stuff in the sake of “completeness”.
Not only for practical reasons, but also because pursuing completeness in general is impossible, we can start thinking about a lot of variations to each operation, and at some point, the similarities start to get loose; as I pointed out at the beginning that such operations are not truly inverses as you think.