I love tuples. They allow you to quickly group relevant information together without having to write a struct or class for it. This is very useful while refactoring very localized code.
Initializing a list of them however seems a bit redundant.
var tupleList = new List<Tuple<int, string>>
{
Tuple.Create( 1, "cow" ),
Tuple.Create( 5, "chickens" ),
Tuple.Create( 1, "airplane" )
};
Isn't there a better way? I would love a solution along the lines of the Dictionary initializer.
Dictionary<int, string> students = new Dictionary<int, string>()
{
{ 111, "bleh" },
{ 112, "bloeh" },
{ 113, "blah" }
};
Can't we use a similar syntax?
c# 7.0 lets you do this:
var tupleList = new List<(int, string)>
{
(1, "cow"),
(5, "chickens"),
(1, "airplane")
};
If you don't need a List, but just an array, you can do:
var tupleList = new(int, string)[]
{
(1, "cow"),
(5, "chickens"),
(1, "airplane")
};
And if you don't like "Item1" and "Item2", you can do:
var tupleList = new List<(int Index, string Name)>
{
(1, "cow"),
(5, "chickens"),
(1, "airplane")
};
or for an array:
var tupleList = new (int Index, string Name)[]
{
(1, "cow"),
(5, "chickens"),
(1, "airplane")
};
which lets you do: tupleList[0].Index and tupleList[0].Name
Framework 4.6.2 and below
You must install System.ValueTuple from the Nuget Package Manager.
Framework 4.7 and above
It is built into the framework. Do not install System.ValueTuple. In fact, remove it and delete it from the bin directory.
note: In real life, I wouldn't be able to choose between cow, chickens or airplane. I would be really torn.
Yes! This is possible.
The { } syntax of the collection initializer works on any IEnumerable
type which has an Add method with the correct amount of arguments.
Without bothering how that works under the covers, that means you can
simply extend from List<T>, add a custom Add method to initialize your
T, and you are done!
public class TupleList<T1, T2> : List<Tuple<T1, T2>>
{
public void Add( T1 item, T2 item2 )
{
Add( new Tuple<T1, T2>( item, item2 ) );
}
}
This allows you to do the following:
var groceryList = new TupleList<int, string>
{
{ 1, "kiwi" },
{ 5, "apples" },
{ 3, "potatoes" },
{ 1, "tomato" }
};
C# 6 adds a new feature just for this: extension Add methods. This has always been possible for VB.net but is now available in C#.
Now you don't have to add Add() methods to your classes directly, you can implement them as extension methods. When extending any enumerable type with an Add() method, you'll be able to use it in collection initializer expressions. So you don't have to derive from lists explicitly anymore (as mentioned in another answer), you can simply extend it.
public static class TupleListExtensions
{
public static void Add<T1, T2>(this IList<Tuple<T1, T2>> list,
T1 item1, T2 item2)
{
list.Add(Tuple.Create(item1, item2));
}
public static void Add<T1, T2, T3>(this IList<Tuple<T1, T2, T3>> list,
T1 item1, T2 item2, T3 item3)
{
list.Add(Tuple.Create(item1, item2, item3));
}
// and so on...
}
This will allow you to do this on any class that implements IList<>:
var numbers = new List<Tuple<int, string>>
{
{ 1, "one" },
{ 2, "two" },
{ 3, "three" },
{ 4, "four" },
{ 5, "five" },
};
var points = new ObservableCollection<Tuple<double, double, double>>
{
{ 0, 0, 0 },
{ 1, 2, 3 },
{ -4, -2, 42 },
};
Of course you're not restricted to extending collections of tuples, it can be for collections of any specific type you want the special syntax for.
public static class BigIntegerListExtensions
{
public static void Add(this IList<BigInteger> list,
params byte[] value)
{
list.Add(new BigInteger(value));
}
public static void Add(this IList<BigInteger> list,
string value)
{
list.Add(BigInteger.Parse(value));
}
}
var bigNumbers = new List<BigInteger>
{
new BigInteger(1), // constructor BigInteger(int)
2222222222L, // implicit operator BigInteger(long)
3333333333UL, // implicit operator BigInteger(ulong)
{ 4, 4, 4, 4, 4, 4, 4, 4 }, // extension Add(byte[])
"55555555555555555555555555555555555555", // extension Add(string)
};
C# 7 will be adding in support for tuples built into the language, though they will be of a different type (System.ValueTuple instead). So to it would be good to add overloads for value tuples so you have the option to use them as well. Unfortunately, there are no implicit conversions defined between the two.
public static class ValueTupleListExtensions
{
public static void Add<T1, T2>(this IList<Tuple<T1, T2>> list,
ValueTuple<T1, T2> item) => list.Add(item.ToTuple());
}
This way the list initialization will look even nicer.
var points = new List<Tuple<int, int, int>>
{
(0, 0, 0),
(1, 2, 3),
(-1, 12, -73),
};
But instead of going through all this trouble, it might just be better to switch to using ValueTuple exclusively.
var points = new List<(int, int, int)>
{
(0, 0, 0),
(1, 2, 3),
(-1, 12, -73),
};
You can do this by calling the constructor each time with is slightly better
var tupleList = new List<Tuple<int, string>>
{
new Tuple<int, string>(1, "cow" ),
new Tuple<int, string>( 5, "chickens" ),
new Tuple<int, string>( 1, "airplane" )
};
Old question, but this is what I typically do to make things a bit more readable:
Func<int, string, Tuple<int, string>> tc = Tuple.Create;
var tupleList = new List<Tuple<int, string>>
{
tc( 1, "cow" ),
tc( 5, "chickens" ),
tc( 1, "airplane" )
};
Super Duper Old I know but I would add my piece on using Linq and continuation lambdas on methods with using C# 7. I try to use named tuples as replacements for DTOs and anonymous projections when reused in a class. Yes for mocking and testing you still need classes but doing things inline and passing around in a class is nice to have this newer option IMHO. You can instantiate them from
Direct Instantiation
var items = new List<(int Id, string Name)> { (1, "Me"), (2, "You")};
Off of an existing collection, and now you can return well typed tuples similar to how anonymous projections used to be done.
public class Hold
{
public int Id { get; set; }
public string Name { get; set; }
}
//In some method or main console app:
var holds = new List<Hold> { new Hold { Id = 1, Name = "Me" }, new Hold { Id = 2, Name = "You" } };
var anonymousProjections = holds.Select(x => new { SomeNewId = x.Id, SomeNewName = x.Name });
var namedTuples = holds.Select(x => (TupleId: x.Id, TupleName: x.Name));
Reuse the tuples later with grouping methods or use a method to construct them inline in other logic:
//Assuming holder class above making 'holds' object
public (int Id, string Name) ReturnNamedTuple(int id, string name) => (id, name);
public static List<(int Id, string Name)> ReturnNamedTuplesFromHolder(List<Hold> holds) => holds.Select(x => (x.Id, x.Name)).ToList();
public static void DoSomethingWithNamedTuplesInput(List<(int id, string name)> inputs) => inputs.ForEach(x => Console.WriteLine($"Doing work with {x.id} for {x.name}"));
var namedTuples2 = holds.Select(x => ReturnNamedTuple(x.Id, x.Name));
var namedTuples3 = ReturnNamedTuplesFromHolder(holds);
DoSomethingWithNamedTuplesInput(namedTuples.ToList());
One technique I think is a little easier and that hasn't been mentioned before here:
var asdf = new [] {
(Age: 1, Name: "cow"),
(Age: 2, Name: "bird")
}.ToList();
I think that's a little cleaner than:
var asdf = new List<Tuple<int, string>> {
(Age: 1, Name: "cow"),
(Age: 2, Name: "bird")
};
var colors = new[]
{
new { value = Color.White, name = "White" },
new { value = Color.Silver, name = "Silver" },
new { value = Color.Gray, name = "Gray" },
new { value = Color.Black, name = "Black" },
new { value = Color.Red, name = "Red" },
new { value = Color.Maroon, name = "Maroon" },
new { value = Color.Yellow, name = "Yellow" },
new { value = Color.Olive, name = "Olive" },
new { value = Color.Lime, name = "Lime" },
new { value = Color.Green, name = "Green" },
new { value = Color.Aqua, name = "Aqua" },
new { value = Color.Teal, name = "Teal" },
new { value = Color.Blue, name = "Blue" },
new { value = Color.Navy, name = "Navy" },
new { value = Color.Pink, name = "Pink" },
new { value = Color.Fuchsia, name = "Fuchsia" },
new { value = Color.Purple, name = "Purple" }
};
foreach (var color in colors)
{
stackLayout.Children.Add(
new Label
{
Text = color.name,
TextColor = color.value,
});
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
}
this is a Tuple<Color, string>
Related
I got this multiarray list of integers that would look something like this:
List<List<int>> multiarray = new() {
new() { 8, 63 },
new() { 4, 2 },
new() { 0, -55 },
new() { 8, 57 },
new() { 2, -120},
new() { 8, 53 }
};
Now let's say I want to create it and add items using a variable, how will I do so?
I thought it would be as the following:
int value1 = 4
int value2 = 5
ListStat.Add(value1, value2);
But I get an error saying I cant overload using the method "add", any other command I should use?
(Alternate method for problem solving, without multi List, but it's longer)
//Helper
record ListStat (int value1, int value2)
static void Main(string[] args)
{
List<ListStat> lList = new List<ListStat>()
{
new ListStat(8,63),
new ListStat(4,2),
new ListStat(0,-55),
new ListStat(8,57)
};
lList.Add(new ListStat(0, 0)); //Adding values
Console.WriteLine($"({lList[0].value1};{lList[0].value2})"); //Ref to first element
//Ref for all element step by step in lList
foreach (ListStat singleItem in lList)
{
Console.WriteLine($"({singleItem.value1};{singleItem.value2})");
}
}
As your list has the element type of List<int> and not (int, int), you should NOT use multiarray.Add((value1, value2), but instead use the following:
int value1 = 4;
int value2 = 5;
multiarray.Add(new List<int> { value1, value2 });
or, even shorter using target-typed new-expressions:
multiarray.Add(new(){ value1, value2 });
If your sublists are known to have only 2 items, I would strongly advise you to use ValueTuple<...>s instead of List<int>. This not only enhances the readability of your code, but also reduces your application's memory footprint and execution speed, as less memory has to be allocated and no append/adding-operations have to be executed for the sublists.
The readability is enhanced, as items such as (1, 3) are automatically interpreted as ValueTuple<int, int>.
The resulting code should look as follows:
List<(int, int)> multiarray = new() {
(8, 63 ),
(4, 2 ),
(0, -55 ),
(8, 57 ),
(2, -120),
(8, 53 )
};
You may then add new items as follows (notice the extra parenthesis):
multiarray.Add((value1, value2));
You can display your items as follows:
foreach ((int, int) tuple in multiarray)
Console.WriteLine($"My list has the items ({tuple.Item1}, {tuple.Item2})");
or using the deconstruction-syntax:
foreach ((int value1, int value2) in multiarray)
Console.WriteLine($"My list has the items ({value1}, {value2})");
You may also might want to name your tuple elements:
List<(int a, int b)> multiarray = new() {
(8, 63),
(4, 2),
....
};
which allows you to use the following code:
int first = multiarray[0].a; // first tuple element of the first list item.
The List<T> class has an Add method that takes a single value of type T. When you have a List<List<T>> then the Add member takes a single value of type List<T>. You are trying to add two values of type T and the compiler doesn't know what to do with them.
If you want you code to work as is, then you can add an extension method to make it work.
Try this:
public static class MyListExtensions
{
public static void Add<T>(this List<List<T>> listList, params T[] items)
{
listList.Add(new List<T>(items));
}
}
Now your code works just fine:
List<List<int>> ListStat = new()
{
new() { 8, 63 },
new() { 4, 2 },
new() { 0, -55 },
new() { 8, 57 },
new() { 2, -120 },
new() { 8, 53 }
};
int value1 = 4
int value2 = 5
ListStat.Add(value1, value2);
You should create first your List<int> ListStat and then add to the List<List<int>> multiarray. For example:
var multiarray = new() {
new() { 8, 63 },
new() { 4, 2 },
new() { 0, -55 },
new() { 8, 57 },
new() { 2, -120},
new() { 8, 53 }
};
int value1 = 4;
int value2 = 5;
var ListStat = new(){ value1, value2};
multiarray.Add(ListStat);
I want to delete it with the desired id.
Can you tell me if there is another way to do it with Linq?
I think I can use Select() and Where(), but it doesn't work.
var list = new List<object>() { };
list.Add(new { id = 3, const = "22"});
list.Add(new { id = 4, const = "22"});
list.Add(new { id = 6, const = "22"});
list.Add(new { id = 2, const = "22"});
list.Add(new { id = 1, const = "22"});
//example
list.Remove(new { id = 2, const = "22" });
first of all const is a keyword in C#, so either avoid using it as member name, or escape it by decorating with #.
since your list item type is object, you need to cast them as dynamic to allow access the 'unknown' id -> this a very vague approach, but it works.
= in C# means: set the value of... so if you want to compare equality, you need ==
putting this altogether:
var list = new List<object>();
list.Add(new { id = 4, #const = "22" });
list.Add(new { id = 4, #const = "22" });
list.Add(new { id = 6, #const = "22" });
list.Add(new { id = 2, #const = "22" });
list.Add(new { id = 1, #const = "22" });
list.RemoveAll(i => ((dynamic)i).id == 2);
will work.
Consider using an own Type for your list items, anonymous types should only be used 'locally' in Queries like GroupBy.
you could try:
list.RemoveAt(list.FindIndex(item => item.id == 1));
I am wondering if two int arrays can theoretically be merged into a single array of structures using only LINQ, where each array going into its respective field?
So let's say we have 2 int arrays called numbers_raw and categories_raw, and class Record with 2 fields.
Using procedural code it looks like this:
class Record {
public int number;
public int category;
}
int[] numbers_raw = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 14, -4 };
int[] categories_raw = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2 };
var records = new List<Record>();
for (int ii = 0; ii < numbers_raw.Length; ii++) {
records.Add(new Record { number = numbers_raw[ii], category = categories_raw[ii] });
}
And if we had only 1 array and 1 field we could do it like this:
var records = numbers_raw.Select(x => new Record { number = x });
But I am not sure how to do it in such a way that both arrays are used, categories_raw going into the category field and numbers_raw goes into number field.
I am not sure if it is at all possible using LINQ.
Enumerable.Zip is what you need:
List<Record> records = numbers_raw
.Zip(categories_raw, (n, c) => new Record { number = n, category = c})
.ToList();
If one of both collections is larger, Zip takes all items until the common maximum count. For example, if one sequence has three elements and the other one has four, the result sequence will have only three elements.
This makes me wonder if there is a generic solution for any number of
arrays but I assume there isn't.
You're right, there is no ZipAll and it's not easy to build one. If you have 3 or 4 you could still use Zip, just concatenate them:
List<Record> records = numbers_raw
.Zip(categories_raw, (first, second) => (first, second))
.Zip(thirdSeq, (both, third) => new Record { number = both.first, category = both.second, thirdProperty = third})
.ToList();
or provide an extension method (this exists already with C#6 but only for 3):
public static class EnumerableExtensions
{
public static IEnumerable<TResult> Zip<TFirst, TSecond, TThird, TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
IEnumerable<TThird> third,
Func<TFirst, TSecond,
TThird, TResult> resultSelector)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
using (IEnumerator<TThird> e3 = third.GetEnumerator())
while (e1.MoveNext() && e2.MoveNext() && e3.MoveNext())
yield return resultSelector(e1.Current, e2.Current, e3.Current);
}
}
But these approches work just for a couple of sequences.
Yes, you can use Zip:
var records = numbers_raw.Zip(
categories_raw,
(x, y) => new Record {number = x, category = y})
.ToList();
You can use another version of .Select(element, index)
var records = numbers_raw.Select((x, index) =>
new Record {
number = numbers_raw[index],
category= categories_raw[index]
}).ToList();
Note: Here .Zip() is appropriate than .Select() version, I just shared alternate way to .Zip()
Why I said .Zip() is better than Select()here?
As #TimSchmelter said in his answer, Zip() takes all items until the common maximum count, but Select(element, index) will work only in case of numbers_raw.Count() > categories_raw.Count(). It will throw an Array out of bound exception if this condition failed.
If you desperate to use this solution, then always apply .Select() on the list which contains maximum elements.
Update:
I didn't know Zip actually comes with the same implementation, so I reworked the extension method slightly. As I got the problem with regular Select that it knows nothing about the index, so we can actually workaround this with the following extension:
public static IEnumerable<TResult> SelectByIndex<T1, TResult>(this IEnumerable<T1> sequence, Func<int, TResult> selector)
{
var baseArray = sequence.ToArray();
for (int i = 0; i < baseArray.Length; i++)
{
TResult result;
try
{
result = selector(i);
}
catch (IndexOutOfRangeException)
{
yield break;
}
yield return result;
}
}
And then use it like this:
var result = numbers_int.SelectByIndex(i => new Record {number = numbers_int[i], category = categories_raw[i]});
Old answer:
While Zip is good for the simple solution, you can write your own with the LINQ extension methods.
Let's assume your record class has different types of fields:
class Record
{
public int number;
public string category;
}
Then you can define your own LINQ expression for such case:
public static class LinqExtensions
{
public static IEnumerable<TResult> Merge<T1, T2, TResult>(this IEnumerable<T1> sequence, IEnumerable<T2> other, Func<T1, T2, TResult> selector)
{
using var sequenceEnumerator = sequence.GetEnumerator();
using var otherEnumerator = other.GetEnumerator();
while (sequenceEnumerator.MoveNext() && otherEnumerator.MoveNext())
{
yield return selector(sequenceEnumerator.Current, otherEnumerator.Current);
}
}
}
And use it like this:
int[] numbers_int = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 14, -4 };
string[] categories_str = { "0", "0", "0", "0", "1", "1", "1", "1", "2", "2", "2"};
var result = numbers_int.Merge(categories_str, (i, s) => new Record {number = i, category = s});
This will give you the flexibility of defining your own generic methods for any situtation.
int[] numbers_raw = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 14, -4 };
int[] categories_raw = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2 };
var records = numbers_raw.Select(delegate(int value, int index)
{
return new Record() {category = categories_raw[index], number = numbers_raw[index]};
});
struct process
{
public int Proc_Id;
public int Proc_BurstTime;
public int Proc_Priority;
};
readonly process[] ProcessList = new process[]
{
new process{ Proc_Id = 1, Proc_BurstTime = 3000, Proc_Priority = 1},
new process{ Proc_Id = 2, Proc_BurstTime = 5000, Proc_Priority = 2},
new process{ Proc_Id = 3, Proc_BurstTime = 1000, Proc_Priority = 3},
new process{ Proc_Id = 4, Proc_BurstTime = 10000, Proc_Priority = 4}
};
private void Form1_Load(object sender, EventArgs e)
{
initial_ProcessList.Items.AddRange(ProcessList);
}
This is the error I get:
The best overloaded method match for 'System.Windows.Forms.ListBox.ObjectCollection.AddRange(System.Windows.Forms.ListBox.ObjectCollection)' has some invalid arguments
Argument 1: cannot convert from 'OS450_Proj1.Form1.process[]' to 'System.Windows.Forms.ListBox.ObjectCollection'
I understand that it looks like it doesn't like that kind of array, but is there anything can do to populate the listbox? If it is also because there's no string or anything to "print" onto the list, do I need to add a string value to the struct and initialize it for all the values in the array (ProcessList)? If so, how can I go about that when adding to the listbox?
Yes, you can't cast somearray to object[]. ListBox.Items.AddRange requires object array to be passed as parameter.
Try this
initial_ProcessList.Items.AddRange(ProcessList.Cast<object>().ToArray());
or this
object[] ProcessList = new object[]
{
new process{ Proc_Id = 1, Proc_BurstTime = 3000, Proc_Priority = 1},
new process{ Proc_Id = 2, Proc_BurstTime = 5000, Proc_Priority = 2},
new process{ Proc_Id = 3, Proc_BurstTime = 1000, Proc_Priority = 3},
new process{ Proc_Id = 4, Proc_BurstTime = 10000, Proc_Priority = 4}
};
Or just loop and add
foreach (var p in ProcessList)
{
initial_ProcessList.Items.Add(p);
}
First the error, If the method is expecting a object[] it can't take a process[] for that. Now if you try something like:
initial_ProcessList.Items.AddRange(ProcessList.Cast<object>().ToArray());
The error would go away but the ListBox items would appear like:
WindowForm.ApplicationName.process1
WindowForm.ApplicationName.process1
.....
Which is the default ToString() implementation. So your actual problem is displaying those records in the ListBox. You have to override ToString method. like:
struct process
{
public int Proc_Id;
public int Proc_BurstTime;
public int Proc_Priority;
public override string ToString()
{
return string.Format("{0}, {1}, {2}", Proc_Id, Proc_BurstTime, Proc_Priority);
}
};
Later it would even more simpler if use the DataSource Property instead of adding an object array so simply do:
initial_ProcessList.DataSource = ProcessList;
This would show the result as:
You can also set DisplayMember and ValueMember property of your ListBox but it would require your structure to have properties, instead of fields. So you have to modify your struct as:
struct process
{
public int Proc_Id { get; set; }
public int Proc_BurstTime { get; set; } //auto implemented properties
public int Proc_Priority;
};
And then:
initial_ProcessList.ValueMember = "Proc_Id";
initial_ProcessList.DisplayMember = "Proc_BurstTime";
initial_ProcessList.DataSource = ProcessList;
This would show only Proc_BurstTime, and on selection you will get the value of related Proc_Id
I have the following arrays:
var original= new int[] { 2, 1, 3 };
var target = new int[] { 1, 3, 4 };
enum Operation {Added,Removed}
I would like to execute a LINQ query that would return the following:
{{2,Removed},{4,Added}}
Limitation: I would like LINQ to perform this very efficiently and avoid and O(n^2) style algorithms.
Perhaps a LINQ solution is not the best option in this case.
This will produce a dictionary with the result that you want.
Dictionary<int, Operation> difference = new Dictionary<int,Operation>();
foreach (int value in original) {
difference.Add(value, Operation.Removed);
}
foreach (int value in target) {
if (difference.ContainsKey(value)) {
difference.Remove(value);
} else {
difference.Add(value, Operation.Added);
}
}
To keep the size of the dictionary down, perhaps it's possible to loop the enumerations in parallell. I'll have a look at that...
Edit:
Here it is:
Dictionary<int, Operation> difference = new Dictionary<int,Operation>();
IEnumerator<int> o = ((IEnumerable<int>)original).GetEnumerator();
IEnumerator<int> t = ((IEnumerable<int>)target).GetEnumerator();
bool oActive=true, tActive=true;
while (oActive || tActive) {
if (oActive && (oActive = o.MoveNext())) {
if (difference.ContainsKey(o.Current)) {
difference.Remove(o.Current);
} else {
difference.Add(o.Current, Operation.Removed);
}
}
if (tActive && (tActive = t.MoveNext())) {
if (difference.ContainsKey(t.Current)) {
difference.Remove(t.Current);
} else {
difference.Add(t.Current, Operation.Added);
}
}
}
Edit2:
I did some performance testing. The first version runs 10%-20% faster, both with sorted lists and randomly ordered lists.
I made lists with numbers from 1 to 100000, randomly skipping 10% of the numbers. On my machine the first version of the code matches the lists in about 16 ms.
enum Operation { Added, Removed, }
static void Main(string[] args)
{
var original = new int[] { 2, 1, 3 };
var target = new int[] { 1, 3, 4 };
var result = original.Except(target)
.Select(i => new { Value = i, Operation = Operation.Removed, })
.Concat(
target.Except(original)
.Select(i => new { Value = i, Operation = Operation.Added, })
);
foreach (var item in result)
Console.WriteLine("{0}, {1}", item.Value, item.Operation);
}
I don't think you can do this with LINQ using only a single pass given the stock LINQ extension methods but but might be able to code a custom extension method that will. Your trade off will likely be the loss of deferred execution. It would be interesting to compare the relative performance of both.
You are out of luck. If, as you stated in the comments, the lists are not sorted you can't compute the difference you seek in a single forward pass. Consider:
{ 1, 2, 3, 4, 5, 6, 7, ...
{ 1, 2, 3, 6, 7, 8, 9, ...
At the point where the first difference in encountered (4 vs. 6) it's impossible for you to determine if you are looking at the removal of 4 & 5 (as would be the case if both lists were monotonically increasing, or the insertion of 6, 7, 8, & 9 as would be the case if the lists continued like so:
{ 1, 2, 3, 4, 5, 6, 7, 8, 9,...
{ 1, 2, 3, 6, 7, 8, 9, 4, 5, 6, 7, 8, 9,...
This will achieve the result in a single pass, however I'm not sure of the complexity of the GroupBy operation.
var original= new int[] { 1, 2, 3 };
var target = new int[] { 1, 3, 4 };
var output = original.Select( i => new { I = i, L = "o" } )
.Concat( target.Select( i => new { I = i, L = "t" } ) )
.GroupBy( i => i.I ).Where( i => i.Count() == 1 )
.Select( i => new { I = i.Key, S = (i.ElementAt( 0 ).L == "o" ? Operation.Removed : Operation.Added) } );