【SwiftUI】スタックView

スタックView

名称があっているか分からないが以下のスタックに記載されているViewについてまとめる。

VStack

HStack

ZStack

ZStack image

ZStackイメージ図

使用してみる

ダメなパターン

VStack, HStack, ZStackを以下のように同一階層に並べられない。
この場合、VStackが表示される。
これは、bodyはViewを1つ返す計算プロパティのため。

【SwifUI】計算プロパティ - おっさんの備忘録

var body: some View {
  VStack {
    Text("VStack")
  }
  HStack {
   Text("HStack")
  }
  ZStack {
    Text("ZStack")
  }
}

OKなパターン

以下のようにいずれかの子要素になら同一階層に並べられる。

var body:some View {
  VStack {
    Text("First Line.")
    
    ZStack {
      Circle()
        .frame(width: 150, height: 150, alignment: .center)
        .foregroundColor(.orange)
      Text("Hello, World")
    }
    HStack {
      Text("Second")
      Text("Line")
    }
  }
}

【iPhoneアプリ】乱数表示アプリ

乱数表示アプリ

入門として乱数表示アプリを作成する。
初期表示とボタンをクリックした際に乱数を表示する。

開発環境

  • macOS Monterey ver12.6.8
  • XCode Version14.2(14C18)

コード

struct ContentView: View {
  // SwiftUIではViewは保持するプロパティを変更できないため@Stateをつける
  @State var randomNumber = getRandomNumber()
  var body: some View {
    VStack(spacing: .zero) {
      Text(String(randomNumber))
      
      // Button(ボタンの文字, action:{処理})
      Button("click", action:{
        randomNumber = getRandomNumber()
      })
      .bold()
      .accentColor(Color.black)
      .background(Color.green)
    }
  }
}

func getRandomNumber() -> Int {
  return Int.random(in: 1 ..< 10);
}

【iPhoneアプリ】リストアプリ

リストアプリ

入門としてリストアプリを作成する。

開発環境

  • macOS Monterey ver12.6.8
  • XCode Version14.2(14C18)

プロジェクトの作成

XCodeで[Create a new Xcode project]-[App](場合によってはSingle View Application?)で[List App]を作成。

Appは用意されているアプリのひな形で、最初から1つの画面が容易されているテンプレート。

NavigationView{
  NavigationLink([リンクの文字列], destination:[遷移先のviewを指定])
}

全体

struct Fruit : Identifiable // 値がユニークであることを保証
{
  let linkView: String
  let linkStr: String
  let id = UUID()
}

struct ContentView: View
{
  let fruits = [
    Fruit(linkView: "りんご", linkStr: "Apple"),
    Fruit(linkView: "みかん", linkStr: "Orange"),
    Fruit(linkView: "ばなな", linkStr: "Banana"),
    Fruit(linkView: "いちご", linkStr: "Strawberry"),
  ]
  
  var body : some View
  {
    NavigationView
    {
      List(fruits)
      {
        fruit in NavigationLink(destination: Text(fruit.linkStr))
        {
          Text(fruit.linkView)
        }
      }
      .navigationTitle("英語を表示")
    }
  }
}

二分探索(binary search)

概要

データ探索アルゴリズムの1つ。
探索対象はソート済みのデータ群。
探索範囲を半分に絞り込む作業を繰り返す。

半分に絞り込む作業

①探索範囲の中心の値を比較する。
②探索対象より大きい場合は中心より左に存在する。
③探索対象より小さい場合は中心より右に存在する。
以下の図の黄背景は3、赤背景は12を探した場合のイメージ図

public static int Exec(List<int> numbers, int target)
{
  var left = 0;// 紫文字
  var right = numbers.Count - 1;// 赤文字
  
  while()
  {
    var half = left + (right - left) / 2;// ①
    var number = numbers[half];
    if (number == target)
      return half;
      
    if (target < number)// ②
      right = half - 1;
    else// ③
      left = half + 1;
  }
}

終了条件

上記は対象が必ず存在しない場合は無限ループになってしまう。
以下の図の黄背景は2, 赤背景は16を探した場合のイメージ図
濃い背景色の箇所が探索の打ち切り箇所で、探索対象の左端が右端以上になった場合は終了する

public static int Exec(List<int> numbers, int target)
{
  var left = 0;// 紫文字
  var right = numbers.Count - 1;// 赤文字
  
  while(left < righ)
  {
    var half = left + (right - left) / 2;// ①
    var number = numbers[half];
    if (number == target)
      return half;
      
    if (target < number)// ②
      right = half - 1;
    else// ③
      left = half + 1;
  }
}

計算量

以下の図のように検索対象を半分にしていく。


データ数8に対して3回繰り返すことによってデータを見つけることができた。
log28 = O(logn)

指数と対数

指数と対数

指数も対数も"ある数"と"掛け算をする回数"の関係。

指数

"ある数"と"掛け算をする回数"があらかじめ分かっている。
y=ax

対数

"ある数"と"掛け算をした結果"があらかじめ分かっている。
y=logab
a:底(ある数)
b:真数(掛け算をした結果)

1日ごとに2倍になる米粒

初日が2粒、2日目が4粒、3日目が8粒になる米粒について
4日目の米粒の数"y=24"⇒指数
16粒もらえる日"y=216"⇒対数
log28 = 3⇔23 = 8

常用対数

底が10の対数y=log10b

ネイピア数e

ベルヌーイが発見した対数と密接な関係にある数。
預金と利息の期間の関係で見つけたと言われている。
最初に預けた金額を1、1/n年後の預金額を(1 + 1/n)とする。
nが大きいほど1年後の金額がe = 2.718281...に近づく。

Bit演算

Bit演算(bitwise operation)

データをビット列(2進数の0と1の羅列)とみなして、ビットの移動やビット単位の論理演算

演算の種類

AND演算

var a = 45;    // 101101
var b = 25;    // 011001
var c = a & b; // 001001 = 9

OR演算

var a = 45;    // 101101
var b = 25;    // 011001
var c = a | b; // 111101 = 61

ビットシフト演算

各ビットを左または右へシフトする。

左へシフト

var a = 10;     // 01010
var l = a << 1; // 10100 = 20

右へシフト

var a = 10;     // 01010
var r = a >> 1; // 00101 = 5

ビットシフト演算を用いたフラグ管理

n番目のフラグは(1 << n)と表せる。

フラグが立っているかどうか

n番目が立っているかどうかは、ビットシフト演算とAND演算を使用して確認することができる。

var a = 10; // 01010
var result = a & (1 << n);

aのn番目のフラグが立っている場合のみresultは0より大きくなる。

フラグを立てる

n番目のフラグを立てるためには、ビットシフト演算とOR演算を使用することで可能。

var a = 10; // 01010
var b = a | (1 << n);

フラグを消す

n番目のフラグを消すためには、0をAND演算すればよい。

var a = 10; // 01010;
var b = a & ~(1 << n);

代表的な問題

部分集合

{x, y, z}の部分集合は各桁を"選ぶ"・"選ばない"で決まるため23(8)個存在する。
"選ぶ"がフラグが立っている状態とすると以下のようになる。
000, 001, 010, 011, 100, 101, 110, 111

部分集合のフラグの生成

var setElements = new List<int>(){1, 2, 3};
var elementNumber = setElements.Count;
for (var bit = 0; bit < 1 << elementNumber; ++bit)
  System.Console.WriteLine(System.Convert.ToString(bit, 2).PadLeft(elementNumber, '0'));

部分集合の生成

たとえば101の3桁目はzを、1桁目はxを選んでいることを表している。
n桁目を選んでいるかはフラグが立っているかどうかなので

for (var i = 0; i < elementNumber; ++i)
  if((bit & 1 << i) > 0)

例題

C - Many Formulas

数字からなる文字列が与えられる。
各数字の間に+を"入れる"or"入れない"の全てのパターンの和を求める。
s "123"⇒{1, 2, 3}, {12, 3}, {1, 23}, {123
"各数字の間に+を入れる"がビットが立っているとする。<br/?>

public static long GetAllEquationSumByBitFullSearch(string s)
  var gap = s.Length - 1;
  
  var ret = new List<long>();
  for (var bit = 0; bit < 1 << gap; ++bit)
  {
    var leftMove = 0;
    long sum = 0;
    for (var rightMove = 0; rightMove < gap; ++rightMove)
    {
      if ((bit & 1 << rightMove) == 0) continue;
      sum += long.Parse(s.Substring(leftMove, rightMove - leftMove + 1));
      leftMove = rightMove + 1;
    }
    sum += long.Parse(s.Substring(leftMove));
    ret.Add(sum);
  }
  return ret.Sum();

丸め誤差

丸め誤差

小数点以下の数は2進数で表現できないため近似値を使用している。
例えば"0.1(10)"は"0.0001100110011...(2)"となり、"0011"が永遠に循環する。
このため、適当な桁で結果が偶数になるように丸める(最近接偶数への丸め)。

以上から発生する誤差が丸め誤差
COBOLでは丸め誤差は発生しない。

public static double GetByDoubleVariant()
{
  var a = 0.1;
  var b = 0.1;
  var c = 2.5;
  return (a * b) / c; // 0.004000000000000001
}

対処法

整数型で計算

整数型に変換して、計算を行う。 ただし、小数に10n倍しただけでは整数になっていない可能性もあるので、Math.Roundメソッドを使う。

public static double GetByDoubleConvIntVariant()
{
  var a = 0.1;
  var b = 0.1;
  var c = 2.5;

  var aInt = Math.Round(a * 10, MidpointRounding.AwayFromZero);
  var bInt = Math.Round(b * 10, MidpointRounding.AwayFromZero);
  var cInt = Math.Round(c * 10, MidpointRounding.AwayFromZero);
  return ((aInt * bInt) / cInt) / 10; // 0.004
}

任意精度型を使用

C#ではDecimal型が該当する。ただしパフォーマンスが悪い。

public static decimal GetByDecimalVariant()
{
  var a = 0.1m;
  var b = 0.1m;
  var c = 2.5m;

  return (a * b) / c;
}